diff --git a/.github/workflows/push_ocm.yaml b/.github/workflows/push_ocm.yaml index f33d405f9..582987f17 100644 --- a/.github/workflows/push_ocm.yaml +++ b/.github/workflows/push_ocm.yaml @@ -1,7 +1,7 @@ name: publish as latest on: # publish on pushes to the main branch (image tagged as "latest") - # https://github.com/open-component-model/ocm/pkgs/container/ocm + # https://ocm.software/ocm/pkgs/container/ocm push: branches: - main diff --git a/.github/workflows/release-drafter.yaml b/.github/workflows/release-drafter.yaml index 61b592d2d..e97af0a8b 100644 --- a/.github/workflows/release-drafter.yaml +++ b/.github/workflows/release-drafter.yaml @@ -28,7 +28,7 @@ jobs: - name: Set Version run: | - RELEASE_VERSION=v$(go run $GITHUB_WORKSPACE/pkg/version/generate print-version) + RELEASE_VERSION=v$(go run $GITHUB_WORKSPACE/api/version/generate print-version) echo "release version is $RELEASE_VERSION" echo "RELEASE_VERSION=$RELEASE_VERSION" >> $GITHUB_ENV diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 2c4af9e9b..f9ae26192 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -37,14 +37,14 @@ jobs: run: | echo "Release Job Arguments" if ${{ github.event.inputs.release_candidate }}; then - v="v$(go run $GITHUB_WORKSPACE/pkg/version/generate --no-dev print-rc-version ${{ github.event.inputs.prerelease }})" + v="v$(go run $GITHUB_WORKSPACE/api/version/generate --no-dev print-rc-version ${{ github.event.inputs.prerelease }})" if [ -n "${{ github.event.inputs.prerelease }}" ]; then echo "Candidate: $v" else echo "Candidate: $v (taken from source)" fi else - v="v$(go run $GITHUB_WORKSPACE/pkg/version/generate print-version)" + v="v$(go run $GITHUB_WORKSPACE/api/version/generate print-version)" echo "Final Release: $v" if ${{ github.event.inputs.create_branch }}; then echo "with release branch creation" @@ -55,13 +55,13 @@ jobs: - name: Set Base Version run: | - BASE_VERSION=v$(go run $GITHUB_WORKSPACE/pkg/version/generate print-version) + BASE_VERSION=v$(go run $GITHUB_WORKSPACE/api/version/generate print-version) echo "BASE_VERSION=$BASE_VERSION" >> $GITHUB_ENV - name: Set Pre-Release Version if: inputs.release_candidate == true run: | - RELEASE_VERSION=v$(go run $GITHUB_WORKSPACE/pkg/version/generate --no-dev print-rc-version ${{ github.event.inputs.prerelease }}) + RELEASE_VERSION=v$(go run $GITHUB_WORKSPACE/api/version/generate --no-dev print-rc-version ${{ github.event.inputs.prerelease }}) echo "RELEASE_VERSION=$RELEASE_VERSION" >> $GITHUB_ENV - name: Set Version @@ -164,13 +164,13 @@ jobs: - name: Set Base Version run: | - BASE_VERSION=v$(go run $GITHUB_WORKSPACE/pkg/version/generate print-version) + BASE_VERSION=v$(go run $GITHUB_WORKSPACE/api/version/generate print-version) echo "BASE_VERSION=$BASE_VERSION" >> $GITHUB_ENV - name: Set Pre-Release Version if: inputs.release_candidate == true run: | - RELEASE_VERSION=v$(go run $GITHUB_WORKSPACE/pkg/version/generate --no-dev print-rc-version ${{ github.event.inputs.prerelease }}) + RELEASE_VERSION=v$(go run $GITHUB_WORKSPACE/api/version/generate --no-dev print-rc-version ${{ github.event.inputs.prerelease }}) echo "RELEASE_VERSION=$RELEASE_VERSION" >> $GITHUB_ENV echo "release name is $RELEASE_VERSION" @@ -247,7 +247,7 @@ jobs: run: | n="releases/${{env.RELEASE_VERSION}}" git checkout -b "$n" - v="$(go run ./pkg/version/generate bump-patch)" + v="$(go run ./api/version/generate bump-patch)" echo "$v" > VERSION git add VERSION git commit -m "Prepare Development of v$v" @@ -258,7 +258,7 @@ jobs: run: | set -e git checkout ${GITHUB_REF#refs/heads/} - v="$(go run ./pkg/version/generate bump-version)" + v="$(go run ./api/version/generate bump-version)" echo "$v" > VERSION git add VERSION git commit -m "Update version file to $v" diff --git a/.github/workflows/releasenotes.yaml b/.github/workflows/releasenotes.yaml index e3ebcaa27..d20a5e43b 100644 --- a/.github/workflows/releasenotes.yaml +++ b/.github/workflows/releasenotes.yaml @@ -29,7 +29,7 @@ jobs: - name: Set Version run: | - RELEASE_VERSION=v$(go run $GITHUB_WORKSPACE/pkg/version/generate print-version) + RELEASE_VERSION=v$(go run $GITHUB_WORKSPACE/api/version/generate print-version) echo "release version is $RELEASE_VERSION" echo "RELEASE_VERSION=$RELEASE_VERSION" >> $GITHUB_ENV @@ -48,7 +48,7 @@ jobs: run: | set -e echo "Release Notes:\n $RELEASE_NOTES'" - v="v$(go run $GITHUB_WORKSPACE/pkg/version/generate print-version)" + v="v$(go run $GITHUB_WORKSPACE/api/version/generate print-version)" f="docs/releasenotes/$v.md" echo "$RELEASE_NOTES" > "$f" git add "$f" diff --git a/.golangci.yaml b/.golangci.yaml index e8aa23bd0..a2d6efa90 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -98,7 +98,7 @@ linters-settings: - blank - dot - default - - prefix(github.com/open-component-model/ocm) + - prefix(ocm.software/ocm) custom-order: true funlen: lines: 110 @@ -129,7 +129,7 @@ issues: exclude-dirs: - "hack" # External code from containerd/containerd - - "pkg/docker" + - "api/tech/docker" exclude: - composites exclude-rules: diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 52c4f8c76..4253d4e1c 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -9,7 +9,7 @@ builds: binary: ocm main: ./cmds/ocm/main.go ldflags: - - -s -w -X github.com/open-component-model/ocm/pkg/version.gitVersion={{.Version}} -X github.com/open-component-model/ocm/pkg/version.gitCommit={{.Commit}} -X github.com/open-component-model/ocm/pkg/version.buildDate={{.CommitDate}} + - -s -w -X ocm.software/ocm/api/version.gitVersion={{.Version}} -X ocm.software/ocm/api/version.gitCommit={{.Commit}} -X ocm.software/ocm/api/version.buildDate={{.CommitDate}} env: - CGO_ENABLED=0 id: linux diff --git a/.reuse/dep5 b/.reuse/dep5 index e75df02db..55264e46d 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -1,7 +1,7 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: ocm Upstream-Contact: ospo@sap.com -Source: https://github.com/open-component-model/ocm +Source: https://ocm.software/ocm Disclaimer: The code in this project may include calls to APIs ("API Calls") of SAP or third-party products or services developed outside of this project ("External Products"). @@ -28,6 +28,6 @@ Files: ** Copyright: 2024 SAP SE or an SAP affiliate company and Open Component Model contributors License: Apache-2.0 -Files: pkg/contexts/ocm/blobhandler/handlers/generic/npm/publish.go +Files: api/ocm/extensions/blobhandler/handlers/generic/npm/publish.go Copyright: Copyright 2021 - cloverstd License: MIT diff --git a/Dockerfile b/Dockerfile index 45a407ccf..2364fc51b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,10 +14,10 @@ RUN --mount=type=cache,target=/root/.cache/go-build go mod download COPY . . RUN --mount=type=cache,target=/root/.cache/go-build \ - export VERSION=$(go run pkg/version/generate/release_generate.go print-rc-version) && \ + export VERSION=$(go run api/version/generate/release_generate.go print-rc-version) && \ export NOW=$(date -u +%FT%T%z) && \ go build -trimpath -ldflags \ - "-s -w -X github.com/open-component-model/ocm/pkg/version.gitVersion=$VERSION -X github.com/open-component-model/ocm/pkg/version.buildDate=$NOW" \ + "-s -w -X ocm.software/ocm/api/version.gitVersion=$VERSION -X ocm.software/ocm/api/version.buildDate=$NOW" \ -o /bin/ocm ./cmds/ocm/main.go FROM alpine:${ALPINE_VERSION} diff --git a/Makefile b/Makefile index e35074471..80eaadd33 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ NAME := ocm REPO_ROOT := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) GITHUBORG ?= open-component-model OCMREPO ?= ghcr.io/$(GITHUBORG)/ocm -VERSION := $(shell go run pkg/version/generate/release_generate.go print-rc-version $(CANDIDATE)) +VERSION := $(shell go run api/version/generate/release_generate.go print-rc-version $(CANDIDATE)) EFFECTIVE_VERSION := $(VERSION)+$(shell git rev-parse HEAD) GIT_TREE_STATE := $(shell [ -z "$$(git status --porcelain 2>/dev/null)" ] && echo clean || echo dirty) COMMIT := $(shell git rev-parse --verify HEAD) @@ -23,17 +23,17 @@ GOPATH := $(shell go env GOPATH) NOW := $(shell date -u +%FT%T%z) BUILD_FLAGS := "-s -w \ - -X github.com/open-component-model/ocm/pkg/version.gitVersion=$(EFFECTIVE_VERSION) \ - -X github.com/open-component-model/ocm/pkg/version.gitTreeState=$(GIT_TREE_STATE) \ - -X github.com/open-component-model/ocm/pkg/version.gitCommit=$(COMMIT) \ - -X github.com/open-component-model/ocm/pkg/version.buildDate=$(NOW)" + -X ocm.software/ocm/api/version.gitVersion=$(EFFECTIVE_VERSION) \ + -X ocm.software/ocm/api/version.gitTreeState=$(GIT_TREE_STATE) \ + -X ocm.software/ocm/api/version.gitCommit=$(COMMIT) \ + -X ocm.software/ocm/api/version.buildDate=$(NOW)" COMPONENTS ?= ocmcli helminstaller demoplugin ecrplugin helmdemo subchartsdemo .PHONY: build build: ${SOURCES} mkdir -p bin - go build ./pkg/... + go build ./api/... go build ./examples/... CGO_ENABLED=0 go build -ldflags $(BUILD_FLAGS) -o bin/ocm ./cmds/ocm CGO_ENABLED=0 go build -ldflags $(BUILD_FLAGS) -o bin/helminstaller ./cmds/helminstaller @@ -55,7 +55,7 @@ install-requirements: .PHONY: prepare prepare: generate format generate-deepcopy build test check -EFFECTIVE_DIRECTORIES := $(REPO_ROOT)/cmds/ocm/... $(REPO_ROOT)/cmds/helminstaller/... $(REPO_ROOT)/cmds/ecrplugin/... $(REPO_ROOT)/cmds/demoplugin/... $(REPO_ROOT)/cmds/cliplugin/... $(REPO_ROOT)/examples/... $(REPO_ROOT)/cmds/subcmdplugin/... $(REPO_ROOT)/pkg/... +EFFECTIVE_DIRECTORIES := $(REPO_ROOT)/cmds/ocm/... $(REPO_ROOT)/cmds/helminstaller/... $(REPO_ROOT)/cmds/ecrplugin/... $(REPO_ROOT)/cmds/demoplugin/... $(REPO_ROOT)/cmds/cliplugin/... $(REPO_ROOT)/examples/... $(REPO_ROOT)/cmds/subcmdplugin/... $(REPO_ROOT)/api/... .PHONY: format format: @@ -89,7 +89,7 @@ generate: .PHONY: generate-deepcopy generate-deepcopy: controller-gen - $(CONTROLLER_GEN) object paths=./pkg/contexts/ocm/compdesc/versions/... paths=./pkg/contexts/ocm/compdesc/meta/... + $(CONTROLLER_GEN) object paths=./api/ocm/compdesc/versions/... paths=./api/ocm/compdesc/meta/... .PHONY: controller-gen controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. diff --git a/README.md b/README.md index cb7225c81..e50c42471 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # Open Component Model [![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/7156/badge)](https://bestpractices.coreinfrastructure.org/projects/7156) -[![REUSE status](https://api.reuse.software/badge/github.com/open-component-model/ocm)](https://api.reuse.software/info/github.com/open-component-model/ocm) -[![OCM Integration Tests](https://github.com/open-component-model/ocm-integrationtest/actions/workflows/integrationtest.yaml/badge.svg?branch=main)](https://open-component-model.github.io/ocm-integrationtest/report.html) -[![Go Report Card](https://goreportcard.com/badge/github.com/open-component-model/ocm)](https://goreportcard.com/report/github.com/open-component-model/ocm) +[![REUSE status](https://api.reuse.software/badge/ocm.software/ocm)](https://api.reuse.software/info/ocm.software/ocm) +[![OCM Integration Tests](https://ocm.software/ocm-integrationtest/actions/workflows/integrationtest.yaml/badge.svg?branch=main)](https://open-component-model.github.io/ocm-integrationtest/report.html) +[![Go Report Card](https://goreportcard.com/badge/ocm.software/ocm)](https://goreportcard.com/report/ocm.software/ocm) The Open Component Model (OCM) is an open standard to describe software bills of delivery (SBOD). OCM is a technology-agnostic and machine-readable format focused on the software artifacts that must be delivered for software products. @@ -11,14 +11,14 @@ Check out the [the main OCM project web page](https://ocm.software) to find out ## OCM Specifications -OCM describes delivery [artifacts](https://github.com/open-component-model/ocm-spec/tree/main/doc/01-model/02-elements-toplevel.md#artifacts-resources-and-sources) that can be accessed from many types of [component repositories](https://github.com/open-component-model/ocm-spec/tree/main/doc/01-model/01-model.md#component-repositories). It defines a set of semantic, formatting, and other types of specifications that can be found in the [`ocm-spec` repository](https://github.com/open-component-model/ocm-spec). Start learning about the core concepts of OCM elements [here](https://github.com/open-component-model/ocm-spec/tree/main/doc/01-model/02-elements-toplevel.md#model-elements). +OCM describes delivery [artifacts](https://ocm.software/ocm-spec/tree/main/doc/01-model/02-elements-toplevel.md#artifacts-resources-and-sources) that can be accessed from many types of [component repositories](https://ocm.software/ocm-spec/tree/main/doc/01-model/01-model.md#component-repositories). It defines a set of semantic, formatting, and other types of specifications that can be found in the [`ocm-spec` repository](https://ocm.software/ocm-spec). Start learning about the core concepts of OCM elements [here](https://ocm.software/ocm-spec/tree/main/doc/01-model/02-elements-toplevel.md#model-elements). ## OCM Library This project provides a Go library containing an API for interacting with the -[Open Component Model (OCM)](https://github.com/open-component-model/ocm-spec) elements and mechanisms. +[Open Component Model (OCM)](https://ocm.software/ocm-spec) elements and mechanisms. -The library currently supports the following [repository mappings](https://github.com/open-component-model/ocm-spec/tree/main/doc/03-persistence/02-mappings.md#mappings-for-ocm-persistence): +The library currently supports the following [repository mappings](https://ocm.software/ocm-spec/tree/main/doc/03-persistence/02-mappings.md#mappings-for-ocm-persistence): - **OCI**: Use the repository prefix path of an OCI repository to implement an OCM repository. @@ -40,12 +40,12 @@ Additionally, OCM provides a generic solution for how to: The [`ocm` CLI](docs/reference/ocm.md) may also be used to interact with OCM mechanisms. It makes it easy to create component versions and embed them in build processes. -The `ocm` CLI documentation can be found [here](<(https://github.com/open-component-model/ocm/blob/main/docs/reference/ocm.md)>). +The `ocm` CLI documentation can be found [here](<(https://ocm.software/ocm/blob/main/docs/reference/ocm.md)>). -The code for the CLI can be found in [packageĀ `cmds/ocm`](https://github.com/open-component-model/ocm/blob/main/cmds/ocm). +The code for the CLI can be found in [packageĀ `cmds/ocm`](https://ocm.software/ocm/blob/main/cmds/ocm). The OCI and OCM support can be found in packages -[`pkg/contexts/oci`](pkg/contexts/oci) and [`pkg/contexts/ocm`](pkg/contexts/ocm). +[`pkg/contexts/oci`](pkg/contexts/oci) and [`api/ocm`](api/ocm). ## Installation @@ -56,7 +56,7 @@ Install the latest release from any of - [AUR](https://aur.archlinux.org/packages/ocm-cli) - [Docker](https://www.docker.com/) - [Podman](https://podman.io/) -- [GitHub Releases](https://github.com/open-component-model/ocm/releases) +- [GitHub Releases](https://ocm.software/ocm/releases) ### Bash @@ -140,9 +140,9 @@ podman build -t ocm --build-arg GO_VERSION=1.22 --build-arg ALPINE_VERSION=3.19 ## Examples -An example of how to use the `ocm` CLI in a Makefile can be found in [`examples/make`](https://github.com/open-component-model/ocm/blob/main/examples/make/Makefile). +An example of how to use the `ocm` CLI in a Makefile can be found in [`examples/make`](https://ocm.software/ocm/blob/main/examples/make/Makefile). -More comprehensive examples can be taken from the [`components`](https://github.com/open-component-model/ocm/tree/main/components) contained in this repository. [Here](components/helmdemo/README.md) a complete component build including a multi-arch image is done and finally packaged into a CTF archive which can be tranported into an OCI repository. See the readme files for details. +More comprehensive examples can be taken from the [`components`](https://ocm.software/ocm/tree/main/components) contained in this repository. [Here](components/helmdemo/README.md) a complete component build including a multi-arch image is done and finally packaged into a CTF archive which can be tranported into an OCI repository. See the readme files for details. ## Contributing @@ -154,4 +154,4 @@ OCM follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/m Copyright 2024 SAP SE or an SAP affiliate company and Open Component Model contributors. Please see our [LICENSE](LICENSE) for copyright and license information. -Detailed information including third-party components and their licensing/copyright information is available [via the REUSE tool](https://api.reuse.software/info/github.com/open-component-model/ocm). +Detailed information including third-party components and their licensing/copyright information is available [via the REUSE tool](https://api.reuse.software/info/ocm.software/ocm). diff --git a/api/cli/builder.go b/api/cli/builder.go new file mode 100644 index 000000000..5f20b6d03 --- /dev/null +++ b/api/cli/builder.go @@ -0,0 +1,44 @@ +package clictx + +import ( + "context" + "io" + + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/cli/internal" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/ocm" +) + +func WithContext(ctx context.Context) internal.Builder { + return internal.Builder{}.WithContext(ctx) +} + +func WithSharedAttributes(ctx datacontext.AttributesContext) internal.Builder { + return internal.Builder{}.WithSharedAttributes(ctx) +} + +func WithOCM(ctx ocm.Context) internal.Builder { + return internal.Builder{}.WithOCM(ctx) +} + +func WithFileSystem(fs vfs.FileSystem) internal.Builder { + return internal.Builder{}.WithFileSystem(fs) +} + +func WithOutput(w io.Writer) internal.Builder { + return internal.Builder{}.WithOutput(w) +} + +func WithErrorOutput(w io.Writer) internal.Builder { + return internal.Builder{}.WithErrorOutput(w) +} + +func WithInput(r io.Reader) internal.Builder { + return internal.Builder{}.WithInput(r) +} + +func New(mode ...datacontext.BuilderMode) internal.Context { + return internal.Builder{}.New(mode...) +} diff --git a/api/cli/config/config_test.go b/api/cli/config/config_test.go new file mode 100644 index 000000000..ff7536f5a --- /dev/null +++ b/api/cli/config/config_test.go @@ -0,0 +1,61 @@ +package config_test + +import ( + "encoding/json" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/cli/config" + "ocm.software/ocm/api/oci/extensions/repositories/ocireg" + ocmocireg "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" +) + +var DefaultContext = clictx.New() + +func normalize(i interface{}) ([]byte, error) { + data, err := json.Marshal(i) + if err != nil { + return nil, err + } + var generic map[string]interface{} + err = json.Unmarshal(data, &generic) + if err != nil { + return nil, err + } + return json.Marshal(generic) +} + +var _ = Describe("command config", func() { + ocispec := ocireg.NewRepositorySpec("ghcr.io") + + ocidata, err := normalize(ocispec) + Expect(err).To(Succeed()) + + ocmspec := ocmocireg.NewRepositorySpec("gcr.io", nil) + ocmdata, err := normalize(ocmspec) + Expect(err).To(Succeed()) + + specdata := "{\"ociRepositories\":{\"oci\":" + string(ocidata) + "},\"ocmRepositories\":{\"ocm\":" + string(ocmdata) + "},\"type\":\"" + config.OCMCmdConfigType + "\"}" + + Context("serialize", func() { + It("serializes config", func() { + cfg := config.New() + err := cfg.AddOCIRepository("oci", ocispec) + Expect(err).To(Succeed()) + err = cfg.AddOCMRepository("ocm", ocmspec) + Expect(err).To(Succeed()) + + data, err := normalize(cfg) + + Expect(err).To(Succeed()) + Expect(data).To(Equal([]byte(specdata))) + + cfg2 := config.New() + err = json.Unmarshal(data, cfg2) + Expect(err).To(Succeed()) + Expect(cfg2).To(Equal(cfg)) + }) + }) +}) diff --git a/pkg/contexts/clictx/config/suite_test.go b/api/cli/config/suite_test.go similarity index 100% rename from pkg/contexts/clictx/config/suite_test.go rename to api/cli/config/suite_test.go diff --git a/api/cli/config/type.go b/api/cli/config/type.go new file mode 100644 index 000000000..84ef23212 --- /dev/null +++ b/api/cli/config/type.go @@ -0,0 +1,100 @@ +package config + +import ( + "fmt" + + "ocm.software/ocm/api/cli/internal" + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/config/cpi" + ocicpi "ocm.software/ocm/api/oci/cpi" + ocmcpi "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + OCMCmdConfigType = "ocm.cmd" + cpi.OCM_CONFIG_TYPE_SUFFIX + OCMCmdConfigTypeV1 = OCMCmdConfigType + runtime.VersionSeparator + "v1" +) + +func init() { + cpi.RegisterConfigType(cpi.NewConfigType[*Config](OCMCmdConfigType, usage)) + cpi.RegisterConfigType(cpi.NewConfigType[*Config](OCMCmdConfigTypeV1, usage)) +} + +// Config describes a memory based repository interface. +type Config struct { + runtime.ObjectVersionedType `json:",inline"` + OCMRepositories map[string]*ocmcpi.GenericRepositorySpec `json:"ocmRepositories,omitempty"` + OCIRepositories map[string]*ocicpi.GenericRepositorySpec `json:"ociRepositories,omitempty"` +} + +// New creates a new memory ConfigSpec. +func New() *Config { + return &Config{ + ObjectVersionedType: runtime.NewVersionedTypedObject(OCMCmdConfigType), + } +} + +func (a *Config) GetType() string { + return OCMCmdConfigType +} + +func (a *Config) AddOCIRepository(name string, spec ocicpi.RepositorySpec) error { + g, err := ocicpi.ToGenericRepositorySpec(spec) + if err != nil { + return fmt.Errorf("unable to convert oci repository spec to generic spec: %w", err) + } + + if a.OCIRepositories == nil { + a.OCIRepositories = map[string]*ocicpi.GenericRepositorySpec{} + } + + a.OCIRepositories[name] = g + + return nil +} + +func (a *Config) AddOCMRepository(name string, spec ocmcpi.RepositorySpec) error { + g, err := ocmcpi.ToGenericRepositorySpec(spec) + if err != nil { + return fmt.Errorf("unable to convert ocm repository spec to generic spec: %w", err) + } + + if a.OCMRepositories == nil { + a.OCMRepositories = map[string]*ocmcpi.GenericRepositorySpec{} + } + + a.OCMRepositories[name] = g + + return nil +} + +func (a *Config) ApplyTo(ctx config.Context, target interface{}) error { + t, ok := target.(internal.Context) + if !ok { + return config.ErrNoContext(OCMCmdConfigType) + } + for n, s := range a.OCIRepositories { + t.OCI().Context().SetAlias(n, s) + } + for n, s := range a.OCMRepositories { + t.OCM().Context().SetAlias(n, s) + } + return nil +} + +const usage = ` +The config type ` + OCMCmdConfigType + ` can be used to +configure predefined aliases for dedicated OCM repositories and +OCI registries. + +
+   type: ` + OCMCmdConfigType + `
+   ocmRepositories:
+       <name>: <specification of OCM repository>
+   ...
+   ociRepositories:
+       <name>: <specification of OCI registry>
+   ...
+
+` diff --git a/api/cli/interface.go b/api/cli/interface.go new file mode 100644 index 000000000..5d216fb3a --- /dev/null +++ b/api/cli/interface.go @@ -0,0 +1,15 @@ +package clictx + +import ( + "ocm.software/ocm/api/cli/internal" +) + +type ( + Context = internal.Context + OCI = internal.OCI + OCM = internal.OCM +) + +func DefaultContext() Context { + return internal.DefaultContext +} diff --git a/api/cli/internal/builder.go b/api/cli/internal/builder.go new file mode 100644 index 000000000..fb980e003 --- /dev/null +++ b/api/cli/internal/builder.go @@ -0,0 +1,84 @@ +package internal + +import ( + "context" + "io" + + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/utils/out" +) + +type Builder struct { + ctx context.Context + shared datacontext.AttributesContext + ocm ocm.Context + out out.Context + filesystem vfs.FileSystem +} + +func (b *Builder) getContext() context.Context { + if b.ctx == nil { + return context.Background() + } + return b.ctx +} + +func (b Builder) WithContext(ctx context.Context) Builder { + b.ctx = ctx + return b +} + +func (b Builder) WithFileSystem(fs vfs.FileSystem) Builder { + b.filesystem = fs + return b +} + +func (b Builder) WithSharedAttributes(ctx datacontext.AttributesContext) Builder { + b.shared = ctx + return b +} + +func (b Builder) WithOCM(ctx ocm.Context) Builder { + b.ocm = ctx + return b +} + +func (b Builder) WithOutput(w io.Writer) Builder { + b.out = out.WithOutput(b.out, w) + return b +} + +func (b Builder) WithErrorOutput(w io.Writer) Builder { + b.out = out.WithErrorOutput(b.out, w) + return b +} + +func (b Builder) WithInput(r io.Reader) Builder { + b.out = out.WithInput(b.out, r) + return b +} + +func (b Builder) Bound() (Context, context.Context) { + c := b.New() + return c, context.WithValue(b.getContext(), key, c) +} + +func (b Builder) New(m ...datacontext.BuilderMode) Context { + mode := datacontext.Mode(m...) + ctx := b.getContext() + + if b.ocm == nil { + var ok bool + b.ocm, ok = ocm.DefinedForContext(ctx) + if !ok && mode != datacontext.MODE_SHARED { + b.ocm = ocm.New(mode) + } + } + if b.shared == nil { + b.shared = b.ocm.AttributesContext() + } + return datacontext.SetupContext(mode, newContext(b.shared, b.ocm, out.NewFor(b.out), b.filesystem, b.shared)) +} diff --git a/api/cli/internal/context.go b/api/cli/internal/context.go new file mode 100644 index 000000000..d5314bd32 --- /dev/null +++ b/api/cli/internal/context.go @@ -0,0 +1,313 @@ +package internal + +import ( + "context" + "io" + "reflect" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/general" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/config" + cfgcpi "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/oci" + ctfoci "ocm.software/ocm/api/oci/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm" + ctfocm "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/out" +) + +const CONTEXT_TYPE = "ocm.cmd" + datacontext.OCM_CONTEXT_SUFFIX + +type OCI interface { + Context() oci.Context + OpenCTF(path string) (oci.Repository, error) +} + +type OCM interface { + Context() ocm.Context + OpenCTF(path string) (ocm.Repository, error) +} + +type FileSystem struct { + vfs.FileSystem +} + +func (f *FileSystem) ApplyOption(options accessio.Options) error { + options.SetPathFileSystem(f.FileSystem) + return nil +} + +type ContextProvider interface { + CLIContext() Context +} + +type Context interface { + datacontext.Context + ContextProvider + datacontext.ContextProvider + config.ContextProvider + credentials.ContextProvider + oci.ContextProvider + ocm.ContextProvider + + FileSystem() *FileSystem + + OCI() OCI + OCM() OCM + + ApplyOption(options accessio.Options) error + + out.Context + WithStdIO(r io.Reader, o io.Writer, e io.Writer) Context +} + +var key = reflect.TypeOf(_context{}) + +// DefaultContext is the default context initialized by init functions. +var DefaultContext = Builder{}.New(datacontext.MODE_SHARED) + +// ForContext returns the Context to use for context.Context. +// This is either an explicit context or the default context. +// The returned context incorporates the given context. +func ForContext(ctx context.Context) Context { + c, _ := datacontext.ForContextByKey(ctx, key, DefaultContext) + return c.(Context) +} + +func DefinedForContext(ctx context.Context) (Context, bool) { + c, ok := datacontext.ForContextByKey(ctx, key, DefaultContext) + if c != nil { + return c.(Context), ok + } + return nil, ok +} + +//////////////////////////////////////////////////////////////////////////////// + +type _InternalContext = datacontext.InternalContext + +type _context struct { + _InternalContext + updater cfgcpi.Updater + + sharedAttributes datacontext.AttributesContext + + credentials credentials.Context + oci *_oci + ocm *_ocm + + out out.Context +} + +var ( + _ Context = (*_context)(nil) + _ datacontext.ViewCreator[Context] = (*_context)(nil) +) + +// gcWrapper is used as garbage collectable +// wrapper for a context implementation +// to establish a runtime finalizer. +type gcWrapper struct { + datacontext.GCWrapper + *_context +} + +func newView(c *_context, ref ...bool) Context { + if general.Optional(ref...) { + return datacontext.FinalizedContext[gcWrapper](c) + } + return c +} + +func (w *gcWrapper) SetContext(c *_context) { + w._context = c +} + +func newContext(shared datacontext.AttributesContext, ocmctx ocm.Context, outctx out.Context, fs vfs.FileSystem, delegates datacontext.Delegates) Context { + if outctx == nil { + outctx = out.New() + } + if shared == nil { + shared = ocmctx.AttributesContext() + } + c := &_context{ + sharedAttributes: datacontext.PersistentContextRef(shared), + credentials: datacontext.PersistentContextRef(ocmctx.CredentialsContext()), + out: outctx, + } + c._InternalContext = datacontext.NewContextBase(c, CONTEXT_TYPE, key, ocmctx.GetAttributes(), delegates) + c.updater = cfgcpi.NewUpdater(datacontext.PersistentContextRef(ocmctx.CredentialsContext().ConfigContext()), c) + ocmctx = datacontext.PersistentContextRef(ocmctx) + c.oci = newOCI(c, ocmctx) + c.ocm = newOCM(c, ocmctx) + if fs != nil { + vfsattr.Set(c.AttributesContext(), fs) + } + return newView(c, true) +} + +func (c *_context) CreateView() Context { + return newView(c, true) +} + +func (c *_context) CLIContext() Context { + return newView(c) +} + +func (c *_context) Update() error { + return c.updater.Update() +} + +func (c *_context) AttributesContext() datacontext.AttributesContext { + return c.sharedAttributes +} + +func (c *_context) ConfigContext() config.Context { + return c.updater.GetContext() +} + +func (c *_context) CredentialsContext() credentials.Context { + return c.credentials +} + +func (c *_context) OCIContext() oci.Context { + return c.oci.Context() +} + +func (c *_context) OCMContext() ocm.Context { + return c.ocm.Context() +} + +func (c *_context) FileSystem() *FileSystem { + return &FileSystem{vfsattr.Get(c.CLIContext())} +} + +func (c *_context) OCI() OCI { + return c.oci +} + +func (c *_context) OCM() OCM { + return c.ocm +} + +func (c *_context) ApplyOption(options accessio.Options) error { + options.SetPathFileSystem(c.FileSystem()) + return nil +} + +func (c *_context) StdOut() io.Writer { + return c.out.StdOut() +} + +func (c *_context) StdErr() io.Writer { + return c.out.StdErr() +} + +func (c *_context) StdIn() io.Reader { + return c.out.StdIn() +} + +func (c *_context) WithStdIO(r io.Reader, o io.Writer, e io.Writer) Context { + return &_view{ + Context: c.CLIContext(), + out: out.NewFor(out.WithStdIO(c.out, r, o, e)), + } +} + +//////////////////////////////////////////////////////////////////////////////// + +type _view struct { + Context + out out.Context +} + +func (c *_view) StdOut() io.Writer { + return c.out.StdOut() +} + +func (c *_view) StdErr() io.Writer { + return c.out.StdErr() +} + +func (c *_view) StdIn() io.Reader { + return c.out.StdIn() +} + +func (c *_view) WithStdIO(r io.Reader, o io.Writer, e io.Writer) Context { + return &_view{ + Context: c.CLIContext(), + out: out.NewFor(out.WithStdIO(c.out, r, o, e)), + } +} + +//////////////////////////////////////////////////////////////////////////////// +// the coding for _oci and _ocm is identical except the package used: +// _oci uses oci and ctfoci +// _ocm uses ocm and ctfocm + +type _oci struct { + cli *_context + ctx oci.Context + repos map[string]oci.RepositorySpec +} + +func newOCI(ctx *_context, ocmctx ocm.Context) *_oci { + return &_oci{ + cli: ctx, + ctx: ocmctx.OCIContext(), + repos: map[string]oci.RepositorySpec{}, + } +} + +func (c *_oci) Context() oci.Context { + return c.ctx +} + +func (c *_oci) OpenCTF(path string) (oci.Repository, error) { + ok, err := vfs.Exists(c.cli.FileSystem(), path) + if err != nil { + return nil, err + } + if !ok { + return nil, errors.ErrNotFound("file", path) + } + return ctfoci.Open(c.ctx, accessobj.ACC_WRITABLE, path, 0, accessio.PathFileSystem(c.cli.FileSystem())) +} + +//////////////////////////////////////////////////////////////////////////////// + +type _ocm struct { + cli *_context + ctx ocm.Context + repos map[string]ocm.RepositorySpec +} + +func newOCM(ctx *_context, ocmctx ocm.Context) *_ocm { + return &_ocm{ + cli: ctx, + ctx: ocmctx, + repos: map[string]ocm.RepositorySpec{}, + } +} + +func (c *_ocm) Context() ocm.Context { + return c.ctx +} + +func (c *_ocm) OpenCTF(path string) (ocm.Repository, error) { + ok, err := vfs.Exists(c.cli.FileSystem(), path) + if err != nil { + return nil, err + } + if !ok { + return nil, errors.ErrNotFound("file", path) + } + return ctfocm.Open(c.ctx, accessobj.ACC_WRITABLE, path, 0, c.cli.FileSystem()) +} diff --git a/pkg/contexts/config/README.md b/api/config/README.md similarity index 100% rename from pkg/contexts/config/README.md rename to api/config/README.md diff --git a/api/config/builder.go b/api/config/builder.go new file mode 100644 index 000000000..ac603d212 --- /dev/null +++ b/api/config/builder.go @@ -0,0 +1,24 @@ +package config + +import ( + "context" + + "ocm.software/ocm/api/config/internal" + "ocm.software/ocm/api/datacontext" +) + +func WithContext(ctx context.Context) internal.Builder { + return internal.Builder{}.WithContext(ctx) +} + +func WithSharedAttributes(ctx datacontext.AttributesContext) internal.Builder { + return internal.Builder{}.WithSharedAttributes(ctx) +} + +func WithConfigTypeScheme(scheme ConfigTypeScheme) internal.Builder { + return internal.Builder{}.WithConfigTypeScheme(scheme) +} + +func New(mode ...datacontext.BuilderMode) Context { + return internal.Builder{}.New(mode...) +} diff --git a/api/config/configutils/configure.go b/api/config/configutils/configure.go new file mode 100644 index 000000000..44238cfc8 --- /dev/null +++ b/api/config/configutils/configure.go @@ -0,0 +1,20 @@ +package configutils + +import ( + _ "ocm.software/ocm/api/datacontext/config" + + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/config" + utils "ocm.software/ocm/api/ocm/ocmutils" +) + +func Configure(path string, fss ...vfs.FileSystem) error { + _, err := utils.Configure(config.DefaultContext(), path, fss...) + return err +} + +func ConfigureContext(ctxp config.ContextProvider, path string, fss ...vfs.FileSystem) error { + _, err := utils.Configure(ctxp, path, fss...) + return err +} diff --git a/api/config/context_test.go b/api/config/context_test.go new file mode 100644 index 000000000..6f2ba63ce --- /dev/null +++ b/api/config/context_test.go @@ -0,0 +1,89 @@ +package config_test + +import ( + "encoding/json" + "reflect" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/datacontext" +) + +var _ = Describe("config handling", func() { + var scheme config.ConfigTypeScheme + var cfgctx config.Context + + BeforeEach(func() { + scheme = config.NewConfigTypeScheme() + cfgctx = config.WithConfigTypeScheme(scheme).New() + Expect(cfgctx.AttributesContext().GetId()).NotTo(BeIdenticalTo(datacontext.DefaultContext.GetId())) + }) + + It("can deserialize unknown", func() { + cfg := NewConfig("a", "b") + data, err := json.Marshal(cfg) + Expect(err).To(Succeed()) + + result, err := cfgctx.GetConfigForData(data, nil) + Expect(err).To(Succeed()) + Expect(config.IsGeneric(result)).To(BeTrue()) + }) + + It("can deserialize known", func() { + RegisterAt(scheme) + + cfg := NewConfig("a", "b") + data, err := json.Marshal(cfg) + Expect(err).To(Succeed()) + + result, err := cfgctx.GetConfigForData(data, nil) + Expect(err).To(Succeed()) + Expect(config.IsGeneric(result)).To(BeFalse()) + Expect(reflect.TypeOf(result).String()).To(Equal("*config_test.Config")) + }) + + It("it applies to existing context", func() { + RegisterAt(scheme) + + d := newDummy(cfgctx) + + cfg := NewConfig("a", "b") + + err := cfgctx.ApplyConfig(cfg, "test") + + Expect(err).To(Succeed()) + + Expect(d.getApplied()).To(Equal([]*Config{cfg})) + }) + + It("it applies to new context", func() { + RegisterAt(scheme) + + cfg := NewConfig("a", "b") + + err := cfgctx.ApplyConfig(cfg, "test") + Expect(err).To(Succeed()) + + d := newDummy(cfgctx) + Expect(d.applied).To(Equal([]*Config{cfg})) + }) + + It("it applies generic to new context", func() { + cfg := NewConfig("a", "b") + data, err := json.Marshal(cfg) + Expect(err).To(Succeed()) + + gen, err := cfgctx.ApplyData(data, nil, "test") + Expect(err).To(HaveOccurred()) + Expect(errors.IsErrUnknownKind(err, config.KIND_CONFIGTYPE)).To(BeTrue()) + Expect(config.IsGeneric(gen)).To(BeTrue()) + + RegisterAt(scheme) + d := newDummy(cfgctx) + Expect(d.getApplied()).To(Equal([]*Config{cfg})) + }) +}) diff --git a/pkg/contexts/config/cpi/README.md b/api/config/cpi/README.md similarity index 100% rename from pkg/contexts/config/cpi/README.md rename to api/config/cpi/README.md diff --git a/api/config/cpi/config.go b/api/config/cpi/config.go new file mode 100644 index 000000000..332c57d77 --- /dev/null +++ b/api/config/cpi/config.go @@ -0,0 +1,54 @@ +package cpi + +import ( + "strings" + + "ocm.software/ocm/api/config/internal" + "ocm.software/ocm/api/utils/runtime" +) + +type ConfigTypeVersionScheme = runtime.TypeVersionScheme[Config, ConfigType] + +func NewConfigTypeVersionScheme(kind string) ConfigTypeVersionScheme { + return runtime.NewTypeVersionScheme[Config, ConfigType](kind, internal.NewStrictConfigTypeScheme()) +} + +func RegisterConfigType(rtype ConfigType) { + internal.DefaultConfigTypeScheme.Register(rtype) +} + +func RegisterConfigTypeVersions(s ConfigTypeVersionScheme) { + internal.DefaultConfigTypeScheme.AddKnownTypes(s) +} + +//////////////////////////////////////////////////////////////////////////////// + +type configType struct { + runtime.VersionedTypedObjectType[Config] + usage string +} + +func NewConfigType[I Config](name string, usages ...string) ConfigType { + return &configType{ + VersionedTypedObjectType: runtime.NewVersionedTypedObjectType[Config, I](name), + usage: strings.Join(usages, "\n"), + } +} + +func NewConfigTypeyConverter[I Config, V runtime.TypedObject](name string, converter runtime.Converter[I, V], usages ...string) ConfigType { + return &configType{ + VersionedTypedObjectType: runtime.NewVersionedTypedObjectTypeByConverter[Config, I](name, converter), + usage: strings.Join(usages, "\n"), + } +} + +func NewConfigTypeByFormatVersion(name string, fmt runtime.FormatVersion[Config], usages ...string) ConfigType { + return &configType{ + VersionedTypedObjectType: runtime.NewVersionedTypedObjectTypeByFormatVersion[Config](name, fmt), + usage: strings.Join(usages, "\n"), + } +} + +func (t *configType) Usage() string { + return t.usage +} diff --git a/pkg/contexts/config/cpi/content.go b/api/config/cpi/content.go similarity index 93% rename from pkg/contexts/config/cpi/content.go rename to api/config/cpi/content.go index a1e864089..abcc1a78d 100644 --- a/pkg/contexts/config/cpi/content.go +++ b/api/config/cpi/content.go @@ -8,8 +8,8 @@ import ( "github.com/mandelsoft/vfs/pkg/osfs" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" ) type RawData []byte diff --git a/api/config/cpi/interface.go b/api/config/cpi/interface.go new file mode 100644 index 000000000..5785671c2 --- /dev/null +++ b/api/config/cpi/interface.go @@ -0,0 +1,77 @@ +package cpi + +// This is the Context Provider Interface for credential providers + +import ( + "ocm.software/ocm/api/config/internal" + "ocm.software/ocm/api/utils/runtime" +) + +const KIND_CONFIGTYPE = internal.KIND_CONFIGTYPE + +const OCM_CONFIG_TYPE_SUFFIX = internal.OCM_CONFIG_TYPE_SUFFIX + +const CONTEXT_TYPE = internal.CONTEXT_TYPE + +type ( + Context = internal.Context + ContextProvider = internal.ContextProvider + Config = internal.Config + ConfigType = internal.ConfigType + ConfigTypeScheme = internal.ConfigTypeScheme + GenericConfig = internal.GenericConfig + + ConfigSet = internal.ConfigSet + ConfigurationList = internal.ConfigurationList + + ConfigApplier = internal.ConfigApplier + ConfigApplierFunction = internal.ConfigApplierFunction +) + +var DefaultContext = internal.DefaultContext + +func FromProvider(p ContextProvider) Context { + return internal.FromProvider(p) +} + +func NewGenericConfig(data []byte, unmarshaler runtime.Unmarshaler) (Config, error) { + return internal.NewGenericConfig(data, unmarshaler) +} + +func ToGenericConfig(c Config) (*GenericConfig, error) { + return internal.ToGenericConfig(c) +} + +func NewConfigTypeScheme() ConfigTypeScheme { + return internal.NewConfigTypeScheme(nil) +} + +func IsGeneric(cfg Config) bool { + return internal.IsGeneric(cfg) +} + +//////////////////////////////////////////////////////////////////////////////// + +type Updater = internal.Updater + +func NewUpdater(ctx ContextProvider, target interface{}) Updater { + return internal.NewUpdater(ctx, target) +} + +func NewUpdaterForFactory[T any](ctx ContextProvider, f func() T) Updater { + return internal.NewUpdaterForFactory(ctx, f) +} + +//////////////////////////////////////////////////////////////////////////////// + +func ErrNoContext(name string) error { + return internal.ErrNoContext(name) +} + +func IsErrNoContext(err error) bool { + return internal.IsErrNoContext(err) +} + +func IsErrConfigNotApplicable(err error) bool { + return internal.IsErrConfigNotApplicable(err) +} diff --git a/api/config/dummy_test.go b/api/config/dummy_test.go new file mode 100644 index 000000000..0ee190c5f --- /dev/null +++ b/api/config/dummy_test.go @@ -0,0 +1,73 @@ +package config_test + +import ( + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + DummyType = "Dummy" + DummyTypeV1 = DummyType + "/v1" +) + +func RegisterAt(reg cpi.ConfigTypeScheme) { + reg.Register(cpi.NewConfigType[*Config](DummyType)) + reg.Register(cpi.NewConfigType[*Config](DummyTypeV1)) +} + +// Config describes a a dummy config +type Config struct { + runtime.ObjectVersionedType `json:",inline"` + Alice string `json:"alice,omitempty"` + Bob string `json:"bob,omitempty"` +} + +// NewConfig creates a new memory Config +func NewConfig(a, b string) *Config { + return &Config{ + ObjectVersionedType: runtime.NewVersionedTypedObject(DummyType), + Alice: a, + Bob: b, + } +} + +func (a *Config) GetType() string { + return DummyType +} + +func (a *Config) ApplyTo(ctx config.Context, target interface{}) error { + d, ok := target.(*dummyContext) + if ok { + d.applied = append(d.applied, a) + return nil + } + return cpi.ErrNoContext(DummyType) +} + +//////////////////////////////////////////////////////////////////////////////// + +func newDummy(ctx config.Context) *dummyContext { + d := &dummyContext{ + config: ctx, + } + d.update() + return d +} + +type dummyContext struct { + config config.Context + lastGeneration int64 + applied []*Config +} + +func (d *dummyContext) getApplied() []*Config { + d.update() + return d.applied +} + +func (d *dummyContext) update() error { + gen, err := d.config.ApplyTo(d.lastGeneration, d) + d.lastGeneration = gen + return err +} diff --git a/api/config/extensions/config/context_test.go b/api/config/extensions/config/context_test.go new file mode 100644 index 000000000..daf4e4ea2 --- /dev/null +++ b/api/config/extensions/config/context_test.go @@ -0,0 +1,178 @@ +package config_test + +import ( + "os" + "reflect" + "runtime" + "time" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/mandelsoft/goutils/general" + "sigs.k8s.io/yaml" + + "ocm.software/ocm/api/config" + local "ocm.software/ocm/api/config/extensions/config" + "ocm.software/ocm/api/datacontext" +) + +func CheckRefs(ctx config.Context, n int) { + runtime.GC() + time.Sleep(time.Second) + Expect(datacontext.GetContextRefCount(ctx)).To(Equal(n)) // all temp refs have been finalized +} + +var _ = Describe("generic config handling", func() { + var scheme config.ConfigTypeScheme + var cfgctx config.Context + + testdataconfig, _ := os.ReadFile("testdata/config.yaml") + testdatajson, _ := yaml.YAMLToJSON(testdataconfig) + + nesteddataconfig, _ := os.ReadFile("testdata/nested.yaml") + + _ = testdatajson + + BeforeEach(func() { + scheme = config.NewConfigTypeScheme() + scheme.AddKnownTypes(config.DefaultContext().ConfigTypes()) + cfgctx = config.WithConfigTypeScheme(scheme).New() + }) + + It("can deserialize config", func() { + result, err := cfgctx.GetConfigForData(testdataconfig, nil) + Expect(err).To(Succeed()) + Expect(config.IsGeneric(result)).To(BeFalse()) + Expect(reflect.TypeOf(result).String()).To(Equal("*config.Config")) + + CheckRefs(cfgctx, 1) + }) + + It("it applies to existing context", func() { + RegisterAt(scheme) + d := newDummy(cfgctx) + + cfg, err := cfgctx.GetConfigForData(testdataconfig, nil) + Expect(err).To(Succeed()) + + err = cfgctx.ApplyConfig(cfg, "testconfig") + Expect(err).To(Succeed()) + gen, cfgs := cfgctx.GetConfig(config.AllGenerations, nil) + Expect(gen).To(Equal(int64(3))) + Expect(len(cfgs)).To(Equal(3)) + + Expect(d.getApplied()).To(Equal([]*Config{NewConfig("alice", ""), NewConfig("", "bob")})) + + CheckRefs(cfgctx, 1) + }) + + It("it applies nested to existing context", func() { + RegisterAt(scheme) + d := newDummy(cfgctx) + + cfg, err := cfgctx.GetConfigForData(nesteddataconfig, nil) + Expect(err).To(Succeed()) + + err = cfgctx.ApplyConfig(cfg, "testconfig") + Expect(err).To(Succeed()) + gen, cfgs := cfgctx.GetConfig(config.AllGenerations, nil) + Expect(gen).To(Equal(int64(4))) + Expect(len(cfgs)).To(Equal(4)) + + Expect(d.getApplied()).To(Equal([]*Config{NewConfig("alice", ""), NewConfig("", "bob")})) + + CheckRefs(cfgctx, 1) + }) + + It("it applies unknown type to existing context", func() { + cfg, err := cfgctx.GetConfigForData(testdataconfig, nil) + Expect(err).To(Succeed()) + + err = cfgctx.ApplyConfig(cfg, "testconfig") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(StringEqualWithContext("testconfig: config apply errors: {config entry 0--testconfig: config type \"Dummy\" is unknown, config entry 1--testconfig: config type \"Dummy\" is unknown}")) + gen, cfgs := cfgctx.GetConfig(config.AllGenerations, nil) + Expect(gen).To(Equal(int64(3))) + Expect(len(cfgs)).To(Equal(3)) + + RegisterAt(scheme) + d := newDummy(cfgctx) + Expect(d.getApplied()).To(Equal([]*Config{NewConfig("alice", ""), NewConfig("", "bob")})) + + CheckRefs(cfgctx, 1) + }) + + It("it applies composed config to existing context", func() { + RegisterAt(scheme) + d := newDummy(cfgctx) + + cfg := local.New() + + nested := NewConfig("alice", "") + cfg.AddConfig(nested) + nested = NewConfig("", "bob") + cfg.AddConfig(nested) + + err := cfgctx.ApplyConfig(cfg, "testconfig") + Expect(err).To(Succeed()) + gen, cfgs := cfgctx.GetConfig(config.AllGenerations, nil) + Expect(gen).To(Equal(int64(3))) + Expect(len(cfgs)).To(Equal(3)) + + Expect(d.getApplied()).To(Equal([]*Config{NewConfig("alice", ""), NewConfig("", "bob")})) + + CheckRefs(cfgctx, 1) + }) + + It("it applies composed config set to existing context", func() { + RegisterAt(scheme) + d := newDummy(cfgctx) + + cfg := local.New() + + nested := NewConfig("alice", "") + cfg.AddConfigToSet("test", nested) + nested = NewConfig("", "bob") + cfg.AddConfig(nested) + + err := cfgctx.ApplyConfig(cfg, "testconfig") + Expect(err).To(Succeed()) + gen, cfgs := cfgctx.GetConfig(config.AllGenerations, nil) + Expect(gen).To(Equal(int64(2))) + Expect(len(cfgs)).To(Equal(2)) + + Expect(d.getApplied()).To(Equal([]*Config{NewConfig("", "bob")})) + + err = cfgctx.ApplyConfigSet("test") + Expect(err).To(Succeed()) + + gen, cfgs = cfgctx.GetConfig(config.AllGenerations, nil) + Expect(gen).To(Equal(int64(3))) + Expect(len(cfgs)).To(Equal(3)) + Expect(d.getApplied()).To(Equal([]*Config{NewConfig("", "bob"), NewConfig("alice", "")})) + + CheckRefs(cfgctx, 1) + }) + + It("it applies compig to storing target", func() { + RegisterAt(scheme) + d := newDummy(cfgctx) + + cfg := NewConfig("alice", "") + + err := cfgctx.ApplyConfig(cfg, "testconfig") + Expect(err).To(Succeed()) + + Expect(d.getApplied()).To(Equal([]*Config{NewConfig("alice", "")})) + + target := dummyTarget{} + MustBeSuccessful(cfgctx.ApplyTo(0, &target)) + Expect(target.used).NotTo(BeNil()) + Expect(target.used.GetId()).To(Equal(cfgctx.GetId())) + + CheckRefs(cfgctx, general.Conditional(datacontext.MULTI_REF, 2, 1)) // config context stored in target with separate ref + target.used.GetId() + }) +}) diff --git a/api/config/extensions/config/dummy_test.go b/api/config/extensions/config/dummy_test.go new file mode 100644 index 000000000..7657404a1 --- /dev/null +++ b/api/config/extensions/config/dummy_test.go @@ -0,0 +1,88 @@ +package config_test + +import ( + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + DummyType = "Dummy" + DummyTypeV1 = DummyType + "/v1" +) + +func RegisterAt(reg cpi.ConfigTypeScheme) { + reg.Register(cpi.NewConfigType[*Config](DummyType)) + reg.Register(cpi.NewConfigType[*Config](DummyTypeV1)) +} + +// Config describes a a dummy config +type Config struct { + runtime.ObjectVersionedType `json:",inline"` + Alice string `json:"alice,omitempty"` + Bob string `json:"bob,omitempty"` +} + +// NewConfig creates a new memory ConfigSpec +func NewConfig(a, b string) *Config { + return &Config{ + ObjectVersionedType: runtime.NewVersionedTypedObject(DummyType), + Alice: a, + Bob: b, + } +} + +func (a *Config) GetType() string { + return DummyType +} + +func (a *Config) Info() string { + return "dummy config" +} + +func (a *Config) ApplyTo(ctx config.Context, target interface{}) error { + d, ok := target.(*dummyContext) + if ok { + d.applied = append(d.applied, a) + return nil + } + c, ok := target.(*dummyTarget) + if ok { + c.used = ctx + return nil + } + return cpi.ErrNoContext(DummyType) +} + +//////////////////////////////////////////////////////////////////////////////// + +type dummyTarget struct { + used config.Context +} + +//////////////////////////////////////////////////////////////////////////////// + +func newDummy(ctx config.Context) *dummyContext { + d := &dummyContext{ + config: ctx, + } + d.update() + return d +} + +type dummyContext struct { + config config.Context + lastGeneration int64 + applied []*Config +} + +func (d *dummyContext) getApplied() []*Config { + d.update() + return d.applied +} + +func (d *dummyContext) update() error { + gen, err := d.config.ApplyTo(d.lastGeneration, d) + d.lastGeneration = gen + return err +} diff --git a/pkg/contexts/config/config/suite_test.go b/api/config/extensions/config/suite_test.go similarity index 100% rename from pkg/contexts/config/config/suite_test.go rename to api/config/extensions/config/suite_test.go diff --git a/pkg/contexts/config/config/testdata/config.yaml b/api/config/extensions/config/testdata/config.yaml similarity index 100% rename from pkg/contexts/config/config/testdata/config.yaml rename to api/config/extensions/config/testdata/config.yaml diff --git a/pkg/contexts/config/config/testdata/nested.yaml b/api/config/extensions/config/testdata/nested.yaml similarity index 100% rename from pkg/contexts/config/config/testdata/nested.yaml rename to api/config/extensions/config/testdata/nested.yaml diff --git a/api/config/extensions/config/type.go b/api/config/extensions/config/type.go new file mode 100644 index 000000000..dac183377 --- /dev/null +++ b/api/config/extensions/config/type.go @@ -0,0 +1,97 @@ +package config + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ConfigType = "generic" + cpi.OCM_CONFIG_TYPE_SUFFIX + ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" +) + +func init() { + cpi.RegisterConfigType(cpi.NewConfigType[*Config](ConfigType, usage)) + cpi.RegisterConfigType(cpi.NewConfigType[*Config](ConfigTypeV1, usage)) +} + +// Config describes a memory based repository interface. +type Config struct { + runtime.ObjectVersionedType `json:",inline"` + cpi.ConfigurationList `json:",inline"` + Sets map[string]cpi.ConfigSet `json:"sets,omitempty"` +} + +// New creates a new memory ConfigSpec. +func New() *Config { + return &Config{ + ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), + ConfigurationList: cpi.ConfigurationList{[]*cpi.GenericConfig{}}, + Sets: map[string]cpi.ConfigSet{}, + } +} + +func (c *Config) AddSet(name, desc string) { + set := c.Sets[name] + set.Description = desc + c.Sets[name] = set +} + +func (c *Config) AddConfigToSet(name string, cfg cpi.Config) error { + set := c.Sets[name] + err := set.AddConfig(cfg) + if err == nil { + c.Sets[name] = set + } + return err +} + +func (c *Config) GetType() string { + return ConfigType +} + +func (c *Config) ApplyTo(ctx cpi.Context, target interface{}) error { + if cctx, ok := target.(cpi.Context); ok { + for n, s := range c.Sets { + set := s + cctx.AddConfigSet(n, &set) + } + + list := errors.ErrListf("applying generic config list") + for i, cfg := range c.Configurations { + sub := fmt.Sprintf("config entry %d", i) + list.Add(cctx.ApplyConfig(cfg, ctx.WithInfo(sub).Info())) + } + return list.Result() + } + return cpi.ErrNoContext(ConfigType) +} + +const usage = ` +The config type ` + ConfigType + ` can be used to define a list +of arbitrary configuration specifications and named configuration sets: + +
+    type: ` + ConfigType + `
+    configurations:
+      - type: <any config type>
+        ...
+      ...
+    sets:
+       standard:
+          description: my selectable standard config
+          configurations:
+            - type: ...
+              ...
+            ...
+
+ +Configurations are directly applied. Configuration sets are +just stored in the configuration context and can be applied +on-demand. On the CLI, this can be done using the main command option +--config-set <name>. +` diff --git a/api/config/extensions/config/utils.go b/api/config/extensions/config/utils.go new file mode 100644 index 000000000..fe4a76d7c --- /dev/null +++ b/api/config/extensions/config/utils.go @@ -0,0 +1,60 @@ +package config + +import ( + "ocm.software/ocm/api/config/cpi" +) + +type Aggregator struct { + cfg cpi.Config + aggr *Config + optimized bool +} + +func NewAggregator(optimized bool, cfgs ...cpi.Config) (*Aggregator, error) { + a := &Aggregator{optimized: optimized} + for _, c := range cfgs { + err := a.AddConfig(c) + if err != nil { + return nil, err + } + } + return a, nil +} + +func (a *Aggregator) Get() cpi.Config { + return a.cfg +} + +func (a *Aggregator) AddConfig(cfg cpi.Config) error { + if a.cfg == nil { + a.cfg = cfg + if aggr, ok := cfg.(*Config); ok && a.optimized { + a.aggr = aggr + } + } else { + if a.aggr == nil { + a.aggr = New() + if m, ok := a.cfg.(*Config); ok { + // transfer initial config aggregation + for _, c := range m.Configurations { + err := a.aggr.AddConfig(c) + if err != nil { + return err + } + } + } else { + // add initial config to new aggregation + err := a.aggr.AddConfig(a.cfg) + if err != nil { + return err + } + } + a.cfg = a.aggr + } + err := a.aggr.AddConfig(cfg) + if err != nil { + return err + } + } + return nil +} diff --git a/api/config/gc_test.go b/api/config/gc_test.go new file mode 100644 index 000000000..7fe3a74ef --- /dev/null +++ b/api/config/gc_test.go @@ -0,0 +1,34 @@ +package config_test + +import ( + "runtime" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + me "ocm.software/ocm/api/config" + "ocm.software/ocm/api/utils/runtimefinalizer" +) + +var _ = Describe("area test", func() { + It("can be garbage collected", func() { + ctx := me.New() + + r := runtimefinalizer.GetRuntimeFinalizationRecorder(ctx) + Expect(r).NotTo(BeNil()) + + runtime.GC() + time.Sleep(time.Second) + ctx.GetType() + Expect(r.Get()).To(BeNil()) + + ctx = nil + for i := 0; i < 100; i++ { + runtime.GC() + time.Sleep(time.Millisecond) + } + + Expect(r.Get()).To(ContainElement(ContainSubstring(me.CONTEXT_TYPE))) + }) +}) diff --git a/api/config/init.go b/api/config/init.go new file mode 100644 index 000000000..ddca39cfe --- /dev/null +++ b/api/config/init.go @@ -0,0 +1,6 @@ +package config + +import ( + _ "ocm.software/ocm/api/config/extensions/config" + _ "ocm.software/ocm/api/datacontext/config" +) diff --git a/api/config/interface.go b/api/config/interface.go new file mode 100644 index 000000000..f6f69c406 --- /dev/null +++ b/api/config/interface.go @@ -0,0 +1,76 @@ +package config + +import ( + "context" + + "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/config/internal" + "ocm.software/ocm/api/utils/runtime" +) + +const KIND_CONFIGTYPE = internal.KIND_CONFIGTYPE + +const OCM_CONFIG_TYPE_SUFFIX = internal.OCM_CONFIG_TYPE_SUFFIX + +const CONTEXT_TYPE = internal.CONTEXT_TYPE + +var AllConfigs = internal.AllConfigs + +const AllGenerations = internal.AllGenerations + +type ( + Context = internal.Context + ContextProvider = internal.ContextProvider + Config = internal.Config + ConfigType = internal.ConfigType + ConfigTypeScheme = internal.ConfigTypeScheme + GenericConfig = internal.GenericConfig + ConfigSelector = internal.ConfigSelector + ConfigSelectorFunction = internal.ConfigSelectorFunction + ConfigApplier = internal.ConfigApplier + ConfigApplierFunction = internal.ConfigApplierFunction +) + +func DefaultContext() internal.Context { + return internal.DefaultContext +} + +func ForContext(ctx context.Context) Context { + return internal.FromContext(ctx) +} + +func FromProvider(p ContextProvider) Context { + return internal.FromProvider(p) +} + +func DefinedForContext(ctx context.Context) (Context, bool) { + return internal.DefinedForContext(ctx) +} + +func NewGenericConfig(data []byte, unmarshaler runtime.Unmarshaler) (Config, error) { + return internal.NewGenericConfig(data, unmarshaler) +} + +func ToGenericConfig(c Config) (*GenericConfig, error) { + return internal.ToGenericConfig(c) +} + +func NewConfigTypeScheme() ConfigTypeScheme { + return internal.NewConfigTypeScheme(nil) +} + +func IsGeneric(cfg Config) bool { + return internal.IsGeneric(cfg) +} + +func ErrNoContext(name string) error { + return internal.ErrNoContext(name) +} + +func IsErrNoContext(err error) bool { + return cpi.IsErrNoContext(err) +} + +func IsErrConfigNotApplicable(err error) bool { + return cpi.IsErrConfigNotApplicable(err) +} diff --git a/api/config/internal/builder.go b/api/config/internal/builder.go new file mode 100644 index 000000000..1a0ef061d --- /dev/null +++ b/api/config/internal/builder.go @@ -0,0 +1,69 @@ +package internal + +import ( + "context" + + "ocm.software/ocm/api/datacontext" +) + +type Builder struct { + ctx context.Context + shared datacontext.AttributesContext + reposcheme ConfigTypeScheme +} + +func (b *Builder) getContext() context.Context { + if b.ctx == nil { + return context.Background() + } + return b.ctx +} + +func (b Builder) WithContext(ctx context.Context) Builder { + b.ctx = ctx + return b +} + +func (b Builder) WithSharedAttributes(ctx datacontext.AttributesContext) Builder { + b.shared = ctx + return b +} + +func (b Builder) WithConfigTypeScheme(scheme ConfigTypeScheme) Builder { + b.reposcheme = scheme + return b +} + +func (b Builder) Bound() (Context, context.Context) { + c := b.New() + return c, context.WithValue(b.getContext(), key, c) +} + +func (b Builder) New(m ...datacontext.BuilderMode) Context { + mode := datacontext.Mode(m...) + ctx := b.getContext() + + if b.shared == nil { + if mode == datacontext.MODE_SHARED { + b.shared = datacontext.ForContext(ctx) + } else { + b.shared = datacontext.New(nil) + } + } + if b.reposcheme == nil { + switch mode { + case datacontext.MODE_INITIAL: + b.reposcheme = NewConfigTypeScheme(nil) + case datacontext.MODE_CONFIGURED: + b.reposcheme = NewConfigTypeScheme(nil) + b.reposcheme.AddKnownTypes(DefaultConfigTypeScheme) + case datacontext.MODE_EXTENDED: + b.reposcheme = NewConfigTypeScheme(nil, DefaultConfigTypeScheme) + case datacontext.MODE_DEFAULTED: + fallthrough + case datacontext.MODE_SHARED: + b.reposcheme = DefaultConfigTypeScheme + } + } + return datacontext.SetupContext(mode, newContext(b.shared, b.reposcheme, b.shared)) +} diff --git a/api/config/internal/builder_test.go b/api/config/internal/builder_test.go new file mode 100644 index 000000000..6f583c014 --- /dev/null +++ b/api/config/internal/builder_test.go @@ -0,0 +1,37 @@ +package internal_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + local "ocm.software/ocm/api/config/internal" + "ocm.software/ocm/api/datacontext" +) + +var _ = Describe("builder test", func() { + It("creates local", func() { + ctx := local.Builder{}.New(datacontext.MODE_SHARED) + + Expect(ctx.AttributesContext()).To(BeIdenticalTo(datacontext.DefaultContext)) + Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) + Expect(ctx.ConfigTypes()).To(BeIdenticalTo(local.DefaultConfigTypeScheme)) + }) + + It("creates configured", func() { + ctx := local.Builder{}.New(datacontext.MODE_CONFIGURED) + + Expect(ctx.AttributesContext()).NotTo(BeIdenticalTo(datacontext.DefaultContext)) + Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) + Expect(ctx.ConfigTypes()).NotTo(BeIdenticalTo(local.DefaultConfigTypeScheme)) + Expect(ctx.ConfigTypes().KnownTypeNames()).To(Equal(local.DefaultConfigTypeScheme.KnownTypeNames())) + }) + + It("creates iniial", func() { + ctx := local.Builder{}.New(datacontext.MODE_INITIAL) + + Expect(ctx.AttributesContext()).NotTo(BeIdenticalTo(datacontext.DefaultContext)) + Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) + Expect(ctx.ConfigTypes()).NotTo(BeIdenticalTo(local.DefaultConfigTypeScheme)) + Expect(len(ctx.ConfigTypes().KnownTypeNames())).To(Equal(0)) + }) +}) diff --git a/api/config/internal/config.go b/api/config/internal/config.go new file mode 100644 index 000000000..639e405a8 --- /dev/null +++ b/api/config/internal/config.go @@ -0,0 +1,61 @@ +package internal + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/utils/runtime" +) + +const KIND_CONFIGSET = "config set" + +type ConfigApplier interface { + ApplyConfigTo(Context, cfg, tgt interface{}) error +} + +type Config interface { + runtime.VersionedTypedObject + + ApplyTo(Context, interface{}) error +} + +type ConfigApplierFunction func(ctx Context, cfg, tgt interface{}) error + +func (f ConfigApplierFunction) ApplyConfigTo(ctx Context, cfg, tgt interface{}) error { + return f(ctx, cfg, tgt) +} + +type ConfigSet struct { + Description string `json:"description,omitempty"` + ConfigurationList `json:",inline"` +} + +type ConfigurationList struct { + Configurations []*GenericConfig `json:"configurations,omitempty"` +} + +func (c *ConfigurationList) AddConfig(cfg Config) error { + g, err := ToGenericConfig(cfg) + if err != nil { + return fmt.Errorf("unable to convert config to generic: %w", err) + } + + c.Configurations = append(c.Configurations, g) + + return nil +} + +func (c *ConfigurationList) AddConfigData(ctx Context, data []byte) error { + cfg, err := ctx.GetConfigForData(data, nil) + if err != nil { + return errors.Wrapf(err, "invalid config specification") + } + g, err := ToGenericConfig(cfg) + if err != nil { + return fmt.Errorf("unable to convert config to generic: %w", err) + } + + c.Configurations = append(c.Configurations, g) + return nil +} diff --git a/pkg/contexts/config/internal/configtypes.go b/api/config/internal/configtypes.go similarity index 97% rename from pkg/contexts/config/internal/configtypes.go rename to api/config/internal/configtypes.go index ac0381952..a4a2cd55d 100644 --- a/pkg/contexts/config/internal/configtypes.go +++ b/api/config/internal/configtypes.go @@ -7,8 +7,8 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/modern-go/reflect2" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" ) type ConfigType interface { diff --git a/api/config/internal/context.go b/api/config/internal/context.go new file mode 100644 index 000000000..de8c12283 --- /dev/null +++ b/api/config/internal/context.go @@ -0,0 +1,356 @@ +package internal + +import ( + "context" + "reflect" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/utils" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" +) + +// OCM_CONFIG_TYPE_SUFFIX is the standard suffix used for configuration +// types provided by this library. +const OCM_CONFIG_TYPE_SUFFIX = ".config" + common.OCM_TYPE_GROUP_SUFFIX + +type ConfigSelector interface { + Select(Config) bool +} +type ConfigSelectorFunction func(Config) bool + +func (f ConfigSelectorFunction) Select(cfg Config) bool { return f(cfg) } + +var AllConfigs = AppliedConfigSelectorFunction(func(*AppliedConfig) bool { return true }) + +const AllGenerations int64 = 0 + +const CONTEXT_TYPE = "config" + datacontext.OCM_CONTEXT_SUFFIX + +type ContextProvider interface { + ConfigContext() Context +} + +type Context interface { + datacontext.Context + ContextProvider + + AttributesContext() datacontext.AttributesContext + + // Info provides the context for nested configuration evaluation + Info() string + // WithInfo provides the same context with additional nesting info + WithInfo(desc string) Context + + ConfigTypes() ConfigTypeScheme + + // SkipUnknownConfig can be used to control the behaviour + // for processing unknown configuration object types. + // It returns the previous mode valid before setting the + // new one. + SkipUnknownConfig(bool) bool + + // Validate validates the applied configuration for not using + // unknown configuration types, anymore. This can be used after setting + // SkipUnknownConfig, to check whether there are still unknown types + // which will be skipped. It does not provide information, whether + // config objects were skipped for previous object configuration + // requests. + Validate() error + + // GetConfigForData deserialize configuration objects for known + // configuration types. + GetConfigForData(data []byte, unmarshaler runtime.Unmarshaler) (Config, error) + + // ApplyData applies the config given by a byte stream to the config store + // If the config type is not known, a generic config is stored and returned. + // In this case an unknown error for kind KIND_CONFIGTYPE is returned. + ApplyData(data []byte, unmarshaler runtime.Unmarshaler, desc string) (Config, error) + // ApplyConfig applies the config to the config store + ApplyConfig(spec Config, desc string) error + + GetConfigForType(generation int64, typ string) (int64, []Config) + GetConfigForName(generation int64, name string) (int64, []Config) + GetConfig(generation int64, selector ConfigSelector) (int64, []Config) + + AddConfigSet(name string, set *ConfigSet) + ApplyConfigSet(name string) error + + // Reset all configs applied so far, subsequent calls to ApplyTo will + // ony see configs allpied after the last reset. + Reset() int64 + // Generation return the actual config generation. + // this is a strictly increasing number, regardless of the number + // of Reset calls. + Generation() int64 + // ApplyTo applies all configurations applied after the last reset with + // a generation larger than the given watermark to the specified target. + // A target may be any object. The applied configuration objects decide + // on their own whether they are applicable for the given target. + // The generation of the last applied object is returned to be used as + // new watermark. + ApplyTo(gen int64, target interface{}) (int64, error) +} + +var key = reflect.TypeOf(_context{}) + +// DefaultContext is the default context initialized by init functions. +var DefaultContext = Builder{}.New(datacontext.MODE_SHARED) + +// FromContext returns the Context to use for context.Context. +// This is either an explicit context or the default context. +// The returned context incorporates the given context. +func FromContext(ctx context.Context) Context { + c, _ := datacontext.ForContextByKey(ctx, key, DefaultContext) + return c.(Context) +} + +func FromProvider(p ContextProvider) Context { + if p == nil { + return nil + } + return p.ConfigContext() +} + +func DefinedForContext(ctx context.Context) (Context, bool) { + c, ok := datacontext.ForContextByKey(ctx, key, DefaultContext) + if c != nil { + return c.(Context), ok + } + return nil, ok +} + +//////////////////////////////////////////////////////////////////////////////// + +type _InternalContext = datacontext.InternalContext + +type coreContext struct { + _InternalContext + updater Updater + + sharedAttributes datacontext.AttributesContext + + knownConfigTypes ConfigTypeScheme + + configs *ConfigStore + skipUnknownConfig bool +} + +type _context struct { + *coreContext + description string +} + +var ( + _ Context = (*_context)(nil) + _ datacontext.ViewCreator[Context] = (*_context)(nil) +) + +// gcWrapper is used as garbage collectable +// wrapper for a context implementation +// to establish a runtime finalizer. +type gcWrapper struct { + datacontext.GCWrapper + *_context +} + +func newView(c *_context, ref ...bool) Context { + if utils.Optional(ref...) { + return datacontext.FinalizedContext[gcWrapper](c) + } + return c +} + +func (w *gcWrapper) SetContext(c *_context) { + w._context = c +} + +func newContext(shared datacontext.AttributesContext, reposcheme ConfigTypeScheme, delegates datacontext.Delegates) Context { + c := &_context{ + coreContext: &coreContext{ + sharedAttributes: shared, + knownConfigTypes: reposcheme, + configs: NewConfigStore(), + }, + } + c._InternalContext = datacontext.NewContextBase(c, CONTEXT_TYPE, key, shared.GetAttributes(), delegates) + c.updater = NewUpdaterForFactory(c, c.ConfigContext) // provide target as new view to internal context + datacontext.AssureUpdater(shared, NewUpdater(c, datacontext.PersistentContextRef(shared))) + + return newView(c, true) +} + +func (c *_context) CreateView() Context { + return newView(c, true) +} + +func (c *_context) ConfigContext() Context { + return newView(c) +} + +func (c *_context) Update() error { + return c.updater.Update() +} + +var _ datacontext.Updater = (*_context)(nil) + +func (c *_context) Info() string { + return c.description +} + +func (c *_context) WithInfo(desc string) Context { + if c.description != "" { + desc = desc + "--" + c.description + } + return newView(&_context{c.coreContext, desc}) +} + +func (c *_context) AttributesContext() datacontext.AttributesContext { + c.updater.Update() + return c.sharedAttributes +} + +func (c *_context) ConfigTypes() ConfigTypeScheme { + return c.knownConfigTypes +} + +func (c *_context) SkipUnknownConfig(b bool) bool { + old := c.skipUnknownConfig + c.skipUnknownConfig = b + return old +} + +func (c *_context) ConfigForData(data []byte, unmarshaler runtime.Unmarshaler) (Config, error) { + return c.knownConfigTypes.Decode(data, unmarshaler) +} + +func (c *_context) GetConfigForData(data []byte, unmarshaler runtime.Unmarshaler) (Config, error) { + spec, err := c.knownConfigTypes.Decode(data, unmarshaler) + if err != nil { + return nil, err + } + return spec, nil +} + +func (c *_context) ApplyConfig(spec Config, desc string) error { + var unknown error + + // use temporary view for outbound calls + spec, err := (&AppliedConfig{config: spec}).eval(newView(c)) + if err != nil { + if !errors.IsErrUnknownKind(err, KIND_CONFIGTYPE) { + return errors.Wrapf(err, "%s", desc) + } + if !c.skipUnknownConfig { + unknown = err + } + err = nil + } + + c.configs.Apply(spec, desc) + + for { + // apply directly and also indirectly described configurations + if gen, in := c.updater.State(); err != nil || in || gen >= c.configs.Generation() { + break + } + err = c.Update() + if IsErrNoContext(err) { + err = unknown + } + } + + return errors.Wrapf(err, "%s", desc) +} + +func (c *_context) ApplyData(data []byte, unmarshaler runtime.Unmarshaler, desc string) (Config, error) { + spec, err := c.knownConfigTypes.Decode(data, unmarshaler) + if err != nil { + return nil, err + } + return spec, c.ApplyConfig(spec, desc) +} + +func (c *_context) selector(gen int64, selector ConfigSelector) AppliedConfigSelector { + if gen <= 0 { + return AppliedConfigSelectorFor(selector) + } + if selector == nil { + return AppliedGenerationSelector(gen) + } + return AppliedAndSelector(AppliedGenerationSelector(gen), AppliedConfigSelectorFor(selector)) +} + +func (c *_context) Generation() int64 { + return c.configs.Generation() +} + +func (c *_context) Reset() int64 { + return c.configs.Reset() +} + +func (c *_context) ApplyTo(gen int64, target interface{}) (int64, error) { + cur := c.configs.Generation() + if cur <= gen { + return gen, nil + } + cur, cfgs := c.configs.GetConfigForSelector(c, AppliedGenerationSelector(gen)) + + list := errors.ErrListf("config apply errors") + for _, cfg := range cfgs { + err := cfg.config.ApplyTo(c.WithInfo(cfg.description), target) + if c.skipUnknownConfig && errors.IsErrUnknownKind(err, KIND_CONFIGTYPE) { + err = nil + } + err = errors.Wrapf(err, "%s", cfg.description) + if !IsErrNoContext(err) { + list.Add(err) + } + } + return cur, list.Result() +} + +func (c *_context) Validate() error { + list := errors.ErrList() + + _, cfgs := c.configs.GetConfigForSelector(c, AllAppliedConfigs) + for _, cfg := range cfgs { + _, err := cfg.eval(newView(c)) + list.Add(err) + } + return list.Result() +} + +func (c *_context) AddConfigSet(name string, set *ConfigSet) { + c.configs.AddSet(name, set) +} + +func (c *_context) ApplyConfigSet(name string) error { + set := c.configs.GetSet(name) + if set == nil { + return errors.ErrUnknown(KIND_CONFIGSET, name) + } + desc := "config set " + name + list := errors.ErrListf("applying %s", desc) + for _, cfg := range set.Configurations { + list.Add(c.ApplyConfig(cfg, desc)) + } + return list.Result() +} + +func (c *_context) GetConfig(gen int64, selector ConfigSelector) (int64, []Config) { + gen, cfgs := c.configs.GetConfigForSelector(c, c.selector(gen, selector)) + return gen, cfgs.Configs() +} + +func (c *_context) GetConfigForName(gen int64, name string) (int64, []Config) { + gen, cfgs := c.configs.GetConfigForName(c, name, c.selector(gen, nil)) + return gen, cfgs.Configs() +} + +func (c *_context) GetConfigForType(gen int64, typ string) (int64, []Config) { + gen, cfgs := c.configs.GetConfigForType(c, typ, c.selector(gen, nil)) + return gen, cfgs.Configs() +} diff --git a/pkg/contexts/config/internal/errors.go b/api/config/internal/errors.go similarity index 100% rename from pkg/contexts/config/internal/errors.go rename to api/config/internal/errors.go diff --git a/api/config/internal/logging.go b/api/config/internal/logging.go new file mode 100644 index 000000000..ca8676f5c --- /dev/null +++ b/api/config/internal/logging.go @@ -0,0 +1,9 @@ +package internal + +import ( + ocmlog "ocm.software/ocm/api/utils/logging" +) + +var Realm = ocmlog.DefineSubRealm("configuration management", "config") + +var Logger = ocmlog.DynamicLogger(Realm) diff --git a/api/config/internal/setup_test.go b/api/config/internal/setup_test.go new file mode 100644 index 000000000..731f230cb --- /dev/null +++ b/api/config/internal/setup_test.go @@ -0,0 +1,16 @@ +package internal_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/config/internal" +) + +var _ = Describe("setup", func() { + It("creates initial", func() { + Expect(len(config.DefaultContext().ConfigTypes().KnownTypeNames())).To(Equal(6)) + Expect(len(internal.DefaultConfigTypeScheme.KnownTypeNames())).To(Equal(6)) + }) +}) diff --git a/pkg/contexts/config/internal/store.go b/api/config/internal/store.go similarity index 100% rename from pkg/contexts/config/internal/store.go rename to api/config/internal/store.go diff --git a/pkg/contexts/config/internal/suite_test.go b/api/config/internal/suite_test.go similarity index 100% rename from pkg/contexts/config/internal/suite_test.go rename to api/config/internal/suite_test.go diff --git a/pkg/contexts/config/internal/updater.go b/api/config/internal/updater.go similarity index 100% rename from pkg/contexts/config/internal/updater.go rename to api/config/internal/updater.go diff --git a/api/config/logging.go b/api/config/logging.go new file mode 100644 index 000000000..ad7501506 --- /dev/null +++ b/api/config/logging.go @@ -0,0 +1,17 @@ +package config + +import ( + "ocm.software/ocm/api/config/internal" +) + +var Realm = internal.Realm + +var Logger = internal.Logger + +func Debug(c Context, msg string, keypairs ...interface{}) { + c.LoggingContext().Logger(Realm).Debug(msg, append(keypairs, "id", c.GetId())...) +} + +func Info(c Context, msg string, keypairs ...interface{}) { + c.LoggingContext().Logger(Realm).Info(msg, append(keypairs, "id", c.GetId())...) +} diff --git a/api/config/plugin/type.go b/api/config/plugin/type.go new file mode 100644 index 000000000..1030f2747 --- /dev/null +++ b/api/config/plugin/type.go @@ -0,0 +1,21 @@ +package plugin + +import ( + "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/config/internal" + "ocm.software/ocm/api/utils/runtime" +) + +var _ cpi.Config = (*Config)(nil) + +type Config struct { + runtime.UnstructuredVersionedTypedObject `json:",inline"` +} + +func (c *Config) ApplyTo(context internal.Context, i interface{}) error { + return nil +} + +func New(name string, desc string) cpi.ConfigType { + return cpi.NewConfigType[*Config](name, desc) +} diff --git a/pkg/contexts/config/suite_test.go b/api/config/suite_test.go similarity index 100% rename from pkg/contexts/config/suite_test.go rename to api/config/suite_test.go diff --git a/pkg/contexts/credentials/area_test.go b/api/credentials/area_test.go similarity index 91% rename from pkg/contexts/credentials/area_test.go rename to api/credentials/area_test.go index 7bc91a31f..ed104f822 100644 --- a/pkg/contexts/credentials/area_test.go +++ b/api/credentials/area_test.go @@ -7,7 +7,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/contexts/credentials" + "ocm.software/ocm/api/credentials" ) var DefaultContext = credentials.New() diff --git a/api/credentials/builder.go b/api/credentials/builder.go new file mode 100644 index 000000000..a9df8bd1e --- /dev/null +++ b/api/credentials/builder.go @@ -0,0 +1,29 @@ +package credentials + +import ( + "context" + + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/credentials/internal" + "ocm.software/ocm/api/datacontext" +) + +func WithContext(ctx context.Context) internal.Builder { + return internal.Builder{}.WithContext(ctx) +} + +func WithConfigs(ctx config.Context) internal.Builder { + return internal.Builder{}.WithConfig(ctx) +} + +func WithRepositoyTypeScheme(scheme RepositoryTypeScheme) internal.Builder { + return internal.Builder{}.WithRepositoyTypeScheme(scheme) +} + +func WithStandardConumerMatchers(matchers internal.IdentityMatcherRegistry) internal.Builder { + return internal.Builder{}.WithStandardConumerMatchers(matchers) +} + +func New(mode ...datacontext.BuilderMode) Context { + return internal.Builder{}.New(mode...) +} diff --git a/pkg/contexts/credentials/builtin/github/ghcr.go b/api/credentials/builtin/github/ghcr.go similarity index 75% rename from pkg/contexts/credentials/builtin/github/ghcr.go rename to api/credentials/builtin/github/ghcr.go index 9c664afc7..e82fcd909 100644 --- a/pkg/contexts/credentials/builtin/github/ghcr.go +++ b/api/credentials/builtin/github/ghcr.go @@ -3,9 +3,9 @@ package github import ( "os" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" + "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/credentials/cpi" + common "ocm.software/ocm/api/utils/misc" ) const HOST = "ghcr.io" diff --git a/api/credentials/builtin/github/github.go b/api/credentials/builtin/github/github.go new file mode 100644 index 000000000..b46f2225f --- /dev/null +++ b/api/credentials/builtin/github/github.go @@ -0,0 +1,22 @@ +package github + +import ( + "os" + + "ocm.software/ocm/api/credentials/builtin/github/identity" + "ocm.software/ocm/api/credentials/cpi" + common "ocm.software/ocm/api/utils/misc" +) + +func init() { + t := os.Getenv("GITHUB_TOKEN") + if t != "" { + us := os.Getenv("GITHUB_SERVER_URL") + id := identity.GetConsumerId(us) + + if src, err := cpi.DefaultContext.GetCredentialsForConsumer(id); err != nil || src == nil { + creds := cpi.NewCredentials(common.Properties{cpi.ATTR_TOKEN: t}) + cpi.DefaultContext.SetCredentialsForConsumer(id, creds) + } + } +} diff --git a/api/credentials/builtin/github/identity/identity.go b/api/credentials/builtin/github/identity/identity.go new file mode 100644 index 000000000..facaf68c5 --- /dev/null +++ b/api/credentials/builtin/github/identity/identity.go @@ -0,0 +1,83 @@ +package identity + +import ( + "net/url" + "path" + "strings" + + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/credentials/identity/hostpath" + "ocm.software/ocm/api/utils/listformat" +) + +const CONSUMER_TYPE = "Github" + +// identity properties +const ( + ID_HOSTNAME = hostpath.ID_HOSTNAME + ID_PORT = hostpath.ID_PORT + ID_PATHPREFIX = hostpath.ID_PATHPREFIX +) + +// credential properties +const ( + ATTR_TOKEN = cpi.ATTR_TOKEN +) + +const GITHUB = "github.com" + +var identityMatcher = hostpath.IdentityMatcher(CONSUMER_TYPE) + +func IdentityMatcher(pattern, cur, id cpi.ConsumerIdentity) bool { + return identityMatcher(pattern, cur, id) +} + +func init() { + attrs := listformat.FormatListElements("", listformat.StringElementDescriptionList{ + ATTR_TOKEN, "GitHub personal access token", + }) + cpi.RegisterStandardIdentity(CONSUMER_TYPE, identityMatcher, + `GitHub credential matcher + +This matcher is a hostpath matcher.`, + attrs) +} + +func PATCredentials(pat string) cpi.Credentials { + return cpi.DirectCredentials{ + ATTR_TOKEN: pat, + } +} + +func GetConsumerId(serverurl string, repo ...string) cpi.ConsumerIdentity { + host := GITHUB + port := "" + if serverurl != "" { + u, err := url.Parse(serverurl) + if err == nil { + host = u.Host + } + } + if idx := strings.Index(host, ":"); idx > 0 { + port = host[idx+1:] + host = host[:idx] + } + + id := cpi.ConsumerIdentity{ + cpi.ID_TYPE: CONSUMER_TYPE, + ID_HOSTNAME: host, + } + if port != "" { + id[ID_PORT] = port + } + p := path.Join(repo...) + if p != "" { + id[ID_PATHPREFIX] = p + } + return id +} + +func GetCredentials(ctx cpi.ContextProvider, serverurl string, repo ...string) (cpi.Credentials, error) { + id := GetConsumerId(serverurl, repo...) + return cpi.CredentialsForConsumer(ctx.CredentialsContext(), id, identityMatcher) +} diff --git a/api/credentials/builtin/helm/identity/identity.go b/api/credentials/builtin/helm/identity/identity.go new file mode 100644 index 000000000..b4397de1e --- /dev/null +++ b/api/credentials/builtin/helm/identity/identity.go @@ -0,0 +1,103 @@ +package identity + +import ( + "strings" + + "helm.sh/helm/v3/pkg/registry" + + ociidentity "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/credentials/identity/hostpath" + "ocm.software/ocm/api/utils/listformat" + common "ocm.software/ocm/api/utils/misc" +) + +// CONSUMER_TYPE is the Helm chart repository type. +const CONSUMER_TYPE = "HelmChartRepository" + +// ID_TYPE is the type field of a consumer identity. +const ID_TYPE = cpi.ID_TYPE + +// ID_SCHEME is the scheme of the repository. +const ID_SCHEME = hostpath.ID_SCHEME + +// ID_HOSTNAME is the hostname of a repository. +const ID_HOSTNAME = hostpath.ID_HOSTNAME + +// ID_PORT is the port number of a repository. +const ID_PORT = hostpath.ID_PORT + +// ID_PATHPREFIX is the path of a repository. +const ID_PATHPREFIX = hostpath.ID_PATHPREFIX + +func init() { + attrs := listformat.FormatListElements("", listformat.StringElementDescriptionList{ + ATTR_USERNAME, "the basic auth user name", + ATTR_PASSWORD, "the basic auth password", + ATTR_CERTIFICATE, "TLS client certificate", + ATTR_PRIVATE_KEY, "TLS private key", + ATTR_CERTIFICATE_AUTHORITY, "TLS certificate authority", + }) + + cpi.RegisterStandardIdentity(CONSUMER_TYPE, IdentityMatcher, `Helm chart repository + +It matches the `+CONSUMER_TYPE+` consumer type and additionally acts like +the `+hostpath.IDENTITY_TYPE+` type.`, + attrs) +} + +var identityMatcher = hostpath.IdentityMatcher("") + +func IdentityMatcher(pattern, cur, id cpi.ConsumerIdentity) bool { + return identityMatcher(pattern, cur, id) +} + +// used credential attributes + +const ( + ATTR_USERNAME = cpi.ATTR_USERNAME + ATTR_PASSWORD = cpi.ATTR_PASSWORD + ATTR_CERTIFICATE_AUTHORITY = cpi.ATTR_CERTIFICATE_AUTHORITY + ATTR_CERTIFICATE = cpi.ATTR_CERTIFICATE + ATTR_PRIVATE_KEY = cpi.ATTR_PRIVATE_KEY +) + +func OCIRepoURL(repourl string, chartname string) string { + repourl = strings.TrimSuffix(repourl, "/")[3+len(registry.OCIScheme):] + if chartname != "" { + repourl += "/" + chartname + } + return repourl +} + +func SimpleCredentials(user, passwd string) cpi.Credentials { + return cpi.DirectCredentials{ + ATTR_USERNAME: user, + ATTR_PASSWORD: passwd, + } +} + +func GetConsumerId(repourl string, chartname string) cpi.ConsumerIdentity { + i := strings.LastIndex(chartname, ":") + if i >= 0 { + chartname = chartname[:i] + } + if registry.IsOCI(repourl) { + repourl = strings.TrimSuffix(repourl, "/") + return ociidentity.GetConsumerId(OCIRepoURL(repourl, ""), chartname) + } else { + return hostpath.GetConsumerIdentity(CONSUMER_TYPE, repourl) + } +} + +func GetCredentials(ctx cpi.ContextProvider, repourl string, chartname string) common.Properties { + id := GetConsumerId(repourl, chartname) + if id == nil { + return nil + } + creds, err := cpi.CredentialsForConsumer(ctx.CredentialsContext(), id) + if creds == nil || err != nil { + return nil + } + return creds.Properties() +} diff --git a/api/credentials/builtin/helm/identity/identity_test.go b/api/credentials/builtin/helm/identity/identity_test.go new file mode 100644 index 000000000..ddefea679 --- /dev/null +++ b/api/credentials/builtin/helm/identity/identity_test.go @@ -0,0 +1,77 @@ +package identity_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/credentials/builtin/helm/identity" + + "ocm.software/ocm/api/credentials" + ociidentity "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/oci" + common "ocm.software/ocm/api/utils/misc" +) + +var _ = Describe("consumer id handling", func() { + Context("id deternation", func() { + It("handles helm repos", func() { + id := GetConsumerId("https://acme.org/charts", "demo:v1") + Expect(id).To(Equal(credentials.NewConsumerIdentity(CONSUMER_TYPE, + "pathprefix", "charts", + "port", "443", + "hostname", "acme.org", + "scheme", "https", + ))) + }) + + It("handles oci repos", func() { + id := GetConsumerId("oci://acme.org/charts", "demo:v1") + Expect(id).To(Equal(credentials.NewConsumerIdentity(ociidentity.CONSUMER_TYPE, + "pathprefix", "charts/demo", + "hostname", "acme.org", + ))) + }) + }) + + Context("query credentials", func() { + var ctx oci.Context + var credctx credentials.Context + + BeforeEach(func() { + ctx = oci.New(datacontext.MODE_EXTENDED) + credctx = ctx.CredentialsContext() + }) + + It("queries helm credentials", func() { + id := GetConsumerId("https://acme.org/charts", "demo:v1") + credctx.SetCredentialsForConsumer(id, + credentials.CredentialsFromList( + ATTR_USERNAME, "helm", + ATTR_PASSWORD, "helmpass", + ), + ) + + creds := GetCredentials(ctx, "https://acme.org/charts", "demo:v1") + Expect(creds).To(Equal(common.Properties{ + ATTR_USERNAME: "helm", + ATTR_PASSWORD: "helmpass", + })) + }) + + It("queries oci credentials", func() { + id := GetConsumerId("oci://acme.org/charts", "demo:v1") + credctx.SetCredentialsForConsumer(id, + credentials.CredentialsFromList( + ATTR_USERNAME, "oci", + ATTR_PASSWORD, "ocipass", + ), + ) + + creds := GetCredentials(ctx, "oci://acme.org/charts", "demo:v1") + Expect(creds).To(Equal(common.Properties{ + ATTR_USERNAME: "oci", + ATTR_PASSWORD: "ocipass", + })) + }) + }) +}) diff --git a/pkg/contexts/credentials/builtin/helm/identity/suite_test.go b/api/credentials/builtin/helm/identity/suite_test.go similarity index 100% rename from pkg/contexts/credentials/builtin/helm/identity/suite_test.go rename to api/credentials/builtin/helm/identity/suite_test.go diff --git a/api/credentials/builtin/init.go b/api/credentials/builtin/init.go new file mode 100644 index 000000000..210a22450 --- /dev/null +++ b/api/credentials/builtin/init.go @@ -0,0 +1,8 @@ +package builtin + +import ( + _ "ocm.software/ocm/api/credentials/builtin/github" + _ "ocm.software/ocm/api/credentials/builtin/helm/identity" + _ "ocm.software/ocm/api/credentials/builtin/oci/identity" + _ "ocm.software/ocm/api/credentials/builtin/wget/identity" +) diff --git a/api/credentials/builtin/maven/identity/identity.go b/api/credentials/builtin/maven/identity/identity.go new file mode 100644 index 000000000..14b89c53b --- /dev/null +++ b/api/credentials/builtin/maven/identity/identity.go @@ -0,0 +1,62 @@ +package identity + +import ( + . "net/url" + + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/credentials/identity/hostpath" + "ocm.software/ocm/api/utils/listformat" + "ocm.software/ocm/api/utils/logging" +) + +const ( + // CONSUMER_TYPE is the maven repository type. + CONSUMER_TYPE = "MavenRepository" + + // 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 maven registry. + ATTR_PASSWORD = cpi.ATTR_PASSWORD +) + +// REALM the logging realm / prefix. +var REALM = logging.DefineSubRealm("Maven repository", "maven") + +func init() { + attrs := listformat.FormatListElements("", listformat.StringElementDescriptionList{ + ATTR_USERNAME, "the basic auth user name", + ATTR_PASSWORD, "the basic auth password", + }) + + cpi.RegisterStandardIdentity(CONSUMER_TYPE, hostpath.IdentityMatcher(CONSUMER_TYPE), `MVN repository + +It matches the `+CONSUMER_TYPE+` consumer type and additionally acts like +the `+hostpath.IDENTITY_TYPE+` type.`, + attrs) +} + +var identityMatcher = hostpath.IdentityMatcher(CONSUMER_TYPE) + +func IdentityMatcher(pattern, cur, id cpi.ConsumerIdentity) bool { + return identityMatcher(pattern, cur, id) +} + +func GetConsumerId(rawURL, groupId string) (cpi.ConsumerIdentity, error) { + url, err := JoinPath(rawURL, groupId) + if err != nil { + return nil, err + } + return hostpath.GetConsumerIdentity(CONSUMER_TYPE, url), nil +} + +func GetCredentials(ctx cpi.ContextProvider, repoUrl, groupId string) (cpi.Credentials, error) { + id, err := GetConsumerId(repoUrl, groupId) + if err != nil { + return nil, err + } + if id == nil { + logging.DynamicLogger(REALM).Debug("No consumer identity found.", "url", repoUrl, "groupId", groupId) + return nil, nil + } + return cpi.CredentialsForConsumer(ctx.CredentialsContext(), id) +} diff --git a/api/credentials/builtin/npm/identity/identity.go b/api/credentials/builtin/npm/identity/identity.go new file mode 100644 index 000000000..d65390686 --- /dev/null +++ b/api/credentials/builtin/npm/identity/identity.go @@ -0,0 +1,68 @@ +package identity + +import ( + "net/url" + + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/credentials/identity/hostpath" + "ocm.software/ocm/api/utils/listformat" + "ocm.software/ocm/api/utils/logging" +) + +const ( + // CONSUMER_TYPE is the npm repository type. + CONSUMER_TYPE = "NpmRegistry" + + // ATTR_USERNAME is the username attribute. Required for login at any npm registry. + ATTR_USERNAME = cpi.ATTR_USERNAME + // ATTR_PASSWORD is the password attribute. Required for login at any npm registry. + ATTR_PASSWORD = cpi.ATTR_PASSWORD + // ATTR_EMAIL is the email attribute. Required for login at any npm registry. + ATTR_EMAIL = cpi.ATTR_EMAIL + // ATTR_TOKEN is the token attribute. May exist after login at any npm registry. + ATTR_TOKEN = cpi.ATTR_TOKEN +) + +// Logging Realm. +var REALM = logging.DefineSubRealm("NPM registry", "npm") + +func init() { + attrs := listformat.FormatListElements("", listformat.StringElementDescriptionList{ + ATTR_USERNAME, "the basic auth user name", + ATTR_PASSWORD, "the basic auth password", + ATTR_EMAIL, "NPM registry, require an email address", + ATTR_TOKEN, "the token attribute. May exist after login at any npm registry. Check your .npmrc file!", + }) + + cpi.RegisterStandardIdentity(CONSUMER_TYPE, hostpath.IdentityMatcher(CONSUMER_TYPE), `NPM registry + +It matches the `+CONSUMER_TYPE+` consumer type and additionally acts like +the `+hostpath.IDENTITY_TYPE+` type.`, + attrs) +} + +var identityMatcher = hostpath.IdentityMatcher(CONSUMER_TYPE) + +func IdentityMatcher(pattern, cur, id cpi.ConsumerIdentity) bool { + return identityMatcher(pattern, cur, id) +} + +func GetConsumerId(rawURL, groupId string) (cpi.ConsumerIdentity, error) { + _url, err := url.JoinPath(rawURL, groupId) + if err != nil { + return nil, err + } + return hostpath.GetConsumerIdentity(CONSUMER_TYPE, _url), nil +} + +func GetCredentials(ctx cpi.ContextProvider, repoUrl string, pkgName string) (cpi.Credentials, error) { + id, err := GetConsumerId(repoUrl, pkgName) + if err != nil { + return nil, err + } + if id == nil { + logging.DynamicLogger(REALM).Debug("No consumer identity found.", "url", repoUrl, "groupId", pkgName) + return nil, nil + } + return cpi.CredentialsForConsumer(ctx.CredentialsContext(), id) +} diff --git a/pkg/contexts/credentials/builtin/oci/identity/creds.go b/api/credentials/builtin/oci/identity/creds.go similarity index 86% rename from pkg/contexts/credentials/builtin/oci/identity/creds.go rename to api/credentials/builtin/oci/identity/creds.go index 972f8cf9c..d2988ac20 100644 --- a/pkg/contexts/credentials/builtin/oci/identity/creds.go +++ b/api/credentials/builtin/oci/identity/creds.go @@ -3,8 +3,8 @@ package identity import ( "path" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/utils" ) func SimpleCredentials(user, passwd string) cpi.Credentials { diff --git a/api/credentials/builtin/oci/identity/id_test.go b/api/credentials/builtin/oci/identity/id_test.go new file mode 100644 index 000000000..82fbb6239 --- /dev/null +++ b/api/credentials/builtin/oci/identity/id_test.go @@ -0,0 +1,145 @@ +package identity_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/builtin/oci/identity" +) + +var _ = Describe("ctf management", func() { + Context("with path", func() { + pat := credentials.ConsumerIdentity{ + identity.ID_HOSTNAME: "host", + identity.ID_PATHPREFIX: "a/b", + identity.ID_PORT: "4711", + } + + It("complete", func() { + id := credentials.ConsumerIdentity{ + identity.ID_HOSTNAME: "host", + identity.ID_PATHPREFIX: "a/b", + identity.ID_PORT: "4711", + } + Expect(identity.IdentityMatcher(pat, nil, id)).To(BeTrue()) + Expect(identity.IdentityMatcher(pat, id, id)).To(BeFalse()) + }) + + It("path prefix", func() { + id := credentials.ConsumerIdentity{ + identity.ID_HOSTNAME: "host", + identity.ID_PATHPREFIX: "a", + identity.ID_PORT: "4711", + } + Expect(identity.IdentityMatcher(pat, nil, id)).To(BeTrue()) + Expect(identity.IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + It("different prefix", func() { + id := credentials.ConsumerIdentity{ + identity.ID_HOSTNAME: "host", + identity.ID_PATHPREFIX: "b", + identity.ID_PORT: "4711", + } + Expect(identity.IdentityMatcher(pat, nil, id)).To(BeFalse()) + Expect(identity.IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + It("longer prefix", func() { + id := credentials.ConsumerIdentity{ + identity.ID_HOSTNAME: "host", + identity.ID_PATHPREFIX: "a/b/c", + identity.ID_PORT: "4711", + } + Expect(identity.IdentityMatcher(pat, nil, id)).To(BeFalse()) + Expect(identity.IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + It("missing path", func() { + id := credentials.ConsumerIdentity{ + identity.ID_HOSTNAME: "host", + identity.ID_PORT: "4711", + } + Expect(identity.IdentityMatcher(pat, nil, id)).To(BeTrue()) + Expect(identity.IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + It("missing port", func() { + id := credentials.ConsumerIdentity{ + identity.ID_HOSTNAME: "host", + identity.ID_PATHPREFIX: "a/b", + } + Expect(identity.IdentityMatcher(pat, nil, id)).To(BeTrue()) + Expect(identity.IdentityMatcher(pat, pat, id)).To(BeFalse()) + + Expect(identity.IdentityMatcher(id, nil, pat)).To(BeTrue()) // accept additional port as fallback + Expect(identity.IdentityMatcher(id, id, pat)).To(BeFalse()) // but not to replace more general match + }) + It("different port", func() { + id := credentials.ConsumerIdentity{ + identity.ID_HOSTNAME: "host", + identity.ID_PATHPREFIX: "a/b", + identity.ID_PORT: "0815", + } + Expect(identity.IdentityMatcher(pat, nil, id)).To(BeFalse()) + Expect(identity.IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + + It("different host", func() { + id := credentials.ConsumerIdentity{ + identity.ID_HOSTNAME: "other", + identity.ID_PATHPREFIX: "a/b", + identity.ID_PORT: "4711", + } + Expect(identity.IdentityMatcher(pat, nil, id)).To(BeFalse()) + Expect(identity.IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + It("no host", func() { + id := credentials.ConsumerIdentity{ + identity.ID_PATHPREFIX: "a/b", + identity.ID_PORT: "4711", + } + Expect(identity.IdentityMatcher(id, nil, pat)).To(BeTrue()) + Expect(identity.IdentityMatcher(pat, id, id)).To(BeFalse()) + Expect(identity.IdentityMatcher(pat, id, pat)).To(BeTrue()) + }) + }) + + Context("without path", func() { + pat := credentials.ConsumerIdentity{ + identity.ID_HOSTNAME: "host", + identity.ID_PORT: "4711", + } + + It("complete", func() { + id := credentials.ConsumerIdentity{ + identity.ID_HOSTNAME: "host", + identity.ID_PORT: "4711", + } + Expect(identity.IdentityMatcher(pat, nil, id)).To(BeTrue()) + Expect(identity.IdentityMatcher(pat, id, id)).To(BeFalse()) + }) + + It("different prefix", func() { + id := credentials.ConsumerIdentity{ + identity.ID_HOSTNAME: "host", + identity.ID_PORT: "4711", + identity.ID_PATHPREFIX: "b", + } + Expect(identity.IdentityMatcher(pat, nil, id)).To(BeFalse()) + Expect(identity.IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + It("missing port", func() { + id := credentials.ConsumerIdentity{ + identity.ID_HOSTNAME: "host", + } + Expect(identity.IdentityMatcher(pat, nil, id)).To(BeTrue()) + Expect(identity.IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + It("different port", func() { + id := credentials.ConsumerIdentity{ + identity.ID_HOSTNAME: "host", + identity.ID_PORT: "0815", + } + Expect(identity.IdentityMatcher(pat, nil, id)).To(BeFalse()) + Expect(identity.IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + }) +}) diff --git a/api/credentials/builtin/oci/identity/identity.go b/api/credentials/builtin/oci/identity/identity.go new file mode 100644 index 000000000..004d477a9 --- /dev/null +++ b/api/credentials/builtin/oci/identity/identity.go @@ -0,0 +1,48 @@ +package identity + +import ( + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/credentials/identity/hostpath" + "ocm.software/ocm/api/utils/listformat" +) + +// CONSUMER_TYPE is the OCT registry type. +const CONSUMER_TYPE = "OCIRegistry" + +// used identity properties. +const ( + ID_TYPE = hostpath.ID_TYPE + ID_HOSTNAME = hostpath.ID_HOSTNAME + ID_PORT = hostpath.ID_PORT + ID_PATHPREFIX = hostpath.ID_PATHPREFIX + ID_SCHEME = hostpath.ID_SCHEME +) + +// used credential properties. +const ( + ATTR_USERNAME = cpi.ATTR_USERNAME + ATTR_PASSWORD = cpi.ATTR_PASSWORD + ATTR_IDENTITY_TOKEN = cpi.ATTR_IDENTITY_TOKEN + ATTR_CERTIFICATE_AUTHORITY = cpi.ATTR_CERTIFICATE_AUTHORITY +) + +func init() { + attrs := listformat.FormatListElements("", listformat.StringElementDescriptionList{ + ATTR_USERNAME, "the basic auth user name", + ATTR_PASSWORD, "the basic auth password", + ATTR_IDENTITY_TOKEN, "the bearer token used for non-basic auth authorization", + ATTR_CERTIFICATE_AUTHORITY, "the certificate authority certificate used to verify certificates", + }) + + cpi.RegisterStandardIdentity(CONSUMER_TYPE, IdentityMatcher, `OCI registry credential matcher + +It matches the `+CONSUMER_TYPE+` consumer type and additionally acts like +the `+hostpath.IDENTITY_TYPE+` type.`, + attrs) +} + +var identityMatcher = hostpath.IdentityMatcher(CONSUMER_TYPE) + +func IdentityMatcher(pattern, cur, id cpi.ConsumerIdentity) bool { + return identityMatcher(pattern, cur, id) +} diff --git a/pkg/contexts/credentials/builtin/oci/identity/suite_test.go b/api/credentials/builtin/oci/identity/suite_test.go similarity index 100% rename from pkg/contexts/credentials/builtin/oci/identity/suite_test.go rename to api/credentials/builtin/oci/identity/suite_test.go diff --git a/api/credentials/builtin/wget/identity/identity.go b/api/credentials/builtin/wget/identity/identity.go new file mode 100644 index 000000000..508c13e0c --- /dev/null +++ b/api/credentials/builtin/wget/identity/identity.go @@ -0,0 +1,56 @@ +package identity + +import ( + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/credentials/identity/hostpath" + "ocm.software/ocm/api/utils/listformat" +) + +// CONSUMER_TYPE is the wget access method type. +const CONSUMER_TYPE = "wget" + +// used identity properties. +const ( + ID_TYPE = hostpath.ID_TYPE + ID_HOSTNAME = hostpath.ID_HOSTNAME + ID_PORT = hostpath.ID_PORT + ID_PATHPREFIX = hostpath.ID_PATHPREFIX + ID_SCHEME = hostpath.ID_SCHEME +) + +// used credential properties. +const ( + ATTR_USERNAME = cpi.ATTR_USERNAME + ATTR_PASSWORD = cpi.ATTR_PASSWORD + ATTR_IDENTITY_TOKEN = cpi.ATTR_IDENTITY_TOKEN + ATTR_CERTIFICATE_AUTHORITY = cpi.ATTR_CERTIFICATE_AUTHORITY + ATTR_CERTIFICATE = cpi.ATTR_CERTIFICATE + ATTR_PRIVATE_KEY = cpi.ATTR_PRIVATE_KEY +) + +func init() { + attrs := listformat.FormatListElements("", listformat.StringElementDescriptionList{ + ATTR_USERNAME, "the basic auth user name", + ATTR_PASSWORD, "the basic auth password", + ATTR_IDENTITY_TOKEN, "the bearer token used for non-basic auth authorization", + ATTR_CERTIFICATE_AUTHORITY, "the certificate authority certificate used to verify certificates presented by the server", + ATTR_CERTIFICATE, "the certificate used to present to the server", + ATTR_PRIVATE_KEY, "the private key corresponding to the certificate", + }) + + cpi.RegisterStandardIdentity(CONSUMER_TYPE, IdentityMatcher, `wget credential matcher + +It matches the `+CONSUMER_TYPE+` consumer type and additionally acts like +the `+hostpath.IDENTITY_TYPE+` type.`, + attrs) +} + +var identityMatcher = hostpath.IdentityMatcher(CONSUMER_TYPE) + +func IdentityMatcher(pattern, cur, id cpi.ConsumerIdentity) bool { + return identityMatcher(pattern, cur, id) +} + +func GetConsumerId(url string) cpi.ConsumerIdentity { + return hostpath.GetConsumerIdentity(CONSUMER_TYPE, url) +} diff --git a/api/credentials/builtin/wget/identity/identity_test.go b/api/credentials/builtin/wget/identity/identity_test.go new file mode 100644 index 000000000..703b80526 --- /dev/null +++ b/api/credentials/builtin/wget/identity/identity_test.go @@ -0,0 +1,145 @@ +package identity_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/credentials/builtin/wget/identity" + + "ocm.software/ocm/api/credentials" +) + +var _ = Describe("wget credential management", func() { + Context("with path", func() { + pat := credentials.ConsumerIdentity{ + ID_HOSTNAME: "host", + ID_PATHPREFIX: "a/b", + ID_PORT: "4711", + } + + It("complete", func() { + id := credentials.ConsumerIdentity{ + ID_HOSTNAME: "host", + ID_PATHPREFIX: "a/b", + ID_PORT: "4711", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) + Expect(IdentityMatcher(pat, id, id)).To(BeFalse()) + }) + + It("path prefix", func() { + id := credentials.ConsumerIdentity{ + ID_HOSTNAME: "host", + ID_PATHPREFIX: "a", + ID_PORT: "4711", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) + Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + It("different prefix", func() { + id := credentials.ConsumerIdentity{ + ID_HOSTNAME: "host", + ID_PATHPREFIX: "b", + ID_PORT: "4711", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) + Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + It("longer prefix", func() { + id := credentials.ConsumerIdentity{ + ID_HOSTNAME: "host", + ID_PATHPREFIX: "a/b/c", + ID_PORT: "4711", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) + Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + It("missing path", func() { + id := credentials.ConsumerIdentity{ + ID_HOSTNAME: "host", + ID_PORT: "4711", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) + Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + It("missing port", func() { + id := credentials.ConsumerIdentity{ + ID_HOSTNAME: "host", + ID_PATHPREFIX: "a/b", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) + Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) + + Expect(IdentityMatcher(id, nil, pat)).To(BeTrue()) // accept additional port as fallback + Expect(IdentityMatcher(id, id, pat)).To(BeFalse()) // but not to replace more general match + }) + It("different port", func() { + id := credentials.ConsumerIdentity{ + ID_HOSTNAME: "host", + ID_PATHPREFIX: "a/b", + ID_PORT: "0815", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) + Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + + It("different host", func() { + id := credentials.ConsumerIdentity{ + ID_HOSTNAME: "other", + ID_PATHPREFIX: "a/b", + ID_PORT: "4711", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) + Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + It("no host", func() { + id := credentials.ConsumerIdentity{ + ID_PATHPREFIX: "a/b", + ID_PORT: "4711", + } + Expect(IdentityMatcher(id, nil, pat)).To(BeTrue()) + Expect(IdentityMatcher(pat, id, id)).To(BeFalse()) + Expect(IdentityMatcher(pat, id, pat)).To(BeTrue()) + }) + }) + + Context("without path", func() { + pat := credentials.ConsumerIdentity{ + ID_HOSTNAME: "host", + ID_PORT: "4711", + } + + It("complete", func() { + id := credentials.ConsumerIdentity{ + ID_HOSTNAME: "host", + ID_PORT: "4711", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) + Expect(IdentityMatcher(pat, id, id)).To(BeFalse()) + }) + + It("different prefix", func() { + id := credentials.ConsumerIdentity{ + ID_HOSTNAME: "host", + ID_PORT: "4711", + ID_PATHPREFIX: "b", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) + Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + It("missing port", func() { + id := credentials.ConsumerIdentity{ + ID_HOSTNAME: "host", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) + Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + It("different port", func() { + id := credentials.ConsumerIdentity{ + ID_HOSTNAME: "host", + ID_PORT: "0815", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) + Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + }) +}) diff --git a/pkg/contexts/credentials/builtin/wget/identity/suite_test.go b/api/credentials/builtin/wget/identity/suite_test.go similarity index 100% rename from pkg/contexts/credentials/builtin/wget/identity/suite_test.go rename to api/credentials/builtin/wget/identity/suite_test.go diff --git a/api/credentials/config/config_test.go b/api/credentials/config/config_test.go new file mode 100644 index 000000000..2e7752452 --- /dev/null +++ b/api/credentials/config/config_test.go @@ -0,0 +1,209 @@ +package config_test + +import ( + "encoding/json" + "reflect" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/mandelsoft/goutils/testutils" + + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/credentials" + localconfig "ocm.software/ocm/api/credentials/config" + "ocm.software/ocm/api/credentials/extensions/repositories/aliases" + "ocm.software/ocm/api/credentials/extensions/repositories/directcreds" + "ocm.software/ocm/api/credentials/extensions/repositories/memory" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" +) + +var DefaultContext = credentials.New() + +var _ = Describe("generic credentials", func() { + props := common.Properties{ + "user": "USER", + "password": "PASSWORD", + } + + repospec := memory.NewRepositorySpec("test") + credspec := credentials.NewCredentialsSpec("cred", repospec) + direct := directcreds.NewRepositorySpec(props) + + cfgconsumerdata := "{\"type\":\"credentials.config.ocm.software\",\"consumers\":[{\"identity\":{\"type\":\"oci\",\"url\":\"https://acme.com\"},\"credentials\":[{\"credentialsName\":\"cred\",\"repoName\":\"test\",\"type\":\"Memory\"}]}]}" + cfgrepodata := "{\"type\":\"credentials.config.ocm.software\",\"repositories\":[{\"repository\":{\"repoName\":\"test\",\"type\":\"Memory\"},\"credentials\":[{\"properties\":{\"password\":\"PASSWORD\",\"user\":\"USER\"},\"type\":\"Credentials\"}]}]}" + cfgaliasdata := "{\"type\":\"credentials.config.ocm.software\",\"aliases\":{\"alias\":{\"repository\":{\"repoName\":\"test\",\"type\":\"Memory\"},\"credentials\":[{\"properties\":{\"password\":\"PASSWORD\",\"user\":\"USER\"},\"type\":\"Credentials\"}]}}}" + _ = props + + Context("serialize", func() { + It("serializes repository spec not in map", func() { + mapdata := "{\"repositories\":{\"repository\":{\"repoName\":\"test\",\"type\":\"Memory\"}}}" + type S struct { + Repositories localconfig.RepositorySpec `json:"repositories"` + } + + rspec, err := credentials.ToGenericRepositorySpec(repospec) + Expect(err).To(Succeed()) + s := &S{ + Repositories: localconfig.RepositorySpec{Repository: *rspec}, + } + data, err := json.Marshal(s) + + Expect(err).To(Succeed()) + Expect(data).To(Equal([]byte(mapdata))) + }) + + It("serializes repository spec map", func() { + mapdata := "{\"repositories\":{\"repo\":{\"repository\":{\"repoName\":\"test\",\"type\":\"Memory\"}}}}" + type S struct { + Repositories map[string]localconfig.RepositorySpec `json:"repositories"` + } + + rspec, err := credentials.ToGenericRepositorySpec(repospec) + Expect(err).To(Succeed()) + s := &S{ + Repositories: map[string]localconfig.RepositorySpec{ + "repo": {Repository: *rspec}, + }, + } + data, err := json.Marshal(s) + Expect(err).To(Succeed()) + Expect(data).To(Equal([]byte(mapdata))) + }) + }) + + Context("composition", func() { + It("composes a config for consumers", func() { + consumerid := credentials.ConsumerIdentity{ + "type": "oci", + "url": "https://acme.com", + } + + cfg := localconfig.New() + + cfg.AddConsumer(consumerid, credspec) + + data, err := json.Marshal(cfg) + Expect(err).To(Succeed()) + Expect(data).To(Equal([]byte(cfgconsumerdata))) + + cfg2 := &localconfig.Config{} + err = json.Unmarshal(data, cfg2) + Expect(err).To(Succeed()) + Expect(cfg2).To(Equal(cfg)) + }) + + It("composes a config for repositories", func() { + cfg := localconfig.New() + + cfg.AddRepository(repospec, direct) + + data, err := json.Marshal(cfg) + Expect(err).To(Succeed()) + Expect(data).To(Equal([]byte(cfgrepodata))) + + cfg2 := &localconfig.Config{} + err = json.Unmarshal(data, cfg2) + Expect(err).To(Succeed()) + Expect(cfg2).To(Equal(cfg)) + }) + + It("composes a config for aliases", func() { + cfg := localconfig.New() + + cfg.AddAlias("alias", repospec, direct) + + data, err := json.Marshal(cfg) + Expect(err).To(Succeed()) + Expect(data).To(Equal([]byte(cfgaliasdata))) + + cfg2 := &localconfig.Config{} + err = json.Unmarshal(data, cfg2) + Expect(err).To(Succeed()) + Expect(cfg2).To(Equal(cfg)) + }) + }) + + Context("apply", func() { + var ctx credentials.Context + + _ = ctx + + BeforeEach(func() { + ctx = credentials.WithConfigs(config.New()).New() + }) + + It("applies a config for aliases", func() { + cfg := localconfig.New() + cfg.AddAlias("alias", repospec, direct) + + ctx.ConfigContext().ApplyConfig(cfg, "testconfig") + + spec := aliases.NewRepositorySpec("alias") + + repo, err := ctx.RepositoryForSpec(spec) + Expect(err).To(Succeed()) + Expect(reflect.TypeOf(repo).String()).To(Equal("*memory.Repository")) + }) + + It("applies a config for consumers", func() { + cfg := localconfig.New() + + consumer := credentials.ConsumerIdentity{ + credentials.ID_TYPE: "mytype", + "host": "localhost", + } + props := common.Properties{"token": "mytoken"} + creds := directcreds.NewCredentials(props) + Expect(cfg.AddConsumer(consumer, creds)).To(Succeed()) + + data, err := runtime.DefaultYAMLEncoding.Marshal(cfg) + Expect(err).To(Succeed()) + Expect(string(data)).To(testutils.StringEqualTrimmedWithContext(` +consumers: +- credentials: + - credentialsName: Credentials + properties: + token: mytoken + type: Credentials + identity: + host: localhost + type: mytype +type: credentials.config.ocm.software +`)) + + ctx.ConfigContext().ApplyConfig(cfg, "testconfig") + + result, err := credentials.CredentialsForConsumer(ctx, consumer, credentials.CompleteMatch) + Expect(err).To(Succeed()) + + Expect(result.Properties()).To(Equal(props)) + }) + + It("applies a config for consumers", func() { + props := common.Properties{"token": "mytoken"} + consumer := credentials.ConsumerIdentity{ + credentials.ID_TYPE: "mytype", + "host": "localhost", + } + data := ` +type: credentials.config.ocm.software +consumers: +- credentials: + - type: Credentials + properties: + token: mytoken + identity: + host: localhost + type: mytype +` + ctx.ConfigContext().ApplyData([]byte(data), nil, "testconfig") + + result, err := credentials.CredentialsForConsumer(ctx, consumer, credentials.CompleteMatch) + Expect(err).To(Succeed()) + + Expect(result.Properties()).To(Equal(props)) + }) + }) +}) diff --git a/pkg/contexts/credentials/config/suite_test.go b/api/credentials/config/suite_test.go similarity index 100% rename from pkg/contexts/credentials/config/suite_test.go rename to api/credentials/config/suite_test.go diff --git a/api/credentials/config/type.go b/api/credentials/config/type.go new file mode 100644 index 000000000..05f56f4ff --- /dev/null +++ b/api/credentials/config/type.go @@ -0,0 +1,186 @@ +package config + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + + cfgcpi "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ConfigType = "credentials" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX + ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" +) + +func init() { + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1, usage)) +} + +// Config describes a configuration for the config context. +type Config struct { + runtime.ObjectVersionedType `json:",inline"` + // Consumers describe predefine logical cosumer specs mapped to credentials + // These will (potentially) be evaluated if access objects requiring credentials + // are provided by other modules (e.g. oci repo access) without + // specifying crednentials. Then this module can request credentials here by passing + // an appropriate consumer spec. + Consumers []ConsumerSpec `json:"consumers,omitempty"` + // Repositories describe preloaded credential repositories with potential credential chain + Repositories []RepositorySpec `json:"repositories,omitempty"` + // Aliases describe logical credential repositories mapped to implementing repositories + Aliases map[string]RepositorySpec `json:"aliases,omitempty"` +} + +type ConsumerSpec struct { + Identity cpi.ConsumerIdentity `json:"identity"` + Credentials []cpi.GenericCredentialsSpec `json:"credentials"` +} + +type RepositorySpec struct { + Repository cpi.GenericRepositorySpec `json:"repository"` + Credentials []cpi.GenericCredentialsSpec `json:"credentials,omitempty"` +} + +// NewConfigSpec creates a new memory ConfigSpec. +func New() *Config { + return &Config{ + ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), + } +} + +func (a *Config) GetType() string { + return ConfigType +} + +func (a *Config) MapCredentialsChain(creds ...cpi.CredentialsSpec) ([]cpi.GenericCredentialsSpec, error) { + var cgens []cpi.GenericCredentialsSpec + for _, c := range creds { + cgen, err := cpi.ToGenericCredentialsSpec(c) + if err != nil { + return nil, err + } + cgens = append(cgens, *cgen) + } + return cgens, nil +} + +func (a *Config) AddConsumer(id cpi.ConsumerIdentity, creds ...cpi.CredentialsSpec) error { + cgens, err := a.MapCredentialsChain(creds...) + if err != nil { + return fmt.Errorf("failed to map credentials chain: %w", err) + } + + spec := &ConsumerSpec{ + Identity: id, + Credentials: cgens, + } + a.Consumers = append(a.Consumers, *spec) + return nil +} + +func (a *Config) MapRepository(repo cpi.RepositorySpec, creds ...cpi.CredentialsSpec) (*RepositorySpec, error) { + rgen, err := cpi.ToGenericRepositorySpec(repo) + if err != nil { + return nil, err + } + + cgens, err := a.MapCredentialsChain(creds...) + if err != nil { + return nil, err + } + + return &RepositorySpec{ + Repository: *rgen, + Credentials: cgens, + }, nil +} + +func (a *Config) AddRepository(repo cpi.RepositorySpec, creds ...cpi.CredentialsSpec) error { + spec, err := a.MapRepository(repo, creds...) + if err != nil { + return fmt.Errorf("failed to map repository: %w", err) + } + + a.Repositories = append(a.Repositories, *spec) + + return nil +} + +func (a *Config) AddAlias(name string, repo cpi.RepositorySpec, creds ...cpi.CredentialsSpec) error { + spec, err := a.MapRepository(repo, creds...) + if err != nil { + return fmt.Errorf("failed to map repository: %w", err) + } + + if a.Aliases == nil { + a.Aliases = map[string]RepositorySpec{} + } + a.Aliases[name] = *spec + return nil +} + +// --- begin apply --- + +func (a *Config) ApplyTo(ctx cfgcpi.Context, target interface{}) error { + list := errors.ErrListf("applying config") + t, ok := target.(cpi.Context) + if !ok { + return cfgcpi.ErrNoContext(ConfigType) + } + for _, e := range a.Consumers { + t.SetCredentialsForConsumer(e.Identity, CredentialsChain(e.Credentials...)) + } + // --- end apply --- + sub := errors.ErrListf("applying aliases") + for n, e := range a.Aliases { + sub.Add(t.SetAlias(n, &e.Repository, CredentialsChain(e.Credentials...))) + } + list.Add(sub.Result()) + sub = errors.ErrListf("applying repositories") + for i, e := range a.Repositories { + _, err := t.RepositoryForSpec(&e.Repository, CredentialsChain(e.Credentials...)) + sub.Add(errors.Wrapf(err, "repository entry %d", i)) + } + list.Add(sub.Result()) + + return list.Result() +} + +func CredentialsChain(creds ...cpi.GenericCredentialsSpec) cpi.CredentialsChain { + r := make([]cpi.CredentialsSource, len(creds)) + for i := range creds { + r[i] = &creds[i] + } + return r +} + +const usage = ` +The config type ` + ConfigType + ` can be used to define a list +of arbitrary configuration specifications: + +
+    type: ` + ConfigType + `
+    consumers:
+      - identity:
+          <name>: <value>
+          ...
+        credentials:
+          - <credential specification>
+          ... credential chain
+    repositories:
+       - repository: <repository specification>
+         credentials:
+          - <credential specification>
+          ... credential chain
+    aliases:
+       <name>: 
+         repository: <repository specification>
+         credentials:
+          - <credential specification>
+          ... credential chain
+
+` diff --git a/api/credentials/const.go b/api/credentials/const.go new file mode 100644 index 000000000..88b5e8774 --- /dev/null +++ b/api/credentials/const.go @@ -0,0 +1,20 @@ +package credentials + +import ( + "ocm.software/ocm/api/credentials/internal" +) + +const ( + ID_TYPE = internal.ID_TYPE + + ATTR_TYPE = internal.ATTR_TYPE + ATTR_USERNAME = internal.ATTR_USERNAME + ATTR_PASSWORD = internal.ATTR_PASSWORD + ATTR_CERTIFICATE_AUTHORITY = internal.ATTR_CERTIFICATE_AUTHORITY + ATTR_CERTIFICATE = internal.ATTR_CERTIFICATE // PEM encoded + ATTR_PRIVATE_KEY = internal.ATTR_PRIVATE_KEY // PEM encoded + ATTR_SERVER_ADDRESS = internal.ATTR_SERVER_ADDRESS + ATTR_IDENTITY_TOKEN = internal.ATTR_IDENTITY_TOKEN + ATTR_REGISTRY_TOKEN = internal.ATTR_REGISTRY_TOKEN + ATTR_TOKEN = internal.ATTR_TOKEN +) diff --git a/pkg/contexts/credentials/cpi/README.md b/api/credentials/cpi/README.md similarity index 100% rename from pkg/contexts/credentials/cpi/README.md rename to api/credentials/cpi/README.md diff --git a/pkg/contexts/credentials/cpi/builtin.go b/api/credentials/cpi/builtin.go similarity index 88% rename from pkg/contexts/credentials/cpi/builtin.go rename to api/credentials/cpi/builtin.go index 3384e3dd2..93cf99123 100644 --- a/pkg/contexts/credentials/cpi/builtin.go +++ b/api/credentials/cpi/builtin.go @@ -1,7 +1,7 @@ package cpi import ( - "github.com/open-component-model/ocm/pkg/contexts/credentials/internal" + "ocm.software/ocm/api/credentials/internal" ) const AliasRepositoryType = internal.AliasRepositoryType diff --git a/api/credentials/cpi/const.go b/api/credentials/cpi/const.go new file mode 100644 index 000000000..a68cf53ec --- /dev/null +++ b/api/credentials/cpi/const.go @@ -0,0 +1,22 @@ +package cpi + +import ( + "ocm.software/ocm/api/credentials/internal" +) + +const ( + ID_TYPE = internal.ID_TYPE + + ATTR_TYPE = internal.ATTR_TYPE + ATTR_USERNAME = internal.ATTR_USERNAME + ATTR_EMAIL = internal.ATTR_EMAIL + ATTR_PASSWORD = internal.ATTR_PASSWORD + ATTR_SERVER_ADDRESS = internal.ATTR_SERVER_ADDRESS + ATTR_TOKEN = internal.ATTR_TOKEN + ATTR_IDENTITY_TOKEN = internal.ATTR_IDENTITY_TOKEN + ATTR_REGISTRY_TOKEN = internal.ATTR_REGISTRY_TOKEN + ATTR_KEY = internal.ATTR_KEY + ATTR_CERTIFICATE_AUTHORITY = internal.ATTR_CERTIFICATE_AUTHORITY + ATTR_CERTIFICATE = internal.ATTR_CERTIFICATE + ATTR_PRIVATE_KEY = internal.ATTR_PRIVATE_KEY +) diff --git a/api/credentials/cpi/interface.go b/api/credentials/cpi/interface.go new file mode 100644 index 000000000..f2870d8fe --- /dev/null +++ b/api/credentials/cpi/interface.go @@ -0,0 +1,128 @@ +package cpi + +// This is the Context Provider Interface for credential providers + +import ( + "ocm.software/ocm/api/credentials/internal" + "ocm.software/ocm/api/datacontext" + common "ocm.software/ocm/api/utils/misc" +) + +const ( + KIND_CREDENTIALS = internal.KIND_CREDENTIALS + KIND_REPOSITORY = internal.KIND_REPOSITORY +) + +const CONTEXT_TYPE = internal.CONTEXT_TYPE + +type ( + Context = internal.Context + ContextProvider = internal.ContextProvider + Repository = internal.Repository + RepositoryType = internal.RepositoryType + RepositoryTypeProvider = internal.RepositoryTypeProvider + RepositoryTypeScheme = internal.RepositoryTypeScheme + Credentials = internal.Credentials + CredentialsSource = internal.CredentialsSource + CredentialsChain = internal.CredentialsChain + CredentialsSpec = internal.CredentialsSpec + RepositorySpec = internal.RepositorySpec + GenericRepositorySpec = internal.GenericRepositorySpec + GenericCredentialsSpec = internal.GenericCredentialsSpec + DirectCredentials = internal.DirectCredentials + EvaluationContext = internal.EvaluationContext +) + +type ( + ConsumerIdentity = internal.ConsumerIdentity + ConsumerIdentityProvider = internal.ConsumerIdentityProvider + ProviderIdentity = internal.ProviderIdentity + ConsumerProvider = internal.ConsumerProvider + UsageContext = internal.UsageContext + StringUsageContext = internal.StringUsageContext + IdentityMatcher = internal.IdentityMatcher + IdentityMatcherInfo = internal.IdentityMatcherInfo + IdentityMatcherRegistry = internal.IdentityMatcherRegistry +) + +var DefaultContext = internal.DefaultContext + +func FromProvider(p ContextProvider) Context { + return internal.FromProvider(p) +} + +func New(m ...datacontext.BuilderMode) Context { + return internal.Builder{}.New(m...) +} + +func NewConsumerIdentity(typ string, attrs ...string) ConsumerIdentity { + return internal.NewConsumerIdentity(typ, attrs...) +} + +func NewGenericCredentialsSpec(name string, repospec *GenericRepositorySpec) *GenericCredentialsSpec { + return internal.NewGenericCredentialsSpec(name, repospec) +} + +func NewCredentialsSpec(name string, repospec RepositorySpec) CredentialsSpec { + return internal.NewCredentialsSpec(name, repospec) +} + +func ToGenericCredentialsSpec(spec CredentialsSpec) (*GenericCredentialsSpec, error) { + return internal.ToGenericCredentialsSpec(spec) +} + +func ToGenericRepositorySpec(spec RepositorySpec) (*GenericRepositorySpec, error) { + return internal.ToGenericRepositorySpec(spec) +} + +func RegisterStandardIdentityMatcher(typ string, matcher IdentityMatcher, desc string) { + internal.StandardIdentityMatchers.Register(typ, matcher, desc) +} + +func RegisterStandardIdentity(typ string, matcher IdentityMatcher, desc string, attrs string) { + internal.StandardIdentityMatchers.Register(typ, matcher, desc, attrs) +} + +func NewCredentials(props common.Properties) Credentials { + return internal.NewCredentials(props) +} + +func ErrUnknownCredentials(name string) error { + return internal.ErrUnknownCredentials(name) +} + +func ErrUnknownRepository(kind, name string) error { + return internal.ErrUnknownRepository(kind, name) +} + +func CredentialsForConsumer(ctx ContextProvider, id ConsumerIdentity, matchers ...IdentityMatcher) (Credentials, error) { + return internal.CredentialsForConsumer(ctx, id, false, matchers...) +} + +func RequiredCredentialsForConsumer(ctx ContextProvider, id ConsumerIdentity, matchers ...IdentityMatcher) (Credentials, error) { + return internal.CredentialsForConsumer(ctx, id, true, matchers...) +} + +func GetCredentialsForConsumer(ctx Context, ectx EvaluationContext, identity ConsumerIdentity, matchers ...IdentityMatcher) (CredentialsSource, error) { + return internal.GetCredentialsForConsumer(ctx, ectx, identity, matchers...) +} + +func GetEvaluationContextFor[T any](ectx EvaluationContext) T { + return internal.GetEvaluationContextFor[T](ectx) +} + +func SetEvaluationContextFor(ectx EvaluationContext, e any) { + internal.SetEvaluationContextFor(ectx, e) +} + +var ( + CompleteMatch = internal.CompleteMatch + NoMatch = internal.NoMatch + PartialMatch = internal.PartialMatch +) + +// provide context interface for other files to avoid diffs in imports. +var ( + newStrictRepositoryTypeScheme = internal.NewStrictRepositoryTypeScheme + defaultRepositoryTypeScheme = internal.DefaultRepositoryTypeScheme +) diff --git a/api/credentials/cpi/repotypes.go b/api/credentials/cpi/repotypes.go new file mode 100644 index 000000000..673998c36 --- /dev/null +++ b/api/credentials/cpi/repotypes.go @@ -0,0 +1,49 @@ +package cpi + +// this file is identical for contexts oci and credentials and similar for +// ocm. + +import ( + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/api/utils/runtime/descriptivetype" +) + +type RepositoryTypeVersionScheme = runtime.TypeVersionScheme[RepositorySpec, RepositoryType] + +func NewRepositoryTypeVersionScheme(kind string) RepositoryTypeVersionScheme { + return runtime.NewTypeVersionScheme[RepositorySpec, RepositoryType](kind, newStrictRepositoryTypeScheme()) +} + +func RegisterRepositoryType(rtype RepositoryType) { + defaultRepositoryTypeScheme.Register(rtype) +} + +func RegisterRepositoryTypeVersions(s RepositoryTypeVersionScheme) { + defaultRepositoryTypeScheme.AddKnownTypes(s) +} + +//////////////////////////////////////////////////////////////////////////////// + +func NewRepositoryType[I RepositorySpec](name string, opts ...RepositoryOption) RepositoryType { + return descriptivetype.NewTypedObjectTypeObject(runtime.NewVersionedTypedObjectType[RepositorySpec, I](name), opts...) +} + +func NewRepositoryTypeByConverter[I RepositorySpec, V runtime.TypedObject](name string, converter runtime.Converter[I, V], opts ...RepositoryOption) RepositoryType { + return descriptivetype.NewTypedObjectTypeObject(runtime.NewVersionedTypedObjectTypeByConverter[RepositorySpec, I](name, converter), opts...) +} + +func NewRepositoryTypeByFormatVersion(name string, fmt runtime.FormatVersion[RepositorySpec], opts ...RepositoryOption) RepositoryType { + return descriptivetype.NewTypedObjectTypeObject(runtime.NewVersionedTypedObjectTypeByFormatVersion[RepositorySpec](name, fmt), opts...) +} + +//////////////////////////////////////////////////////////////////////////////// + +type RepositoryOption = descriptivetype.Option + +func WithDescription(v string) RepositoryOption { + return descriptivetype.WithDescription(v) +} + +func WithFormatSpec(v string) RepositoryOption { + return descriptivetype.WithFormatSpec(v) +} diff --git a/pkg/contexts/credentials/doc.go b/api/credentials/doc.go similarity index 100% rename from pkg/contexts/credentials/doc.go rename to api/credentials/doc.go diff --git a/api/credentials/extensions/repositories/aliases/cache.go b/api/credentials/extensions/repositories/aliases/cache.go new file mode 100644 index 000000000..102f9a79f --- /dev/null +++ b/api/credentials/extensions/repositories/aliases/cache.go @@ -0,0 +1,37 @@ +package aliases + +import ( + "sync" + + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/datacontext" +) + +const ATTR_REPOS = "ocm.software/ocm/api/credentials/extensions/repositories/aliases" + +type Repositories struct { + sync.RWMutex + repos map[string]*Repository +} + +func newRepositories(datacontext.Context) interface{} { + return &Repositories{ + repos: map[string]*Repository{}, + } +} + +func (c *Repositories) GetRepository(name string) *Repository { + c.RLock() + defer c.RUnlock() + return c.repos[name] +} + +func (c *Repositories) Set(name string, spec cpi.RepositorySpec, creds cpi.CredentialsSource) { + c.Lock() + defer c.Unlock() + c.repos[name] = &Repository{ + name: name, + spec: spec, + creds: creds, + } +} diff --git a/api/credentials/extensions/repositories/aliases/repo_test.go b/api/credentials/extensions/repositories/aliases/repo_test.go new file mode 100644 index 000000000..36bbe8b2c --- /dev/null +++ b/api/credentials/extensions/repositories/aliases/repo_test.go @@ -0,0 +1,70 @@ +package aliases_test + +import ( + "encoding/json" + "reflect" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/credentials" + local "ocm.software/ocm/api/credentials/extensions/repositories/aliases" + common "ocm.software/ocm/api/utils/misc" +) + +var DefaultContext = credentials.New() + +var _ = Describe("alias credentials", func() { + props := common.Properties{ + "user": "USER", + "password": "PASSWORD", + } + + memorydata := "{\"type\":\"Memory\",\"repoName\":\"myrepo\"}" + specdata := "{\"type\":\"Alias\",\"alias\":\"test\"}" + + It("serializes repo spec", func() { + spec := local.NewRepositorySpec("test") + data, err := json.Marshal(spec) + Expect(err).To(Succeed()) + Expect(data).To(Equal([]byte(specdata))) + }) + It("deserializes repo spec", func() { + spec, err := DefaultContext.RepositorySpecForConfig([]byte(specdata), nil) + Expect(err).To(Succeed()) + Expect(reflect.TypeOf(spec).String()).To(Equal("*aliases.RepositorySpec")) + Expect(spec.(*local.RepositorySpec).Alias).To(Equal("test")) + }) + + It("resolves repository", func() { + memoryspec, err := credentials.NewGenericRepositorySpec([]byte(memorydata), nil) + Expect(err).To(Succeed()) + + err = DefaultContext.SetAlias("test", memoryspec) + Expect(err).To(Succeed()) + + repo, err := DefaultContext.RepositoryForConfig([]byte(specdata), nil) + Expect(err).To(Succeed()) + Expect(reflect.TypeOf(repo).String()).To(Equal("*memory.Repository")) + }) + + It("sets and retrieves credentials", func() { + memoryspec, err := credentials.NewGenericRepositorySpec([]byte(memorydata), nil) + Expect(err).To(Succeed()) + + err = DefaultContext.SetAlias("test", memoryspec) + Expect(err).To(Succeed()) + + repo, err := DefaultContext.RepositoryForConfig([]byte(memorydata), nil) + Expect(err).To(Succeed()) + + _, err = repo.WriteCredentials("bibo", credentials.NewCredentials(props)) + Expect(err).To(Succeed()) + + credspec := credentials.NewCredentialsSpec("bibo", local.NewRepositorySpec("test")) + + creds, err := DefaultContext.CredentialsForSpec(credspec) + Expect(err).To(Succeed()) + Expect(creds.Properties()).To(Equal(props)) + }) +}) diff --git a/api/credentials/extensions/repositories/aliases/repository.go b/api/credentials/extensions/repositories/aliases/repository.go new file mode 100644 index 000000000..d2df5d6ad --- /dev/null +++ b/api/credentials/extensions/repositories/aliases/repository.go @@ -0,0 +1,45 @@ +package aliases + +import ( + "sync" + + "ocm.software/ocm/api/credentials/cpi" +) + +type Repository struct { + sync.Mutex + name string + spec cpi.RepositorySpec + creds cpi.CredentialsSource + repo cpi.Repository +} + +func (a *Repository) GetRepository(ctx cpi.Context, creds cpi.Credentials) (cpi.Repository, error) { + a.Lock() + defer a.Unlock() + if a.repo != nil { + return a.repo, nil + } + + src := cpi.CredentialsChain{} + if a.creds != nil { + src = append(src, a.creds) + } + if creds != nil { + src = append(src, creds) + } + repo, err := ctx.RepositoryForSpec(a.spec, src...) + if err != nil { + return nil, err + } + a.repo = repo + return repo, nil +} + +func NewRepository(name string, spec cpi.RepositorySpec, creds cpi.Credentials) *Repository { + return &Repository{ + name: name, + spec: spec, + creds: creds, + } +} diff --git a/pkg/contexts/credentials/repositories/aliases/suite_test.go b/api/credentials/extensions/repositories/aliases/suite_test.go similarity index 100% rename from pkg/contexts/credentials/repositories/aliases/suite_test.go rename to api/credentials/extensions/repositories/aliases/suite_test.go diff --git a/api/credentials/extensions/repositories/aliases/type.go b/api/credentials/extensions/repositories/aliases/type.go new file mode 100644 index 000000000..c0fb7989e --- /dev/null +++ b/api/credentials/extensions/repositories/aliases/type.go @@ -0,0 +1,59 @@ +package aliases + +import ( + "fmt" + + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + Type = cpi.AliasRepositoryType + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +func init() { + cpi.RegisterRepositoryType(cpi.NewAliasRegistry(cpi.NewRepositoryType[*RepositorySpec](Type), setAlias)) + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1)) +} + +func setAlias(ctx cpi.Context, name string, spec cpi.RepositorySpec, creds cpi.CredentialsSource) error { + r := ctx.GetAttributes().GetOrCreateAttribute(ATTR_REPOS, newRepositories) + repos, ok := r.(*Repositories) + if !ok { + return fmt.Errorf("failed to assert type %T to Repositories", r) + } + repos.Set(name, spec, creds) + return nil +} + +// RepositorySpec describes a memory based repository interface. +type RepositorySpec struct { + runtime.ObjectVersionedType `json:",inline"` + Alias string `json:"alias"` +} + +// NewRepositorySpec creates a new memory RepositorySpec. +func NewRepositorySpec(name string) *RepositorySpec { + return &RepositorySpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(Type), + Alias: name, + } +} + +func (a *RepositorySpec) GetType() string { + return Type +} + +func (a *RepositorySpec) Repository(ctx cpi.Context, creds cpi.Credentials) (cpi.Repository, error) { + r := ctx.GetAttributes().GetOrCreateAttribute(ATTR_REPOS, newRepositories) + repos, ok := r.(*Repositories) + if !ok { + return nil, fmt.Errorf("failed to assert type %T to Repositories", r) + } + alias := repos.GetRepository(a.Alias) + if alias == nil { + return nil, cpi.ErrUnknownRepository(Type, a.Alias) + } + return alias.GetRepository(ctx, creds) +} diff --git a/api/credentials/extensions/repositories/directcreds/a_usage.go b/api/credentials/extensions/repositories/directcreds/a_usage.go new file mode 100644 index 000000000..cd749a3c5 --- /dev/null +++ b/api/credentials/extensions/repositories/directcreds/a_usage.go @@ -0,0 +1,14 @@ +package directcreds + +import ( + "ocm.software/ocm/api/utils/listformat" +) + +var usage = ` +This repository type can be used to specify a single inline credential +set. The default name is the empty string or ` + Type + `.` + +var format = `The repository specification supports the following fields: +` + listformat.FormatListElements("", listformat.StringElementDescriptionList{ + "properties", "*map[string]string*: direct credential fields", +}) diff --git a/api/credentials/extensions/repositories/directcreds/credentials.go b/api/credentials/extensions/repositories/directcreds/credentials.go new file mode 100644 index 000000000..460545563 --- /dev/null +++ b/api/credentials/extensions/repositories/directcreds/credentials.go @@ -0,0 +1,10 @@ +package directcreds + +import ( + "ocm.software/ocm/api/credentials/cpi" + common "ocm.software/ocm/api/utils/misc" +) + +func NewCredentials(props common.Properties) cpi.CredentialsSpec { + return cpi.NewCredentialsSpec(Type, NewRepositorySpec(props)) +} diff --git a/api/credentials/extensions/repositories/directcreds/repo_test.go b/api/credentials/extensions/repositories/directcreds/repo_test.go new file mode 100644 index 000000000..be1e07ec9 --- /dev/null +++ b/api/credentials/extensions/repositories/directcreds/repo_test.go @@ -0,0 +1,43 @@ +package directcreds_test + +import ( + "encoding/json" + "reflect" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/extensions/repositories/directcreds" + common "ocm.software/ocm/api/utils/misc" +) + +var DefaultContext = credentials.New() + +var _ = Describe("direct credentials", func() { + props := common.Properties{ + "user": "USER", + "password": "PASSWORD", + } + propsdata := "{\"type\":\"Credentials\",\"properties\":{\"password\":\"PASSWORD\",\"user\":\"USER\"}}" + + It("serializes credentials spec", func() { + spec := directcreds.NewRepositorySpec(props) + data, err := json.Marshal(spec) + Expect(err).To(Succeed()) + Expect(data).To(Equal([]byte(propsdata))) + }) + It("deserializes credentials spec", func() { + spec, err := DefaultContext.RepositoryForConfig([]byte(propsdata), nil) + Expect(err).To(Succeed()) + Expect(reflect.TypeOf(spec).String()).To(Equal("*directcreds.Repository")) + }) + + It("resolved direct credentials", func() { + spec := directcreds.NewCredentials(props) + + creds, err := DefaultContext.CredentialsForSpec(spec) + Expect(err).To(Succeed()) + Expect(creds.Properties()).To(Equal(props)) + }) +}) diff --git a/api/credentials/extensions/repositories/directcreds/repository.go b/api/credentials/extensions/repositories/directcreds/repository.go new file mode 100644 index 000000000..915683d3c --- /dev/null +++ b/api/credentials/extensions/repositories/directcreds/repository.go @@ -0,0 +1,34 @@ +package directcreds + +import ( + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/credentials/cpi" +) + +type Repository struct { + Credentials cpi.Credentials +} + +func NewRepository(creds cpi.Credentials) cpi.Repository { + return &Repository{ + Credentials: creds, + } +} + +func (r *Repository) ExistsCredentials(name string) (bool, error) { + return name == Type, nil +} + +func (r *Repository) LookupCredentials(name string) (cpi.Credentials, error) { + if name != Type && name != "" { + return nil, cpi.ErrUnknownCredentials(name) + } + return r.Credentials, nil +} + +func (r *Repository) WriteCredentials(name string, creds cpi.Credentials) (cpi.Credentials, error) { + return nil, errors.ErrNotSupported(cpi.KIND_CREDENTIALS, "write", "constant credential") +} + +var _ cpi.Repository = &Repository{} diff --git a/pkg/contexts/credentials/repositories/directcreds/suite_test.go b/api/credentials/extensions/repositories/directcreds/suite_test.go similarity index 100% rename from pkg/contexts/credentials/repositories/directcreds/suite_test.go rename to api/credentials/extensions/repositories/directcreds/suite_test.go diff --git a/api/credentials/extensions/repositories/directcreds/type.go b/api/credentials/extensions/repositories/directcreds/type.go new file mode 100644 index 000000000..1c36963df --- /dev/null +++ b/api/credentials/extensions/repositories/directcreds/type.go @@ -0,0 +1,56 @@ +package directcreds + +import ( + "ocm.software/ocm/api/credentials/cpi" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + Type = "Credentials" + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +func init() { + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](Type)) + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1, cpi.WithDescription(usage), cpi.WithFormatSpec(format))) +} + +// RepositorySpec describes a repository interface for single direct credentials. +type RepositorySpec struct { + runtime.ObjectVersionedType `json:",inline"` + Properties common.Properties `json:"properties"` +} + +var ( + _ cpi.RepositorySpec = &RepositorySpec{} + _ cpi.CredentialsSpec = &RepositorySpec{} +) + +// NewRepositorySpec creates a new RepositorySpec. +func NewRepositorySpec(credentials common.Properties) *RepositorySpec { + return &RepositorySpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(Type), + Properties: credentials, + } +} + +func (a *RepositorySpec) GetType() string { + return Type +} + +func (a *RepositorySpec) Repository(ctx cpi.Context, creds cpi.Credentials) (cpi.Repository, error) { + return NewRepository(cpi.NewCredentials(a.Properties)), nil +} + +func (a *RepositorySpec) Credentials(context cpi.Context, source ...cpi.CredentialsSource) (cpi.Credentials, error) { + return cpi.NewCredentials(a.Properties), nil +} + +func (a *RepositorySpec) GetCredentialsName() string { + return "" +} + +func (a *RepositorySpec) GetRepositorySpec(context cpi.Context) cpi.RepositorySpec { + return a +} diff --git a/api/credentials/extensions/repositories/dockerconfig/a_usage.go b/api/credentials/extensions/repositories/dockerconfig/a_usage.go new file mode 100644 index 000000000..d445b9b97 --- /dev/null +++ b/api/credentials/extensions/repositories/dockerconfig/a_usage.go @@ -0,0 +1,19 @@ +package dockerconfig + +import ( + "ocm.software/ocm/api/utils/listformat" +) + +var usage = ` +This repository type can be used to access credentials stored in a file +following the docker config json format. It take into account the +credentials helper section, also. If enabled, the described +credentials will be automatically assigned to appropriate consumer ids. +` + +var format = `The repository specification supports the following fields: +` + listformat.FormatListElements("", listformat.StringElementDescriptionList{ + "dockerConfigFile", "*string*: the file path to a docker config file", + "dockerConfig", "*json*: an embedded docker config json", + "propagateConsumerIdentity", "*bool*(optional): enable consumer id propagation", +}) diff --git a/api/credentials/extensions/repositories/dockerconfig/cache.go b/api/credentials/extensions/repositories/dockerconfig/cache.go new file mode 100644 index 000000000..c7a44ddfb --- /dev/null +++ b/api/credentials/extensions/repositories/dockerconfig/cache.go @@ -0,0 +1,40 @@ +package dockerconfig + +import ( + "sync" + + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/datacontext" +) + +const ATTR_REPOS = "ocm.software/ocm/api/credentials/extensions/repositories/dockerconfig" + +type Repositories struct { + lock sync.Mutex + repos map[string]*Repository +} + +func newRepositories(datacontext.Context) interface{} { + return &Repositories{ + repos: map[string]*Repository{}, + } +} + +func (r *Repositories) GetRepository(ctx cpi.Context, name string, data []byte, propagate bool) (*Repository, error) { + r.lock.Lock() + defer r.lock.Unlock() + var ( + err error = nil + repo *Repository + ) + if name != "" { + repo = r.repos[name] + } + if repo == nil { + repo, err = NewRepository(ctx, name, data, propagate) + if err == nil { + r.repos[name] = repo + } + } + return repo, err +} diff --git a/api/credentials/extensions/repositories/dockerconfig/credentials.go b/api/credentials/extensions/repositories/dockerconfig/credentials.go new file mode 100644 index 000000000..92d966f88 --- /dev/null +++ b/api/credentials/extensions/repositories/dockerconfig/credentials.go @@ -0,0 +1,67 @@ +package dockerconfig + +import ( + "github.com/docker/cli/cli/config/configfile" + dockercred "github.com/docker/cli/cli/config/credentials" + "github.com/docker/cli/cli/config/types" + "github.com/mandelsoft/goutils/set" + + "ocm.software/ocm/api/credentials/cpi" + common "ocm.software/ocm/api/utils/misc" +) + +type Credentials struct { + config *configfile.ConfigFile + name string + store dockercred.Store +} + +var _ cpi.Credentials = (*Credentials)(nil) + +// NewCredentials describes a default getter method for a authentication method. +func NewCredentials(cfg *configfile.ConfigFile, name string, store dockercred.Store) cpi.Credentials { + return &Credentials{ + config: cfg, + name: name, + store: store, + } +} + +func (c *Credentials) get() common.Properties { + auth, err := c.config.GetAuthConfig(c.name) + if err != nil { + return common.Properties{} + } + return newCredentials(auth).Properties() +} + +func (c *Credentials) Credentials(context cpi.Context, source ...cpi.CredentialsSource) (cpi.Credentials, error) { + var auth types.AuthConfig + var err error + if c.store == nil { + auth, err = c.config.GetAuthConfig(c.name) + } else { + auth, err = c.store.Get(c.name) + } + if err != nil { + return nil, err + } + return newCredentials(auth), nil +} + +func (c *Credentials) ExistsProperty(name string) bool { + _, ok := c.get()[name] + return ok +} + +func (c *Credentials) GetProperty(name string) string { + return c.get()[name] +} + +func (c *Credentials) PropertyNames() set.Set[string] { + return c.get().Names() +} + +func (c *Credentials) Properties() common.Properties { + return c.get() +} diff --git a/api/credentials/extensions/repositories/dockerconfig/default.go b/api/credentials/extensions/repositories/dockerconfig/default.go new file mode 100644 index 000000000..49d7deeca --- /dev/null +++ b/api/credentials/extensions/repositories/dockerconfig/default.go @@ -0,0 +1,32 @@ +package dockerconfig + +import ( + dockercli "github.com/docker/cli/cli/config" + "github.com/mandelsoft/filepath/pkg/filepath" + "github.com/mandelsoft/vfs/pkg/osfs" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/config" + credcfg "ocm.software/ocm/api/credentials/config" + "ocm.software/ocm/api/ocm/ocmutils/defaultconfigregistry" +) + +func init() { + defaultconfigregistry.RegisterDefaultConfigHandler(DefaultConfigHandler, desc) +} + +func DefaultConfigHandler(cfg config.Context) (string, config.Config, error) { + // use docker config as default config for ocm cli + d := filepath.Join(dockercli.Dir(), dockercli.ConfigFileName) + if ok, err := vfs.FileExists(osfs.New(), d); ok && err == nil { + ccfg := credcfg.New() + ccfg.AddRepository(NewRepositorySpec(d, true)) + return d, ccfg, nil + } + return "", nil, nil +} + +var desc = ` +The docker configuration file at ~/.docker/config.json is +read to feed in the configured credentials for OCI registries. +` diff --git a/api/credentials/extensions/repositories/dockerconfig/logging.go b/api/credentials/extensions/repositories/dockerconfig/logging.go new file mode 100644 index 000000000..a5660b36d --- /dev/null +++ b/api/credentials/extensions/repositories/dockerconfig/logging.go @@ -0,0 +1,7 @@ +package dockerconfig + +import ( + ocmlog "ocm.software/ocm/api/utils/logging" +) + +var REALM = ocmlog.DefineSubRealm("docker config handling as credential repository", "credentials/dockerconfig") diff --git a/api/credentials/extensions/repositories/dockerconfig/provider.go b/api/credentials/extensions/repositories/dockerconfig/provider.go new file mode 100644 index 000000000..f818d8b12 --- /dev/null +++ b/api/credentials/extensions/repositories/dockerconfig/provider.go @@ -0,0 +1,87 @@ +package dockerconfig + +import ( + "github.com/docker/cli/cli/config/configfile" + dockercred "github.com/docker/cli/cli/config/credentials" + + "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/utils" +) + +const PROVIDER = "ocm.software/credentialprovider/" + Type + +type ConsumerProvider struct { + cfg *configfile.ConfigFile +} + +var _ cpi.ConsumerProvider = (*ConsumerProvider)(nil) + +func (p *ConsumerProvider) Unregister(id cpi.ProviderIdentity) { +} + +func (p *ConsumerProvider) Match(ectx cpi.EvaluationContext, req cpi.ConsumerIdentity, cur cpi.ConsumerIdentity, m cpi.IdentityMatcher) (cpi.CredentialsSource, cpi.ConsumerIdentity) { + return p.get(req, cur, m) +} + +func (p *ConsumerProvider) Get(req cpi.ConsumerIdentity) (cpi.CredentialsSource, bool) { + creds, _ := p.get(req, nil, cpi.CompleteMatch) + return creds, creds != nil +} + +func (p *ConsumerProvider) get(req cpi.ConsumerIdentity, cur cpi.ConsumerIdentity, m cpi.IdentityMatcher) (cpi.CredentialsSource, cpi.ConsumerIdentity) { + cfg := p.cfg + all := cfg.GetAuthConfigs() + defaultStore := dockercred.DetectDefaultStore(cfg.CredentialsStore) + var store dockercred.Store + if defaultStore != "" { + store = dockercred.NewNativeStore(cfg, defaultStore) + } + + var creds cpi.CredentialsSource + + for h, a := range all { + hostname, port, _ := utils.SplitLocator(dockercred.ConvertToHostname(h)) + if hostname == "index.docker.io" { + hostname = "docker.io" + } + attrs := []string{identity.ID_HOSTNAME, hostname} + if port != "" { + attrs = append(attrs, identity.ID_PORT, port) + } + id := cpi.NewConsumerIdentity(identity.CONSUMER_TYPE, attrs...) + if m(req, cur, id) { + if IsEmptyAuthConfig(a) { + store := store + for hh, helper := range cfg.CredentialHelpers { + if hh == h { + store = dockercred.NewNativeStore(cfg, helper) + break + } + } + if store == nil { + continue + } + creds = NewCredentials(cfg, h, store) + } else { + creds = newCredentials(a) + } + cur = id + } + } + for h, helper := range cfg.CredentialHelpers { + hostname := dockercred.ConvertToHostname(h) + if hostname == "index.docker.io" { + hostname = "docker.io" + } + id := cpi.ConsumerIdentity{ + cpi.ATTR_TYPE: identity.CONSUMER_TYPE, + identity.ID_HOSTNAME: hostname, + } + if m(req, cur, id) { + creds = NewCredentials(cfg, h, dockercred.NewNativeStore(cfg, helper)) + cur = id + } + } + return creds, cur +} diff --git a/api/credentials/extensions/repositories/dockerconfig/repo_test.go b/api/credentials/extensions/repositories/dockerconfig/repo_test.go new file mode 100644 index 000000000..92981365d --- /dev/null +++ b/api/credentials/extensions/repositories/dockerconfig/repo_test.go @@ -0,0 +1,172 @@ +package dockerconfig_test + +import ( + "encoding/json" + "os" + "reflect" + "runtime" + "time" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/credentials/cpi" + local "ocm.software/ocm/api/credentials/extensions/repositories/dockerconfig" + "ocm.software/ocm/api/datacontext" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtimefinalizer" +) + +var _ = Describe("docker config", func() { + props := common.Properties{ + "username": "mandelsoft", + "password": "password", + "serverAddress": "https://index.docker.io/v1/", + } + + props2 := common.Properties{ + "username": "mandelsoft", + "password": "token", + "serverAddress": "https://ghcr.io", + } + + var DefaultContext credentials.Context + + BeforeEach(func() { + DefaultContext = credentials.New() + }) + + Context("file based", func() { + specdata := "{\"type\":\"DockerConfig\",\"dockerConfigFile\":\"testdata/dockerconfig.json\"}" + specdata2 := "{\"type\":\"DockerConfig\",\"dockerConfigFile\":\"testdata/dockerconfig.json\",\"propagateConsumerIdentity\":true}" + + It("serializes repo spec", func() { + spec := local.NewRepositorySpec("testdata/dockerconfig.json") + data := Must(json.Marshal(spec)) + Expect(data).To(Equal([]byte(specdata))) + + spec = local.NewRepositorySpec("testdata/dockerconfig.json").WithConsumerPropagation(true) + data = Must(json.Marshal(spec)) + Expect(data).To(Equal([]byte(specdata2))) + }) + + It("deserializes repo spec", func() { + spec := Must(DefaultContext.RepositorySpecForConfig([]byte(specdata), nil)) + Expect(reflect.TypeOf(spec).String()).To(Equal("*dockerconfig.RepositorySpec")) + Expect(spec.(*local.RepositorySpec).DockerConfigFile).To(Equal("testdata/dockerconfig.json")) + }) + + It("resolves repository", func() { + repo := Must(DefaultContext.RepositoryForConfig([]byte(specdata), nil)) + Expect(reflect.TypeOf(repo).String()).To(Equal("*dockerconfig.Repository")) + }) + + It("retrieves credentials", func() { + repo := Must(DefaultContext.RepositoryForConfig([]byte(specdata), nil)) + + creds := Must(repo.LookupCredentials("index.docker.io")) + Expect(creds.Properties()).To(Equal(props)) + + creds = Must(repo.LookupCredentials("ghcr.io")) + Expect(creds.Properties()).To(Equal(props2)) + }) + + It("propagates credentials to consumer identity", func() { + Must(DefaultContext.RepositoryForConfig([]byte(specdata2), nil)) + + creds := Must(credentials.CredentialsForConsumer(DefaultContext, credentials.ConsumerIdentity{ + cpi.ATTR_TYPE: identity.CONSUMER_TYPE, + identity.ID_HOSTNAME: "ghcr.io", + })) + Expect(creds.Properties()).To(Equal(props2)) + }) + }) + + Context("inline data", func() { + specdata := "{\"type\":\"DockerConfig\",\"dockerConfig\":{\"auths\":{\"https://index.docker.io/v1/\":{\"auth\":\"bWFuZGVsc29mdDpwYXNzd29yZA==\"},\"https://ghcr.io\":{\"auth\":\"bWFuZGVsc29mdDp0b2tlbg==\"}},\"HttpHeaders\":{\"User-Agent\":\"Docker-Client/18.06.1-ce (linux)\"}},\"propagateConsumerIdentity\":true}" + + It("serializes repo spec", func() { + configdata := Must(os.ReadFile("testdata/dockerconfig.json")) + spec := local.NewRepositorySpecForConfig(configdata).WithConsumerPropagation(true) + data := Must(json.Marshal(spec)) + + var ( + datajson map[string]interface{} + specjson map[string]interface{} + ) + // Comparing the bytes might be problematic as the order of the JSON objects within the config file might change + // during Marshaling + MustBeSuccessful(json.Unmarshal([]byte(specdata), &specjson)) + MustBeSuccessful(json.Unmarshal(data, &datajson)) + Expect(datajson).To(Equal(specjson)) + }) + + It("deserializes repo spec", func() { + spec := Must(DefaultContext.RepositorySpecForConfig([]byte(specdata), nil)) + Expect(reflect.TypeOf(spec).String()).To(Equal("*dockerconfig.RepositorySpec")) + configdata := Must(os.ReadFile("testdata/dockerconfig.json")) + var ( + configdatajson map[string]interface{} + dockerconfigjson map[string]interface{} + ) + // Comparing the bytes might be problematic as the order of the JSON objects within the config file might change + // during Marshaling + MustBeSuccessful(json.Unmarshal(configdata, &configdatajson)) + MustBeSuccessful(json.Unmarshal(spec.(*local.RepositorySpec).DockerConfig, &dockerconfigjson)) + Expect(dockerconfigjson).To(Equal(configdatajson)) + }) + + It("resolves repository", func() { + repo := Must(DefaultContext.RepositoryForConfig([]byte(specdata), nil)) + Expect(reflect.TypeOf(repo).String()).To(Equal("*dockerconfig.Repository")) + }) + + It("retrieves credentials", func() { + repo := Must(DefaultContext.RepositoryForConfig([]byte(specdata), nil)) + + creds := Must(repo.LookupCredentials("index.docker.io")) + Expect(creds.Properties()).To(Equal(props)) + + creds = Must(repo.LookupCredentials("ghcr.io")) + Expect(creds.Properties()).To(Equal(props2)) + }) + + It("propagates credentials to consumer identity", func() { + Must(DefaultContext.RepositoryForConfig([]byte(specdata), nil)) + + creds := Must(credentials.CredentialsForConsumer(DefaultContext, credentials.ConsumerIdentity{ + cpi.ATTR_TYPE: identity.CONSUMER_TYPE, + identity.ID_HOSTNAME: "ghcr.io", + })) + Expect(creds.Properties()).To(Equal(props2)) + }) + }) + + Context("ref handling", func() { + specdata := "{\"type\":\"DockerConfig\",\"dockerConfigFile\":\"testdata/dockerconfig.json\",\"propagateConsumerIdentity\":true}" + + It("can access the default context", func() { + ctx := credentials.New() + + r := runtimefinalizer.GetRuntimeFinalizationRecorder(ctx) + Expect(r).NotTo(BeNil()) + + Must(ctx.RepositoryForConfig([]byte(specdata), nil)) + + runtime.GC() + time.Sleep(time.Second) + ctx.GetType() + Expect(r.Get()).To(BeNil()) + + Expect(datacontext.GetContextRefCount(ctx)).To(Equal(1)) + ctx = nil + runtime.GC() + time.Sleep(time.Second) + + Expect(r.Get()).To(ContainElement(ContainSubstring(credentials.CONTEXT_TYPE))) + }) + }) +}) diff --git a/api/credentials/extensions/repositories/dockerconfig/repository.go b/api/credentials/extensions/repositories/dockerconfig/repository.go new file mode 100644 index 000000000..e82fc5548 --- /dev/null +++ b/api/credentials/extensions/repositories/dockerconfig/repository.go @@ -0,0 +1,143 @@ +package dockerconfig + +import ( + "bytes" + "fmt" + "os" + "strings" + "sync" + + "github.com/docker/cli/cli/config" + "github.com/docker/cli/cli/config/configfile" + "github.com/docker/cli/cli/config/types" + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/utils" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtimefinalizer" +) + +type Repository struct { + lock sync.RWMutex + ctx cpi.Context + propagate bool + path string + data []byte + config *configfile.ConfigFile +} + +func NewRepository(ctx cpi.Context, path string, data []byte, propagate bool) (*Repository, error) { + r := &Repository{ + ctx: datacontext.InternalContextRef(ctx), + propagate: propagate, + path: path, + data: data, + } + if path != "" && len(data) > 0 { + return nil, fmt.Errorf("only config file or config data possible") + } + err := r.Read(true) + return r, err +} + +var _ cpi.Repository = &Repository{} + +func (r *Repository) ExistsCredentials(name string) (bool, error) { + err := r.Read(false) + if err != nil { + return false, err + } + r.lock.RLock() + defer r.lock.RUnlock() + + _, err = r.config.GetAuthConfig(name) + return err != nil, err +} + +func (r *Repository) LookupCredentials(name string) (cpi.Credentials, error) { + err := r.Read(false) + if err != nil { + return nil, err + } + r.lock.RLock() + defer r.lock.RUnlock() + + auth, err := r.config.GetAuthConfig(name) + if err != nil { + return nil, err + } + return newCredentials(auth), nil +} + +func (r *Repository) WriteCredentials(name string, creds cpi.Credentials) (cpi.Credentials, error) { + return nil, errors.ErrNotSupported("write", "credentials", Type) +} + +func (r *Repository) Read(force bool) error { + r.lock.Lock() + defer r.lock.Unlock() + if !force && r.config != nil { + return nil + } + var ( + data []byte + err error + id runtimefinalizer.ObjectIdentity + ) + if r.path != "" { + path, err := utils.ResolvePath(r.path) + if err != nil { + return errors.Wrapf(err, "cannot resolve path %q", r.path) + } + data, err = os.ReadFile(path) + if err != nil { + return fmt.Errorf("failed to read file '%s': %w", path, err) + } + id = cpi.ProviderIdentity(PROVIDER + "/" + path) + } else if len(r.data) > 0 { + data = r.data + id = runtimefinalizer.NewObjectIdentity(PROVIDER) + } + + cfg, err := config.LoadFromReader(bytes.NewBuffer(data)) + if err != nil { + return fmt.Errorf("failed to load config: %w", err) + } + if r.propagate { + r.ctx.RegisterConsumerProvider(id, &ConsumerProvider{cfg}) + } + r.config = cfg + return nil +} + +func newCredentials(auth types.AuthConfig) cpi.Credentials { + props := common.Properties{ + cpi.ATTR_USERNAME: norm(auth.Username), + cpi.ATTR_PASSWORD: norm(auth.Password), + } + props.SetNonEmptyValue("auth", auth.Auth) + props.SetNonEmptyValue(cpi.ATTR_SERVER_ADDRESS, norm(auth.ServerAddress)) + props.SetNonEmptyValue(cpi.ATTR_IDENTITY_TOKEN, norm(auth.IdentityToken)) + props.SetNonEmptyValue(cpi.ATTR_REGISTRY_TOKEN, norm(auth.RegistryToken)) + return cpi.NewCredentials(props) +} + +func norm(s string) string { + for strings.HasSuffix(s, "\n") { + s = s[:len(s)-1] + } + return s +} + +// IsEmptyAuthConfig validates if the resulting auth config contains credentials. +func IsEmptyAuthConfig(auth types.AuthConfig) bool { + if len(auth.Auth) != 0 { + return false + } + if len(auth.Username) != 0 { + return false + } + return true +} diff --git a/pkg/contexts/credentials/repositories/dockerconfig/suite_test.go b/api/credentials/extensions/repositories/dockerconfig/suite_test.go similarity index 100% rename from pkg/contexts/credentials/repositories/dockerconfig/suite_test.go rename to api/credentials/extensions/repositories/dockerconfig/suite_test.go diff --git a/pkg/contexts/credentials/repositories/dockerconfig/testdata/dockerconfig.json b/api/credentials/extensions/repositories/dockerconfig/testdata/dockerconfig.json similarity index 100% rename from pkg/contexts/credentials/repositories/dockerconfig/testdata/dockerconfig.json rename to api/credentials/extensions/repositories/dockerconfig/testdata/dockerconfig.json diff --git a/api/credentials/extensions/repositories/dockerconfig/type.go b/api/credentials/extensions/repositories/dockerconfig/type.go new file mode 100644 index 000000000..6ec2282a1 --- /dev/null +++ b/api/credentials/extensions/repositories/dockerconfig/type.go @@ -0,0 +1,76 @@ +package dockerconfig + +import ( + "encoding/json" + "fmt" + + "github.com/mandelsoft/goutils/generics" + + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + Type = "DockerConfig" + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +func init() { + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](Type)) + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1, cpi.WithDescription(usage), cpi.WithFormatSpec(format))) +} + +// RepositorySpec describes a docker config based credential repository interface. +type RepositorySpec struct { + runtime.ObjectVersionedType `json:",inline"` + DockerConfigFile string `json:"dockerConfigFile,omitempty"` + DockerConfig json.RawMessage `json:"dockerConfig,omitempty"` + PropgateConsumerIdentity *bool `json:"propagateConsumerIdentity,omitempty"` +} + +func (s RepositorySpec) WithConsumerPropagation(propagate bool) *RepositorySpec { + s.PropgateConsumerIdentity = &propagate + return &s +} + +// NewRepositorySpec creates a new memory RepositorySpec. +func NewRepositorySpec(path string, prop ...bool) *RepositorySpec { + var p *bool + if len(prop) > 0 { + p = generics.Pointer(utils.Optional(prop...)) + } + if path == "" { + path = "~/.docker/config.json" + } + return &RepositorySpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(Type), + DockerConfigFile: path, + PropgateConsumerIdentity: p, + } +} + +func NewRepositorySpecForConfig(data []byte, prop ...bool) *RepositorySpec { + var p *bool + if len(prop) > 0 { + p = generics.Pointer(utils.Optional(prop...)) + } + return &RepositorySpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(Type), + DockerConfig: data, + PropgateConsumerIdentity: p, + } +} + +func (a *RepositorySpec) GetType() string { + return Type +} + +func (a *RepositorySpec) Repository(ctx cpi.Context, creds cpi.Credentials) (cpi.Repository, error) { + r := ctx.GetAttributes().GetOrCreateAttribute(ATTR_REPOS, newRepositories) + repos, ok := r.(*Repositories) + if !ok { + return nil, fmt.Errorf("failed to assert type %T to Repositories", r) + } + return repos.GetRepository(ctx, a.DockerConfigFile, a.DockerConfig, utils.AsBool(a.PropgateConsumerIdentity, true)) +} diff --git a/pkg/contexts/credentials/repositories/gardenerconfig/README.md b/api/credentials/extensions/repositories/gardenerconfig/README.md similarity index 100% rename from pkg/contexts/credentials/repositories/gardenerconfig/README.md rename to api/credentials/extensions/repositories/gardenerconfig/README.md diff --git a/api/credentials/extensions/repositories/gardenerconfig/cache.go b/api/credentials/extensions/repositories/gardenerconfig/cache.go new file mode 100644 index 000000000..93bb50c24 --- /dev/null +++ b/api/credentials/extensions/repositories/gardenerconfig/cache.go @@ -0,0 +1,36 @@ +package gardenerconfig + +import ( + "fmt" + "sync" + + "ocm.software/ocm/api/credentials/cpi" + gardenercfgcpi "ocm.software/ocm/api/credentials/extensions/repositories/gardenerconfig/cpi" + "ocm.software/ocm/api/datacontext" +) + +const ATTR_REPOS = "ocm.software/ocm/api/credentials/extensions/repositories/gardenerconfig" + +type Repositories struct { + lock sync.Mutex + repos map[string]*Repository +} + +func newRepositories(datacontext.Context) interface{} { + return &Repositories{ + repos: map[string]*Repository{}, + } +} + +func (r *Repositories) GetRepository(ctx cpi.Context, url string, configType gardenercfgcpi.ConfigType, cipher Cipher, key []byte, propagateConsumerIdentity bool) (*Repository, error) { + r.lock.Lock() + defer r.lock.Unlock() + if _, ok := r.repos[url]; !ok { + repo, err := NewRepository(ctx, url, configType, cipher, key, propagateConsumerIdentity) + if err != nil { + return nil, fmt.Errorf("unable to create repository: %w", err) + } + r.repos[url] = repo + } + return r.repos[url], nil +} diff --git a/api/credentials/extensions/repositories/gardenerconfig/cpi/interface.go b/api/credentials/extensions/repositories/gardenerconfig/cpi/interface.go new file mode 100644 index 000000000..f9270d21d --- /dev/null +++ b/api/credentials/extensions/repositories/gardenerconfig/cpi/interface.go @@ -0,0 +1,53 @@ +package cpi + +import ( + "io" + "sync" + + "ocm.software/ocm/api/credentials/cpi" +) + +type ConfigType string + +const ( + ContainerRegistry ConfigType = "container_registry" +) + +type Credential interface { + Name() string + ConsumerIdentity() cpi.ConsumerIdentity + Properties() cpi.Credentials +} + +type Handler interface { + ConfigType() ConfigType + ParseConfig(io.Reader) ([]Credential, error) +} + +var ( + handlers = map[ConfigType]Handler{} + lock sync.RWMutex +) + +func RegisterHandler(h Handler) { + lock.Lock() + defer lock.Unlock() + handlers[h.ConfigType()] = h +} + +func GetHandler(configType ConfigType) Handler { + lock.RLock() + defer lock.RUnlock() + return handlers[configType] +} + +func GetHandlers() map[ConfigType]Handler { + lock.RLock() + defer lock.RUnlock() + + m := map[ConfigType]Handler{} + for k, v := range handlers { + m[k] = v + } + return m +} diff --git a/api/credentials/extensions/repositories/gardenerconfig/credentials.go b/api/credentials/extensions/repositories/gardenerconfig/credentials.go new file mode 100644 index 000000000..6b12aa92e --- /dev/null +++ b/api/credentials/extensions/repositories/gardenerconfig/credentials.go @@ -0,0 +1,15 @@ +package gardenerconfig + +import ( + "ocm.software/ocm/api/credentials/cpi" +) + +type credentialGetter struct { + getCredentials func() (cpi.Credentials, error) +} + +var _ cpi.CredentialsSource = credentialGetter{} + +func (c credentialGetter) Credentials(ctx cpi.Context, cs ...cpi.CredentialsSource) (cpi.Credentials, error) { + return c.getCredentials() +} diff --git a/api/credentials/extensions/repositories/gardenerconfig/handler/container_registry/credentials.go b/api/credentials/extensions/repositories/gardenerconfig/handler/container_registry/credentials.go new file mode 100644 index 000000000..de0885b9a --- /dev/null +++ b/api/credentials/extensions/repositories/gardenerconfig/handler/container_registry/credentials.go @@ -0,0 +1,26 @@ +package container_registry + +import ( + "ocm.software/ocm/api/credentials/cpi" + gardenercfgcpi "ocm.software/ocm/api/credentials/extensions/repositories/gardenerconfig/cpi" +) + +type credentials struct { + name string + consumerIdentity cpi.ConsumerIdentity + properties cpi.Credentials +} + +func (c credentials) Name() string { + return c.name +} + +func (c credentials) ConsumerIdentity() cpi.ConsumerIdentity { + return c.consumerIdentity +} + +func (c credentials) Properties() cpi.Credentials { + return c.properties +} + +var _ gardenercfgcpi.Credential = credentials{} diff --git a/api/credentials/extensions/repositories/gardenerconfig/handler/container_registry/handler.go b/api/credentials/extensions/repositories/gardenerconfig/handler/container_registry/handler.go new file mode 100644 index 000000000..509975e16 --- /dev/null +++ b/api/credentials/extensions/repositories/gardenerconfig/handler/container_registry/handler.go @@ -0,0 +1,99 @@ +package container_registry + +import ( + "encoding/json" + "fmt" + "io" + "strings" + + "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/credentials/cpi" + gardenercfgcpi "ocm.software/ocm/api/credentials/extensions/repositories/gardenerconfig/cpi" + "ocm.software/ocm/api/credentials/identity/hostpath" + "ocm.software/ocm/api/utils" + common "ocm.software/ocm/api/utils/misc" +) + +func init() { + gardenercfgcpi.RegisterHandler(Handler{}) +} + +// config is the struct that describes the gardener config data structure. +type config struct { + ContainerRegistry map[string]*containerRegistryCredentials `json:"container_registry"` +} + +// containerRegistryCredentials describes the container registry credentials struct as defined by the gardener config. +type containerRegistryCredentials struct { + Username string `json:"username"` + Password string `json:"password"` + Privileges string `json:"privileges"` + Host string `json:"host,omitempty"` + ImageReferencePrefixes []string `json:"image_reference_prefixes,omitempty"` +} + +type Handler struct{} + +func (h Handler) ConfigType() gardenercfgcpi.ConfigType { + return gardenercfgcpi.ContainerRegistry +} + +func (h Handler) ParseConfig(configReader io.Reader) ([]gardenercfgcpi.Credential, error) { + config := &config{} + if err := json.NewDecoder(configReader).Decode(&config); err != nil { + return nil, fmt.Errorf("unable to unmarshal config: %w", err) + } + + creds := []gardenercfgcpi.Credential{} + for credentialName, credential := range config.ContainerRegistry { + var ( + scheme string + port string + ) + if credential.Host != "" { + parsedHost, err := utils.ParseURL(credential.Host) + if err != nil { + return nil, fmt.Errorf("unable to parse host: %w", err) + } + scheme = parsedHost.Scheme + port = parsedHost.Port() + } + + for _, imgRef := range credential.ImageReferencePrefixes { + parsedImgPrefix, err := utils.ParseURL(imgRef) + if err != nil { + return nil, fmt.Errorf("unable to parse image prefix: %w", err) + } + if parsedImgPrefix.Host == "index.docker.io" { + parsedImgPrefix.Host = "docker.io" + } + + consumerIdentity := cpi.ConsumerIdentity{ + cpi.ID_TYPE: identity.CONSUMER_TYPE, + hostpath.ID_HOSTNAME: parsedImgPrefix.Host, + hostpath.ID_PATHPREFIX: strings.Trim(parsedImgPrefix.Path, "/"), + } + consumerIdentity.SetNonEmptyValue(hostpath.ID_SCHEME, scheme) + consumerIdentity.SetNonEmptyValue(hostpath.ID_PORT, port) + + c := credentials{ + name: credentialName, + consumerIdentity: consumerIdentity, + properties: newCredentialsFromContainerRegistryCredentials(credential), + } + + creds = append(creds, c) + } + } + + return creds, nil +} + +func newCredentialsFromContainerRegistryCredentials(auth *containerRegistryCredentials) cpi.Credentials { + props := common.Properties{ + cpi.ATTR_USERNAME: auth.Username, + cpi.ATTR_PASSWORD: auth.Password, + } + props.SetNonEmptyValue(cpi.ATTR_SERVER_ADDRESS, auth.Host) + return cpi.NewCredentials(props) +} diff --git a/api/credentials/extensions/repositories/gardenerconfig/identity/identity.go b/api/credentials/extensions/repositories/gardenerconfig/identity/identity.go new file mode 100644 index 000000000..e5b18c4a0 --- /dev/null +++ b/api/credentials/extensions/repositories/gardenerconfig/identity/identity.go @@ -0,0 +1,60 @@ +package identity + +import ( + "fmt" + "strings" + + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/credentials/identity/hostpath" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/listformat" + common "ocm.software/ocm/api/utils/misc" +) + +const CONSUMER_TYPE = "Buildcredentials" + common.OCM_TYPE_GROUP_SUFFIX + +// used identity attributes. +const ( + ID_SCHEME = hostpath.ID_SCHEME + ID_HOSTNAME = hostpath.ID_HOSTNAME + ID_PORT = hostpath.ID_PORT + ID_PATHPREFIX = hostpath.ID_PATHPREFIX +) + +// used credential properties. +const ( + ATTR_KEY = cpi.ATTR_KEY +) + +var identityMatcher = hostpath.IdentityMatcher(CONSUMER_TYPE) + +func IdentityMatcher(pattern, cur, id cpi.ConsumerIdentity) bool { + return identityMatcher(pattern, cur, id) +} + +func init() { + attrs := listformat.FormatListElements("", listformat.StringElementDescriptionList{ + ATTR_KEY, "secret key use to access the credential server", + }) + + cpi.RegisterStandardIdentity(CONSUMER_TYPE, IdentityMatcher, `Gardener config credential matcher + +It matches the `+CONSUMER_TYPE+` consumer type and additionally acts like +the `+hostpath.IDENTITY_TYPE+` type.`, + attrs) +} + +func GetConsumerId(configURL string) (cpi.ConsumerIdentity, error) { + parsedURL, err := utils.ParseURL(configURL) + if err != nil { + return nil, fmt.Errorf("unable to parse url: %w", err) + } + + id := cpi.NewConsumerIdentity(CONSUMER_TYPE) + id.SetNonEmptyValue(ID_HOSTNAME, parsedURL.Host) + id.SetNonEmptyValue(ID_SCHEME, parsedURL.Scheme) + id.SetNonEmptyValue(ID_PATHPREFIX, strings.Trim(parsedURL.Path, "/")) + id.SetNonEmptyValue(ID_PORT, parsedURL.Port()) + + return id, nil +} diff --git a/api/credentials/extensions/repositories/gardenerconfig/init.go b/api/credentials/extensions/repositories/gardenerconfig/init.go new file mode 100644 index 000000000..e4a591675 --- /dev/null +++ b/api/credentials/extensions/repositories/gardenerconfig/init.go @@ -0,0 +1,5 @@ +package gardenerconfig + +import ( + _ "ocm.software/ocm/api/credentials/extensions/repositories/gardenerconfig/handler/container_registry" +) diff --git a/api/credentials/extensions/repositories/gardenerconfig/repo_test.go b/api/credentials/extensions/repositories/gardenerconfig/repo_test.go new file mode 100644 index 000000000..6f0998959 --- /dev/null +++ b/api/credentials/extensions/repositories/gardenerconfig/repo_test.go @@ -0,0 +1,200 @@ +package gardenerconfig_test + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "reflect" + "strings" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/mandelsoft/vfs/pkg/memoryfs" + + "ocm.software/ocm/api/credentials" + ociidentity "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/credentials/cpi" + local "ocm.software/ocm/api/credentials/extensions/repositories/gardenerconfig" + gardenercfgcpi "ocm.software/ocm/api/credentials/extensions/repositories/gardenerconfig/cpi" + "ocm.software/ocm/api/credentials/extensions/repositories/gardenerconfig/identity" + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/utils" +) + +var _ = Describe("gardener config", func() { + containerRegistryCfg := `{ + "container_registry": { + "test-credentials": { + "username": "abc", + "password": "123", + "image_reference_prefixes": [ + "eu.gcr.io/test-project" + ] + } + } +}` + encryptionKey := "abcdefghijklmnop" + encryptedContainerRegistryCfg := "Uz4mfePXFOUbjUEZnRrnG8zP2T7lRH6bR2rFHYgWDwZUXfW7D5wArwY4dsBACPVFNapF7kcM9z79+LvJXd2kNoIfvUyMOhrSDAyv4LtUqYSKBOoRH/aJMnXjmN9GQBCXSRSJs/Fu21AoDNo8fA9zYvvc7WxTldkYC/vHxLVNJu5j176e1QiaS9hwDjgNhgyUT3XUjHUyQ19PcRgwDglRLfiL4Cs/fYPPxdg4YZQdCnc=" + expectedCreds := cpi.DirectCredentials{ + cpi.ATTR_USERNAME: "abc", + cpi.ATTR_PASSWORD: "123", + } + + repoSpecTemplate := `{"type":"GardenerConfig","url":"%s","configType":"container_registry","cipher":"%s","propagateConsumerIdentity":true}` + + var defaultContext credentials.Context + + BeforeEach(func() { + defaultContext = credentials.New() + }) + + It("serializes repo spec", func() { + const ( + url = "http://localhost:8080/container_registry" + cipher = local.Plaintext + ) + expectedSpec := fmt.Sprintf(repoSpecTemplate, url, cipher) + + spec := local.NewRepositorySpec("http://localhost:8080/container_registry", "container_registry", local.Plaintext, true) + data, err := json.Marshal(spec) + Expect(err).ToNot(HaveOccurred()) + Expect(data).To(Equal([]byte(expectedSpec))) + }) + + It("deserializes repo spec", func() { + const ( + url = "http://localhost:8080/container_registry" + cipher = local.Plaintext + ) + specdata := fmt.Sprintf(repoSpecTemplate, url, cipher) + + spec, err := defaultContext.RepositorySpecForConfig([]byte(specdata), nil) + Expect(err).ToNot(HaveOccurred()) + Expect(reflect.TypeOf(spec).String()).To(Equal("*gardenerconfig.RepositorySpec")) + + parsedSpec := spec.(*local.RepositorySpec) + Expect(parsedSpec.URL).To(Equal(url)) + Expect(parsedSpec.ConfigType).To(Equal(gardenercfgcpi.ContainerRegistry)) + Expect(parsedSpec.Cipher).To(Equal(cipher)) + }) + + It("resolves repository", func() { + const ( + url = "http://localhost:8080/container_registry" + cipher = local.Plaintext + ) + specdata := fmt.Sprintf(repoSpecTemplate, url, cipher) + + repo, err := defaultContext.RepositoryForConfig([]byte(specdata), nil) + Expect(err).ToNot(HaveOccurred()) + Expect(repo).ToNot(BeNil()) + Expect(reflect.TypeOf(repo).String()).To(Equal("*gardenerconfig.Repository")) + }) + + It("retrieves credentials from unencrypted server", func() { + svr := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + writer.WriteHeader(200) + _, err := writer.Write([]byte(containerRegistryCfg)) + Expect(err).ToNot(HaveOccurred()) + })) + defer svr.Close() + + spec := fmt.Sprintf(repoSpecTemplate, svr.URL, local.Plaintext) + + repo, err := defaultContext.RepositoryForConfig([]byte(spec), nil) + Expect(err).ToNot(HaveOccurred()) + Expect(repo).ToNot(BeNil()) + + credentialsFromRepo, err := repo.LookupCredentials("test-credentials") + Expect(err).ToNot(HaveOccurred()) + Expect(credentialsFromRepo).To(Equal(expectedCreds)) + }) + + It("propagates credentials with consumer ids in the context", func() { + expectedConsumerId := cpi.ConsumerIdentity{ + cpi.ID_TYPE: ociidentity.CONSUMER_TYPE, + ociidentity.ID_HOSTNAME: "eu.gcr.io", + ociidentity.ID_PATHPREFIX: "test-project", + } + + svr := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + writer.WriteHeader(200) + _, err := writer.Write([]byte(containerRegistryCfg)) + Expect(err).ToNot(HaveOccurred()) + })) + defer svr.Close() + + spec := fmt.Sprintf(repoSpecTemplate, svr.URL, local.Plaintext) + + repo, err := defaultContext.RepositoryForConfig([]byte(spec), nil) + Expect(err).ToNot(HaveOccurred()) + Expect(repo).ToNot(BeNil()) + + credentialsFromCtx, err := credentials.CredentialsForConsumer(defaultContext, expectedConsumerId) + Expect(err).ToNot(HaveOccurred()) + Expect(credentialsFromCtx).To(Equal(expectedCreds)) + }) + + It("retrieves credentials from encrypted server", func() { + svr := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + writer.WriteHeader(200) + data, err := base64.StdEncoding.DecodeString(encryptedContainerRegistryCfg) + Expect(err).ToNot(HaveOccurred()) + _, err = writer.Write(data) + Expect(err).ToNot(HaveOccurred()) + })) + defer svr.Close() + + parsedURL, err := utils.ParseURL(svr.URL) + Expect(err).ToNot(HaveOccurred()) + + id := cpi.NewConsumerIdentity(identity.CONSUMER_TYPE) + id.SetNonEmptyValue(identity.ID_HOSTNAME, parsedURL.Host) + id.SetNonEmptyValue(identity.ID_SCHEME, parsedURL.Scheme) + id.SetNonEmptyValue(identity.ID_PATHPREFIX, strings.Trim(parsedURL.Path, "/")) + id.SetNonEmptyValue(identity.ID_PORT, parsedURL.Port()) + + creds := credentials.DirectCredentials{ + cpi.ATTR_KEY: encryptionKey, + } + defaultContext.SetCredentialsForConsumer(id, creds) + + spec := fmt.Sprintf(repoSpecTemplate, svr.URL, local.AESECB) + + repo, err := defaultContext.RepositoryForConfig([]byte(spec), nil) + Expect(err).ToNot(HaveOccurred()) + Expect(repo).ToNot(BeNil()) + + credentialsFromRepo, err := repo.LookupCredentials("test-credentials") + Expect(err).ToNot(HaveOccurred()) + Expect(credentialsFromRepo).To(Equal(expectedCreds)) + }) + + It("retrieves credentials from file", func() { + filename := "/container_registry" + fs := memoryfs.New() + vfsattr.Set(defaultContext, fs) + + file, err := fs.Create(filename) + Expect(err).ToNot(HaveOccurred()) + + _, err = file.Write([]byte(containerRegistryCfg)) + Expect(err).ToNot(HaveOccurred()) + + err = file.Close() + Expect(err).ToNot(HaveOccurred()) + + spec := fmt.Sprintf(repoSpecTemplate, "file://"+filename, local.Plaintext) + + repo, err := defaultContext.RepositoryForConfig([]byte(spec), nil) + Expect(err).ToNot(HaveOccurred()) + Expect(repo).ToNot(BeNil()) + + credentialsFromRepo, err := repo.LookupCredentials("test-credentials") + Expect(err).ToNot(HaveOccurred()) + Expect(credentialsFromRepo).To(Equal(expectedCreds)) + }) +}) diff --git a/api/credentials/extensions/repositories/gardenerconfig/repository.go b/api/credentials/extensions/repositories/gardenerconfig/repository.go new file mode 100644 index 000000000..0ef1e8fa5 --- /dev/null +++ b/api/credentials/extensions/repositories/gardenerconfig/repository.go @@ -0,0 +1,223 @@ +package gardenerconfig + +import ( + "bytes" + "context" + "crypto/aes" + "crypto/cipher" + "fmt" + "io" + "net/http" + "net/url" + "sync" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/credentials/cpi" + gardenercfgcpi "ocm.software/ocm/api/credentials/extensions/repositories/gardenerconfig/cpi" + "ocm.software/ocm/api/credentials/extensions/repositories/gardenerconfig/identity" + "ocm.software/ocm/api/credentials/internal" + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/utils/errkind" +) + +type Cipher string + +const ( + Plaintext Cipher = "PLAINTEXT" + AESECB Cipher = "AES.ECB" +) + +type Repository struct { + ctx cpi.Context + lock sync.RWMutex + url string + configType gardenercfgcpi.ConfigType + cipher Cipher + key []byte + propagateConsumerIdentity bool + creds map[string]cpi.Credentials + fs vfs.FileSystem +} + +var _ cpi.ConsumerIdentityProvider = (*Repository)(nil) + +func NewRepository(ctx cpi.Context, url string, configType gardenercfgcpi.ConfigType, cipher Cipher, key []byte, propagateConsumerIdentity bool) (*Repository, error) { + r := &Repository{ + ctx: ctx, + url: url, + configType: configType, + cipher: cipher, + key: key, + propagateConsumerIdentity: propagateConsumerIdentity, + fs: vfsattr.Get(ctx), + } + if err := r.read(true); err != nil { + return nil, fmt.Errorf("unable to read repository content: %w", err) + } + return r, nil +} + +var _ cpi.Repository = &Repository{} + +func (r *Repository) GetConsumerId(uctx ...internal.UsageContext) internal.ConsumerIdentity { + id, err := identity.GetConsumerId(r.url) + if err != nil { + return nil + } + return id +} + +func (r *Repository) GetIdentityMatcher() string { + return identity.CONSUMER_TYPE +} + +func (r *Repository) ExistsCredentials(name string) (bool, error) { + r.lock.RLock() + defer r.lock.RUnlock() + + if err := r.read(false); err != nil { + return false, fmt.Errorf("unable to read repository content: %w", err) + } + + return r.creds[name] != nil, nil +} + +func (r *Repository) LookupCredentials(name string) (cpi.Credentials, error) { + r.lock.RLock() + defer r.lock.RUnlock() + + if err := r.read(false); err != nil { + return nil, fmt.Errorf("unable to read repository content: %w", err) + } + + auth, ok := r.creds[name] + if !ok { + return nil, cpi.ErrUnknownCredentials(name) + } + + return auth, nil +} + +func (r *Repository) WriteCredentials(name string, creds cpi.Credentials) (cpi.Credentials, error) { + return nil, errors.ErrNotSupported("write", "credentials", Type) +} + +func (r *Repository) read(force bool) error { + if !force && r.creds != nil { + return nil + } + + configReader, err := r.getRawConfig() + if err != nil { + return fmt.Errorf("unable to get config: %w", err) + } + if configReader == nil { + return nil + } + defer configReader.Close() + + handler := gardenercfgcpi.GetHandler(r.configType) + if handler == nil { + return errors.Newf("unable to get handler for config type %s", string(r.configType)) + } + + creds, err := handler.ParseConfig(configReader) + if err != nil { + return fmt.Errorf("unable to parse config: %w", err) + } + + r.creds = map[string]cpi.Credentials{} + for _, cred := range creds { + credName := cred.Name() + if _, ok := r.creds[credName]; !ok { + r.creds[credName] = cred.Properties() + } + if r.propagateConsumerIdentity { + getCredentials := func() (cpi.Credentials, error) { + return r.LookupCredentials(credName) + } + cg := credentialGetter{ + getCredentials: getCredentials, + } + r.ctx.SetCredentialsForConsumer(cred.ConsumerIdentity(), cg) + } + } + + return nil +} + +func (r *Repository) getRawConfig() (io.ReadCloser, error) { + u, err := url.Parse(r.url) + if err != nil { + return nil, fmt.Errorf("unable to parse url: %w", err) + } + + var reader io.ReadCloser + if u.Scheme == "file" { + f, err := r.fs.Open(u.Path) + if err != nil { + return nil, fmt.Errorf("unable to open file: %w", err) + } + reader = f + } else { + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, u.String(), nil) + if err != nil { + return nil, fmt.Errorf("request to secret server failed: %w", err) + } + res, err := http.DefaultClient.Do(req) + if err != nil { + // the secret server might be temporarily not available. + // for these situations we should allow a retry at a later point in time + // while keeping the old data for the moment. + if errkind.IsRetryable(err) { + // TODO: log error + return nil, nil + } + return nil, fmt.Errorf("request to secret server failed: %w", err) + } + reader = res.Body + } + + switch r.cipher { + case AESECB: + var srcBuf bytes.Buffer + if _, err := io.Copy(&srcBuf, reader); err != nil { + return nil, fmt.Errorf("unable to read: %w", err) + } + if err := reader.Close(); err != nil { + return nil, fmt.Errorf("unable to close reader: %w", err) + } + block, err := aes.NewCipher(r.key) + if err != nil { + return nil, fmt.Errorf("unable to create cipher: %w", err) + } + dst := make([]byte, srcBuf.Len()) + if err := ecbDecrypt(block, dst, srcBuf.Bytes()); err != nil { + return nil, fmt.Errorf("unable to decrypt: %w", err) + } + + return io.NopCloser(bytes.NewBuffer(dst)), nil + case Plaintext: + return reader, nil + default: + return nil, errors.ErrNotImplemented("cipher algorithm", string(r.cipher), Type) + } +} + +func ecbDecrypt(block cipher.Block, dst, src []byte) error { + blockSize := block.BlockSize() + if len(src)%blockSize != 0 { + return fmt.Errorf("input must contain only full blocks (blocksize: %d; input length: %d)", blockSize, len(src)) + } + if len(dst) < len(src) { + return errors.New("destination is smaller than source") + } + for len(src) > 0 { + block.Decrypt(dst, src[:blockSize]) + src = src[blockSize:] + dst = dst[blockSize:] + } + return nil +} diff --git a/pkg/contexts/credentials/repositories/gardenerconfig/suite_test.go b/api/credentials/extensions/repositories/gardenerconfig/suite_test.go similarity index 100% rename from pkg/contexts/credentials/repositories/gardenerconfig/suite_test.go rename to api/credentials/extensions/repositories/gardenerconfig/suite_test.go diff --git a/api/credentials/extensions/repositories/gardenerconfig/type.go b/api/credentials/extensions/repositories/gardenerconfig/type.go new file mode 100644 index 000000000..2b0d95952 --- /dev/null +++ b/api/credentials/extensions/repositories/gardenerconfig/type.go @@ -0,0 +1,96 @@ +package gardenerconfig + +import ( + "fmt" + + "github.com/mandelsoft/goutils/generics" + + "ocm.software/ocm/api/credentials/cpi" + gardenercfgcpi "ocm.software/ocm/api/credentials/extensions/repositories/gardenerconfig/cpi" + "ocm.software/ocm/api/credentials/extensions/repositories/gardenerconfig/identity" + "ocm.software/ocm/api/credentials/internal" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + Type = "GardenerConfig" + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +func init() { + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](Type)) + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1)) +} + +// RepositorySpec describes a secret server based credential repository interface. +type RepositorySpec struct { + runtime.ObjectVersionedType `json:",inline"` + URL string `json:"url"` + ConfigType gardenercfgcpi.ConfigType `json:"configType"` + Cipher Cipher `json:"cipher"` + PropagateConsumerIdentity *bool `json:"propagateConsumerIdentity,omitempty"` +} + +var _ cpi.ConsumerIdentityProvider = (*RepositorySpec)(nil) + +// NewRepositorySpec creates a new memory RepositorySpec. +func NewRepositorySpec(url string, configType gardenercfgcpi.ConfigType, cipher Cipher, propagateConsumerIdentity ...bool) *RepositorySpec { + return &RepositorySpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(Type), + URL: url, + ConfigType: configType, + Cipher: cipher, + PropagateConsumerIdentity: generics.Pointer(utils.OptionalDefaultedBool(true, propagateConsumerIdentity...)), + } +} + +func (a *RepositorySpec) GetType() string { + return Type +} + +func (a *RepositorySpec) Repository(ctx cpi.Context, creds cpi.Credentials) (cpi.Repository, error) { + r := ctx.GetAttributes().GetOrCreateAttribute(ATTR_REPOS, newRepositories) + repos, ok := r.(*Repositories) + if !ok { + return nil, fmt.Errorf("failed to assert type %T to Responsitories", r) + } + + key, err := getKey(ctx, a.URL) + if err != nil { + return nil, fmt.Errorf("unable to get key from context: %w", err) + } + + return repos.GetRepository(ctx, a.URL, a.ConfigType, a.Cipher, key, utils.AsBool(a.PropagateConsumerIdentity, true)) +} + +func (a *RepositorySpec) GetConsumerId(uctx ...internal.UsageContext) internal.ConsumerIdentity { + id, err := identity.GetConsumerId(a.URL) + if err != nil { + return nil + } + return id +} + +func (a *RepositorySpec) GetIdentityMatcher() string { + return identity.CONSUMER_TYPE +} + +func getKey(cctx cpi.Context, configURL string) ([]byte, error) { + id, err := identity.GetConsumerId(configURL) + if err != nil { + return nil, err + } + + creds, err := cpi.CredentialsForConsumer(cctx, id) + if err != nil { + return nil, err + } + + var key string + if creds != nil { + key = creds.GetProperty(identity.ATTR_KEY) + } + + return []byte(key), nil +} diff --git a/api/credentials/extensions/repositories/init.go b/api/credentials/extensions/repositories/init.go new file mode 100644 index 000000000..4e32290cc --- /dev/null +++ b/api/credentials/extensions/repositories/init.go @@ -0,0 +1,12 @@ +package repositories + +import ( + _ "ocm.software/ocm/api/credentials/extensions/repositories/aliases" + _ "ocm.software/ocm/api/credentials/extensions/repositories/directcreds" + _ "ocm.software/ocm/api/credentials/extensions/repositories/dockerconfig" + _ "ocm.software/ocm/api/credentials/extensions/repositories/gardenerconfig" + _ "ocm.software/ocm/api/credentials/extensions/repositories/memory" + _ "ocm.software/ocm/api/credentials/extensions/repositories/memory/config" + _ "ocm.software/ocm/api/credentials/extensions/repositories/npm" + _ "ocm.software/ocm/api/credentials/extensions/repositories/vault" +) diff --git a/api/credentials/extensions/repositories/memory/cache.go b/api/credentials/extensions/repositories/memory/cache.go new file mode 100644 index 000000000..d58c8e8be --- /dev/null +++ b/api/credentials/extensions/repositories/memory/cache.go @@ -0,0 +1,31 @@ +package memory + +import ( + "sync" + + "ocm.software/ocm/api/datacontext" +) + +const ATTR_REPOS = "ocm.software/ocm/api/credentials/extensions/repositories/memory" + +type Repositories struct { + lock sync.Mutex + repos map[string]*Repository +} + +func newRepositories(datacontext.Context) interface{} { + return &Repositories{ + repos: map[string]*Repository{}, + } +} + +func (r *Repositories) GetRepository(name string) *Repository { + r.lock.Lock() + defer r.lock.Unlock() + repo := r.repos[name] + if repo == nil { + repo = NewRepository(name) + r.repos[name] = repo + } + return repo +} diff --git a/api/credentials/extensions/repositories/memory/config/config_test.go b/api/credentials/extensions/repositories/memory/config/config_test.go new file mode 100644 index 000000000..2c0647a26 --- /dev/null +++ b/api/credentials/extensions/repositories/memory/config/config_test.go @@ -0,0 +1,62 @@ +package config_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/env" + + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/extensions/repositories/memory" + common "ocm.software/ocm/api/utils/misc" +) + +var _ = Describe("configure credentials", func() { + var env *Environment + var ctx credentials.Context + var cfg config.Context + + BeforeEach(func() { + env = NewEnvironment(TestData()) + cfg = config.New() + ctx = credentials.WithConfigs(cfg).New() + }) + + AfterEach(func() { + env.Cleanup() + }) + + It("reads config with ref", func() { + data, err := vfs.ReadFile(env, "/testdata/creds.yaml") + Expect(err).To(Succeed()) + _, err = cfg.ApplyData(data, nil, "creds.yaml") + Expect(err).To(Succeed()) + + spec := memory.NewRepositorySpec("default") + repo, err := ctx.RepositoryForSpec(spec) + Expect(err).To(Succeed()) + mem := repo.(*memory.Repository) + Expect(mem.ExistsCredentials("ref")).To(BeTrue()) + creds, err := mem.LookupCredentials("ref") + Expect(err).To(Succeed()) + Expect(creds.Properties()).To(Equal(common.Properties{"username": "mandelsoft", "password": "specialsecret"})) + }) + + It("reads config with direct", func() { + data, err := vfs.ReadFile(env, "/testdata/creds.yaml") + Expect(err).To(Succeed()) + _, err = cfg.ApplyData(data, nil, "creds.yaml") + Expect(err).To(Succeed()) + + spec := memory.NewRepositorySpec("default") + repo, err := ctx.RepositoryForSpec(spec) + Expect(err).To(Succeed()) + mem := repo.(*memory.Repository) + Expect(mem.ExistsCredentials("direct")).To(BeTrue()) + creds, err := mem.LookupCredentials("direct") + Expect(err).To(Succeed()) + Expect(creds.Properties()).To(Equal(common.Properties{"username": "mandelsoft2", "password": "specialsecret2"})) + }) +}) diff --git a/pkg/contexts/credentials/repositories/memory/config/suite_test.go b/api/credentials/extensions/repositories/memory/config/suite_test.go similarity index 100% rename from pkg/contexts/credentials/repositories/memory/config/suite_test.go rename to api/credentials/extensions/repositories/memory/config/suite_test.go diff --git a/pkg/contexts/credentials/repositories/memory/config/testdata/creds.yaml b/api/credentials/extensions/repositories/memory/config/testdata/creds.yaml similarity index 100% rename from pkg/contexts/credentials/repositories/memory/config/testdata/creds.yaml rename to api/credentials/extensions/repositories/memory/config/testdata/creds.yaml diff --git a/api/credentials/extensions/repositories/memory/config/type.go b/api/credentials/extensions/repositories/memory/config/type.go new file mode 100644 index 000000000..95038eaa8 --- /dev/null +++ b/api/credentials/extensions/repositories/memory/config/type.go @@ -0,0 +1,131 @@ +package config + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + + cfgcpi "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/credentials/extensions/repositories/memory" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ConfigType = "memory.credentials" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX + ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" +) + +func init() { + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1, usage)) +} + +// Config describes a configuration for the config context. +type Config struct { + runtime.ObjectVersionedType `json:",inline"` + RepoName string `json:"repoName"` + Credentials []CredentialsSpec `json:"credentials,omitempty"` +} + +type CredentialsSpec struct { + CredentialsName string `json:"credentialsName"` + // Reference refers to credentials store in some othe repo + Reference *cpi.GenericCredentialsSpec `json:"reference,omitempty"` + // Credentials are direct credentials (one of Reference or Credentials must be set) + Credentials common.Properties `json:"credentials"` +} + +// New creates a new memory ConfigSpec. +func New(repo string, credentials ...CredentialsSpec) *Config { + return &Config{ + ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), + RepoName: repo, + Credentials: credentials, + } +} + +func (a *Config) GetType() string { + return ConfigType +} + +func (a *Config) AddCredentials(name string, props common.Properties) error { + a.Credentials = append(a.Credentials, CredentialsSpec{CredentialsName: name, Credentials: props}) + return nil +} + +func (a *Config) AddCredentialsRef(name string, refname string, spec cpi.RepositorySpec) error { + repo, err := cpi.ToGenericRepositorySpec(spec) + if err != nil { + return fmt.Errorf("unable to convert cpi repository spec to generic: %w", err) + } + + ref := cpi.NewGenericCredentialsSpec(refname, repo) + a.Credentials = append(a.Credentials, CredentialsSpec{CredentialsName: name, Reference: ref}) + + return nil +} + +func (a *Config) ApplyTo(ctx cfgcpi.Context, target interface{}) error { + list := errors.ErrListf("applying config") + + t, ok := target.(cpi.Context) + if !ok { + return cfgcpi.ErrNoContext(ConfigType) + } + + repo, err := t.RepositoryForSpec(memory.NewRepositorySpec(a.RepoName)) + if err != nil { + return fmt.Errorf("unable to get repository for spec: %w", err) + } + + mem, ok := repo.(*memory.Repository) + if !ok { + return fmt.Errorf("invalid type assertion of type %T to memory.Repository", repo) + } + + for i, e := range a.Credentials { + var creds cpi.Credentials + if e.Reference != nil { + if len(e.Credentials) != 0 { + err = fmt.Errorf("credentials and reference set") + } else { + creds, err = e.Reference.Credentials(t) + } + } else { + creds = cpi.NewCredentials(e.Credentials) + } + if err != nil { + list.Add(errors.Wrapf(err, "config entry %d[%s]", i, e.CredentialsName)) + } + if creds != nil { + _, err = mem.WriteCredentials(e.CredentialsName, creds) + if err != nil { + list.Add(errors.Wrapf(err, "config entry %d", i)) + } + } + } + return list.Result() +} + +const usage = ` +The config type ` + ConfigType + ` can be used to define a list +of arbitrary credentials stored in a memory based credentials repository: + +
+    type: ` + ConfigType + `
+    repoName: default
+    credentials:
+      - credentialsName: ref
+        reference:  # refer to a credential set stored in some other credential repository
+          type: Credentials # this is a repo providing just one explicit credential set
+          properties:
+            username: mandelsoft
+            password: specialsecret
+      - credentialsName: direct
+        credentials: # direct credential specification
+            username: mandelsoft2
+            password: specialsecret2
+
+` diff --git a/api/credentials/extensions/repositories/memory/repo_test.go b/api/credentials/extensions/repositories/memory/repo_test.go new file mode 100644 index 000000000..c1a244f48 --- /dev/null +++ b/api/credentials/extensions/repositories/memory/repo_test.go @@ -0,0 +1,119 @@ +package memory_test + +import ( + "encoding/json" + "reflect" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/credentials" + local "ocm.software/ocm/api/credentials/extensions/repositories/memory" + common "ocm.software/ocm/api/utils/misc" +) + +var DefaultContext = credentials.New() + +var _ = Describe("direct credentials", func() { + props := common.Properties{ + "user": "USER", + "password": "PASSWORD", + } + + props2 := common.Properties{ + "user": "OTHER", + "password": "OTHERPASSWORD", + } + + specdata := "{\"type\":\"Memory\",\"repoName\":\"test\"}" + + _ = props + + It("serializes repo spec", func() { + spec := local.NewRepositorySpec("test") + data, err := json.Marshal(spec) + Expect(err).To(Succeed()) + Expect(data).To(Equal([]byte(specdata))) + }) + It("deserializes repo spec", func() { + spec, err := DefaultContext.RepositorySpecForConfig([]byte(specdata), nil) + Expect(err).To(Succeed()) + Expect(reflect.TypeOf(spec).String()).To(Equal("*memory.RepositorySpec")) + Expect(spec.(*local.RepositorySpec).RepositoryName).To(Equal("test")) + }) + + It("resolves repository", func() { + repo, err := DefaultContext.RepositoryForConfig([]byte(specdata), nil) + Expect(err).To(Succeed()) + Expect(reflect.TypeOf(repo).String()).To(Equal("*memory.Repository")) + }) + + It("sets and retrieves credentials", func() { + repo, err := DefaultContext.RepositoryForConfig([]byte(specdata), nil) + Expect(err).To(Succeed()) + + _, err = repo.WriteCredentials("bibo", credentials.NewCredentials(props)) + Expect(err).To(Succeed()) + + creds, err := repo.LookupCredentials("bibo") + Expect(err).To(Succeed()) + Expect(creds.Properties()).To(Equal(props)) + + creds, err = repo.LookupCredentials("other") + Expect(err).NotTo(Succeed()) + Expect(creds).To(BeNil()) + }) + + It("caches repo", func() { + repo, err := DefaultContext.RepositoryForConfig([]byte(specdata), nil) + Expect(err).To(Succeed()) + + _, err = repo.WriteCredentials("bibo", credentials.NewCredentials(props)) + Expect(err).To(Succeed()) + + // re-request repo by spec + repo, err = DefaultContext.RepositoryForConfig([]byte(specdata), nil) + Expect(err).To(Succeed()) + + creds, err := repo.LookupCredentials("bibo") + Expect(err).To(Succeed()) + Expect(creds.Properties()).To(Equal(props)) + + creds, err = repo.LookupCredentials("other") + Expect(err).NotTo(Succeed()) + Expect(creds).To(BeNil()) + }) + + It("caches repo in two contexts", func() { + ctx1 := DefaultContext + ctx2 := credentials.New() + + // write to first context + repo1, err := ctx1.RepositoryForConfig([]byte(specdata), nil) + Expect(err).To(Succeed()) + + _, err = repo1.WriteCredentials("bibo", credentials.NewCredentials(props)) + Expect(err).To(Succeed()) + + // request repo in second context + repo2, err := ctx2.RepositoryForConfig([]byte(specdata), nil) + Expect(err).To(Succeed()) + + creds, err := repo2.LookupCredentials("bibo") + Expect(err).NotTo(Succeed()) + Expect(creds).To(BeNil()) + + // write to second context + _, err = repo2.WriteCredentials("bibo", credentials.NewCredentials(props2)) + Expect(err).To(Succeed()) + + creds, err = repo2.LookupCredentials("bibo") + Expect(err).To(Succeed()) + Expect(creds.Properties()).To(Equal(props2)) + + // check first context + creds, err = repo1.LookupCredentials("bibo") + Expect(err).To(Succeed()) + Expect(creds.Properties()).To(Equal(props)) + }) +}) diff --git a/api/credentials/extensions/repositories/memory/repository.go b/api/credentials/extensions/repositories/memory/repository.go new file mode 100644 index 000000000..3b93c1a99 --- /dev/null +++ b/api/credentials/extensions/repositories/memory/repository.go @@ -0,0 +1,47 @@ +package memory + +import ( + "sync" + + "ocm.software/ocm/api/credentials/cpi" +) + +type Repository struct { + lock sync.RWMutex + name string + credentials map[string]cpi.Credentials +} + +func NewRepository(name string) *Repository { + return &Repository{ + name: name, + credentials: map[string]cpi.Credentials{}, + } +} + +var _ cpi.Repository = &Repository{} + +func (r *Repository) ExistsCredentials(name string) (bool, error) { + r.lock.RLock() + defer r.lock.RUnlock() + _, ok := r.credentials[name] + return ok, nil +} + +func (r *Repository) LookupCredentials(name string) (cpi.Credentials, error) { + r.lock.RLock() + defer r.lock.RUnlock() + c, ok := r.credentials[name] + if ok { + return cpi.NewCredentials(c.Properties()), nil + } + return nil, cpi.ErrUnknownCredentials(name) +} + +func (r *Repository) WriteCredentials(name string, creds cpi.Credentials) (cpi.Credentials, error) { + c := cpi.NewCredentials(creds.Properties()) + r.lock.Lock() + defer r.lock.Unlock() + r.credentials[name] = c + return c, nil +} diff --git a/pkg/contexts/credentials/repositories/memory/suite_test.go b/api/credentials/extensions/repositories/memory/suite_test.go similarity index 100% rename from pkg/contexts/credentials/repositories/memory/suite_test.go rename to api/credentials/extensions/repositories/memory/suite_test.go diff --git a/api/credentials/extensions/repositories/memory/type.go b/api/credentials/extensions/repositories/memory/type.go new file mode 100644 index 000000000..97dc31a44 --- /dev/null +++ b/api/credentials/extensions/repositories/memory/type.go @@ -0,0 +1,45 @@ +package memory + +import ( + "fmt" + + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + Type = "Memory" + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +func init() { + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](Type)) + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1)) +} + +// RepositorySpec describes a memory based repository interface. +type RepositorySpec struct { + runtime.ObjectVersionedType `json:",inline"` + RepositoryName string `json:"repoName"` +} + +// NewRepositorySpec creates a new memory RepositorySpec. +func NewRepositorySpec(name string) *RepositorySpec { + return &RepositorySpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(Type), + RepositoryName: name, + } +} + +func (a *RepositorySpec) GetType() string { + return Type +} + +func (a *RepositorySpec) Repository(ctx cpi.Context, creds cpi.Credentials) (cpi.Repository, error) { + r := ctx.GetAttributes().GetOrCreateAttribute(ATTR_REPOS, newRepositories) + repos, ok := r.(*Repositories) + if !ok { + return nil, fmt.Errorf("failed to assert type %T to Repositories", r) + } + return repos.GetRepository(a.RepositoryName), nil +} diff --git a/api/credentials/extensions/repositories/npm/a_usage.go b/api/credentials/extensions/repositories/npm/a_usage.go new file mode 100644 index 000000000..888a6ac21 --- /dev/null +++ b/api/credentials/extensions/repositories/npm/a_usage.go @@ -0,0 +1,18 @@ +package npm + +import ( + "ocm.software/ocm/api/utils/listformat" +) + +var usage = ` +This repository type can be used to access credentials stored in a file +following the NPM npmrc format (~/.npmrc). It take into account the +credentials helper section, also. If enabled, the described +credentials will be automatically assigned to appropriate consumer ids. +` + +var format = `The repository specification supports the following fields: +` + listformat.FormatListElements("", listformat.StringElementDescriptionList{ + "npmrcFile", "*string*: the file path to a NPM npmrc file", + "propagateConsumerIdentity", "*bool*(optional): enable consumer id propagation", +}) diff --git a/api/credentials/extensions/repositories/npm/cache.go b/api/credentials/extensions/repositories/npm/cache.go new file mode 100644 index 000000000..fe3d8fd39 --- /dev/null +++ b/api/credentials/extensions/repositories/npm/cache.go @@ -0,0 +1,33 @@ +package npm + +import ( + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/datacontext" +) + +type Cache struct { + repos map[string]*Repository +} + +func createCache(_ datacontext.Context) interface{} { + return &Cache{ + repos: map[string]*Repository{}, + } +} + +func (r *Cache) GetRepository(ctx cpi.Context, name string, prop bool) (*Repository, error) { + var ( + err error = nil + repo *Repository + ) + if name != "" { + repo = r.repos[name] + } + if repo == nil { + repo, err = NewRepository(ctx, name, prop) + if err == nil { + r.repos[name] = repo + } + } + return repo, err +} diff --git a/api/credentials/extensions/repositories/npm/config.go b/api/credentials/extensions/repositories/npm/config.go new file mode 100644 index 000000000..eb22e51f8 --- /dev/null +++ b/api/credentials/extensions/repositories/npm/config.go @@ -0,0 +1,56 @@ +package npm + +import ( + "bufio" + "os" + "strings" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/utils" +) + +type npmConfig map[string]string + +// readNpmConfigFile reads "~/.npmrc" file line by line, parse it and return the result as a npmConfig. +func readNpmConfigFile(path string) (npmConfig, string, error) { + path, err := utils.ResolvePath(path) + if err != nil { + return nil, path, errors.Wrapf(err, "cannot resolve path %q", path) + } + + // Open the file + file, err := os.Open(path) + if err != nil { + return nil, path, err + } + defer file.Close() + + // Create a new scanner and read the file line by line + scanner := bufio.NewScanner(file) + cfg := make(map[string]string) + for scanner.Scan() { + line := scanner.Text() + line, authFound := strings.CutPrefix(line, "//") + if !authFound { + // e.g. 'global=false' + continue + } + // Split the line into key and value + parts := strings.SplitN(line, ":_authToken=", 2) + if len(parts) == 2 { + if strings.HasSuffix(parts[0], "/") { + cfg[parts[0][:len(parts[0])-1]] = parts[1] + } else { + cfg[parts[0]] = parts[1] + } + } + } + + // Check for errors + if err = scanner.Err(); err != nil { + return nil, path, err + } + + return cfg, path, nil +} diff --git a/api/credentials/extensions/repositories/npm/config_test.go b/api/credentials/extensions/repositories/npm/config_test.go new file mode 100644 index 000000000..b6dacfef3 --- /dev/null +++ b/api/credentials/extensions/repositories/npm/config_test.go @@ -0,0 +1,38 @@ +package npm_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/builtin/npm/identity" + "ocm.software/ocm/api/credentials/extensions/repositories/npm" + common "ocm.software/ocm/api/utils/misc" +) + +var _ = Describe("Config deserialization Test Environment", func() { + It("read .npmrc", func() { + ctx := credentials.New() + repo := Must(npm.NewRepository(ctx, "testdata/.npmrc")) + Expect(Must(repo.LookupCredentials("registry.npmjs.org")).Properties()).To(Equal(common.Properties{identity.ATTR_TOKEN: "npm_TOKEN"})) + Expect(Must(repo.LookupCredentials("npm.registry.acme.com/api/npm")).Properties()).To(Equal(common.Properties{identity.ATTR_TOKEN: "bearer_TOKEN"})) + }) + + It("propagates credentials", func() { + ctx := credentials.New() + spec := npm.NewRepositorySpec("testdata/.npmrc") + _ = Must(ctx.RepositoryForSpec(spec)) + id := Must(identity.GetConsumerId("registry.npmjs.org", "pkg")) + creds := Must(credentials.CredentialsForConsumer(ctx, id)) + Expect(creds).NotTo(BeNil()) + Expect(creds.GetProperty(identity.ATTR_TOKEN)).To(Equal("npm_TOKEN")) + }) + + It("has description", func() { + ctx := credentials.New() + t := ctx.RepositoryTypes().GetType(npm.TypeV1) + Expect(t).NotTo(BeNil()) + Expect(t.Description()).NotTo(Equal("")) + }) +}) diff --git a/api/credentials/extensions/repositories/npm/default.go b/api/credentials/extensions/repositories/npm/default.go new file mode 100644 index 000000000..261b6e900 --- /dev/null +++ b/api/credentials/extensions/repositories/npm/default.go @@ -0,0 +1,49 @@ +package npm + +import ( + "fmt" + "os" + + "github.com/mandelsoft/filepath/pkg/filepath" + "github.com/mandelsoft/vfs/pkg/osfs" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/config" + credcfg "ocm.software/ocm/api/credentials/config" + "ocm.software/ocm/api/ocm/ocmutils/defaultconfigregistry" +) + +const ( + ConfigFileName = ".npmrc" +) + +func init() { + defaultconfigregistry.RegisterDefaultConfigHandler(DefaultConfigHandler, desc) +} + +func DefaultConfig() (string, error) { + d, err := os.UserHomeDir() + if err != nil { + return "", err + } + return filepath.Join(d, ConfigFileName), nil +} + +func DefaultConfigHandler(cfg config.Context) (string, config.Config, error) { + // use docker config as default config for ocm cli + d, err := DefaultConfig() + if err != nil { + return "", nil, nil + } + if ok, err := vfs.FileExists(osfs.OsFs, d); ok && err == nil { + ccfg := credcfg.New() + ccfg.AddRepository(NewRepositorySpec(d, true)) + return d, ccfg, nil + } + return "", nil, nil +} + +var desc = fmt.Sprintf(` +The npm configuration file at ~/%s is +read to feed in the configured credentials for NPM registries. +`, ConfigFileName) diff --git a/api/credentials/extensions/repositories/npm/provider.go b/api/credentials/extensions/repositories/npm/provider.go new file mode 100644 index 000000000..dd3cc4630 --- /dev/null +++ b/api/credentials/extensions/repositories/npm/provider.go @@ -0,0 +1,51 @@ +package npm + +import ( + npm "ocm.software/ocm/api/credentials/builtin/npm/identity" + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/utils/logging" +) + +type ConsumerProvider struct { + npmrcPath string +} + +var _ cpi.ConsumerProvider = (*ConsumerProvider)(nil) + +func (p *ConsumerProvider) Unregister(_ cpi.ProviderIdentity) { +} + +func (p *ConsumerProvider) Match(ectx cpi.EvaluationContext, req cpi.ConsumerIdentity, cur cpi.ConsumerIdentity, m cpi.IdentityMatcher) (cpi.CredentialsSource, cpi.ConsumerIdentity) { + return p.get(req, cur, m) +} + +func (p *ConsumerProvider) Get(req cpi.ConsumerIdentity) (cpi.CredentialsSource, bool) { + creds, _ := p.get(req, nil, cpi.CompleteMatch) + return creds, creds != nil +} + +func (p *ConsumerProvider) get(requested cpi.ConsumerIdentity, currentFound cpi.ConsumerIdentity, m cpi.IdentityMatcher) (cpi.CredentialsSource, cpi.ConsumerIdentity) { + all, path, err := readNpmConfigFile(p.npmrcPath) + if err != nil { + log := logging.Context().Logger(npm.REALM) + log.LogError(err, "Failed to read npmrc file", "path", path) + return nil, nil + } + + var creds cpi.CredentialsSource + + for key, value := range all { + id, err := npm.GetConsumerId("https://"+key, "") + if err != nil { + log := logging.Context().Logger(npm.REALM) + log.LogError(err, "Failed to get consumer id", "key", key, "value", value) + return nil, nil + } + if m(requested, currentFound, id) { + creds = newCredentials(value) + currentFound = id + } + } + + return creds, currentFound +} diff --git a/api/credentials/extensions/repositories/npm/repository.go b/api/credentials/extensions/repositories/npm/repository.go new file mode 100644 index 000000000..6107f1b1a --- /dev/null +++ b/api/credentials/extensions/repositories/npm/repository.go @@ -0,0 +1,88 @@ +package npm + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + + npmCredentials "ocm.software/ocm/api/credentials/builtin/npm/identity" + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/utils" + common "ocm.software/ocm/api/utils/misc" +) + +const PROVIDER = "ocm.software/credentialprovider/" + Type + +type Repository struct { + ctx cpi.Context + path string + propagate bool + npmrc npmConfig +} + +func NewRepository(ctx cpi.Context, path string, prop ...bool) (*Repository, error) { + return newRepository(ctx, path, utils.OptionalDefaultedBool(true, prop...)) +} + +func newRepository(ctx cpi.Context, path string, prop bool) (*Repository, error) { + r := &Repository{ + ctx: ctx, + path: path, + propagate: prop, + } + err := r.Read(true) + return r, err +} + +var _ cpi.Repository = &Repository{} + +func (r *Repository) ExistsCredentials(name string) (bool, error) { + err := r.Read(false) + if err != nil { + return false, err + } + return r.npmrc[name] != "", nil +} + +func (r *Repository) LookupCredentials(name string) (cpi.Credentials, error) { + exists, err := r.ExistsCredentials(name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.ErrNotFound("credentials", name, Type) + } + return newCredentials(r.npmrc[name]), nil +} + +func (r *Repository) WriteCredentials(_ string, _ cpi.Credentials) (cpi.Credentials, error) { + return nil, errors.ErrNotSupported("write", "credentials", Type) +} + +func (r *Repository) Read(force bool) error { + if !force && r.npmrc != nil { + return nil + } + + if r.path == "" { + return errors.New("npmrc path not provided") + } + cfg, path, err := readNpmConfigFile(r.path) + if err != nil { + return fmt.Errorf("failed to load npmrc: %w", err) + } + id := cpi.ProviderIdentity(PROVIDER + "/" + path) + + if r.propagate { + r.ctx.RegisterConsumerProvider(id, &ConsumerProvider{r.path}) + } + r.npmrc = cfg + return nil +} + +func newCredentials(token string) cpi.Credentials { + props := common.Properties{ + npmCredentials.ATTR_TOKEN: token, + } + return cpi.NewCredentials(props) +} diff --git a/api/credentials/extensions/repositories/npm/repository_test.go b/api/credentials/extensions/repositories/npm/repository_test.go new file mode 100644 index 000000000..6e6daf942 --- /dev/null +++ b/api/credentials/extensions/repositories/npm/repository_test.go @@ -0,0 +1,77 @@ +package npm_test + +import ( + "encoding/json" + "reflect" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/credentials" + npmCredentials "ocm.software/ocm/api/credentials/builtin/npm/identity" + "ocm.software/ocm/api/credentials/cpi" + local "ocm.software/ocm/api/credentials/extensions/repositories/npm" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtimefinalizer" +) + +var _ = Describe("NPM config - .npmrc", func() { + props := common.Properties{ + npmCredentials.ATTR_TOKEN: "npm_TOKEN", + } + + props2 := common.Properties{ + npmCredentials.ATTR_TOKEN: "bearer_TOKEN", + } + + var DefaultContext credentials.Context + + BeforeEach(func() { + DefaultContext = credentials.New() + }) + + specdata := "{\"type\":\"NPMConfig\",\"npmrcFile\":\"testdata/.npmrc\"}" + + It("serializes repo spec", func() { + spec := local.NewRepositorySpec("testdata/.npmrc") + data := Must(json.Marshal(spec)) + Expect(data).To(Equal([]byte(specdata))) + }) + + It("deserializes repo spec", func() { + spec := Must(DefaultContext.RepositorySpecForConfig([]byte(specdata), nil)) + Expect(reflect.TypeOf(spec).String()).To(Equal("*npm.RepositorySpec")) + Expect(spec.(*local.RepositorySpec).NpmrcFile).To(Equal("testdata/.npmrc")) + }) + + It("resolves repository", func() { + repo := Must(DefaultContext.RepositoryForConfig([]byte(specdata), nil)) + Expect(reflect.TypeOf(repo).String()).To(Equal("*npm.Repository")) + }) + + It("retrieves credentials", func() { + repo := Must(DefaultContext.RepositoryForConfig([]byte(specdata), nil)) + + creds := Must(repo.LookupCredentials("registry.npmjs.org")) + Expect(creds.Properties()).To(Equal(props)) + + creds = Must(repo.LookupCredentials("npm.registry.acme.com/api/npm")) + Expect(creds.Properties()).To(Equal(props2)) + }) + + It("can access the default context", func() { + ctx := credentials.New() + + r := runtimefinalizer.GetRuntimeFinalizationRecorder(ctx) + Expect(r).NotTo(BeNil()) + + Must(ctx.RepositoryForConfig([]byte(specdata), nil)) + + ci := cpi.NewConsumerIdentity(npmCredentials.CONSUMER_TYPE) + Expect(ci).NotTo(BeNil()) + credentials := Must(cpi.CredentialsForConsumer(ctx.CredentialsContext(), ci)) + Expect(credentials).NotTo(BeNil()) + Expect(credentials.Properties()).To(Equal(props)) + }) +}) diff --git a/pkg/contexts/credentials/repositories/npm/suite_test.go b/api/credentials/extensions/repositories/npm/suite_test.go similarity index 100% rename from pkg/contexts/credentials/repositories/npm/suite_test.go rename to api/credentials/extensions/repositories/npm/suite_test.go diff --git a/pkg/contexts/credentials/repositories/npm/testdata/.npmrc b/api/credentials/extensions/repositories/npm/testdata/.npmrc similarity index 100% rename from pkg/contexts/credentials/repositories/npm/testdata/.npmrc rename to api/credentials/extensions/repositories/npm/testdata/.npmrc diff --git a/api/credentials/extensions/repositories/npm/type.go b/api/credentials/extensions/repositories/npm/type.go new file mode 100644 index 000000000..91fca87d2 --- /dev/null +++ b/api/credentials/extensions/repositories/npm/type.go @@ -0,0 +1,62 @@ +package npm + +import ( + "fmt" + + "github.com/mandelsoft/goutils/generics" + + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + // Type is the type of the NPMConfig. + Type = "NPMConfig" + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +func init() { + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](Type)) + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1, cpi.WithDescription(usage), cpi.WithFormatSpec(format))) +} + +// RepositorySpec describes a docker npmrc based credential repository interface. +type RepositorySpec struct { + runtime.ObjectVersionedType `json:",inline"` + NpmrcFile string `json:"npmrcFile,omitempty"` + PropgateConsumerIdentity *bool `json:"propagateConsumerIdentity,omitempty"` +} + +// NewRepositorySpec creates a new memory RepositorySpec. +func NewRepositorySpec(path string, propagate ...bool) *RepositorySpec { + var p *bool + if path == "" { + d, err := DefaultConfig() + if err == nil { + path = d + } + } + if len(propagate) > 0 { + p = generics.Pointer(utils.OptionalDefaultedBool(true, propagate...)) + } + + return &RepositorySpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(Type), + NpmrcFile: path, + PropgateConsumerIdentity: p, + } +} + +func (rs *RepositorySpec) GetType() string { + return Type +} + +func (rs *RepositorySpec) Repository(ctx cpi.Context, _ cpi.Credentials) (cpi.Repository, error) { + r := ctx.GetAttributes().GetOrCreateAttribute(".npmrc", createCache) + cache, ok := r.(*Cache) + if !ok { + return nil, fmt.Errorf("failed to assert type %T to Cache", r) + } + return cache.GetRepository(ctx, rs.NpmrcFile, utils.AsBool(rs.PropgateConsumerIdentity, true)) +} diff --git a/api/credentials/extensions/repositories/vault/a_usage.go b/api/credentials/extensions/repositories/vault/a_usage.go new file mode 100644 index 000000000..592fca2d3 --- /dev/null +++ b/api/credentials/extensions/repositories/vault/a_usage.go @@ -0,0 +1,56 @@ +package vault + +import ( + "strings" + + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/credentials/extensions/repositories/vault/identity" + "ocm.software/ocm/api/utils/listformat" +) + +func init() { + info := cpi.DefaultContext.ConsumerIdentityMatchers().GetInfo(identity.CONSUMER_TYPE) + idx := strings.Index(info.Description, "\n") + desc := ` +This repository type can be used to access credentials stored in a HashiCorp +Vault. + +It provides access to list of secrets stored under a dedicated path in +a vault namespace. This list can either explicitly be specified, or +it is taken from the metadata of a specified secret. + +The following custom metadata attributes are evaluated: +- ` + CUSTOM_SECRETS + ` this attribute may contain a comma separated list of + vault secrets, which should be exposed by this repository instance. + The names are evaluated under the path prefix used for the repository. +- ` + CUSTOM_CONSUMERID + ` this attribute may contain a JSON encoded + consumer id , this secret should be assigned to. +- type if no special attribute is defined this attribute + indicated to use the complete custom metadata as consumer id. + +It uses the ` + identity.CONSUMER_TYPE + ` identity matcher and consumer type +to requests credentials for the access. +` + info.Description[idx:] + ` + +It requires the following credential attributes: + +` + info.CredentialAttributes + + usage = desc +} + +var usage string + +var format = ` +The repository specification supports the following fields: +` + listformat.FormatListElements("", listformat.StringElementDescriptionList{ + "serverURL", "*string* (required): the URL of the vault instance", + "namespace", "*string* (optional): the namespace used to evaluate secrets", + "mountPath", "*string* (optional): the mount path to use (default: secrets)", + "path", "*string* (optional): the path prefix used to lookup secrets", + "secrets", "*[]string* (optional): list of secrets", + "propagateConsumerIdentity", "*bool*(optional): evaluate metadata for consumer id propagation", +}) + ` +If the secrets list is empty, all secret entries found in the given path +is read. +` diff --git a/pkg/contexts/credentials/repositories/vault/auth.go b/api/credentials/extensions/repositories/vault/auth.go similarity index 94% rename from pkg/contexts/credentials/repositories/vault/auth.go rename to api/credentials/extensions/repositories/vault/auth.go index 6df7355dc..88cd6e988 100644 --- a/pkg/contexts/credentials/repositories/vault/auth.go +++ b/api/credentials/extensions/repositories/vault/auth.go @@ -10,8 +10,8 @@ import ( "github.com/mandelsoft/goutils/errors" "golang.org/x/exp/maps" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/vault/identity" + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/credentials/extensions/repositories/vault/identity" ) type AuthMethod interface { diff --git a/api/credentials/extensions/repositories/vault/cache.go b/api/credentials/extensions/repositories/vault/cache.go new file mode 100644 index 000000000..e45fb1a8f --- /dev/null +++ b/api/credentials/extensions/repositories/vault/cache.go @@ -0,0 +1,44 @@ +package vault + +import ( + "sync" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/datacontext" +) + +const ATTR_REPOS = "ocm.software/ocm/api/credentials/extensions/repositories/vault" + +type Repositories struct { + lock sync.Mutex + repos map[cpi.ProviderIdentity]*Repository +} + +func newRepositories(datacontext.Context) interface{} { + return &Repositories{ + repos: map[cpi.ProviderIdentity]*Repository{}, + } +} + +func (r *Repositories) GetRepository(ctx cpi.Context, spec *RepositorySpec) (*Repository, error) { + var repo *Repository + + if spec.ServerURL == "" { + return nil, errors.ErrInvalid("server url") + } + r.lock.Lock() + defer r.lock.Unlock() + + var err error + key := spec.GetKey() + repo = r.repos[key] + if repo == nil { + repo, err = NewRepository(ctx, spec) + if err == nil { + r.repos[key] = repo + } + } + return repo, err +} diff --git a/api/credentials/extensions/repositories/vault/identity/identity.go b/api/credentials/extensions/repositories/vault/identity/identity.go new file mode 100644 index 000000000..af8f66907 --- /dev/null +++ b/api/credentials/extensions/repositories/vault/identity/identity.go @@ -0,0 +1,124 @@ +package identity + +import ( + "net" + "net/url" + "strings" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/credentials/identity/hostpath" + "ocm.software/ocm/api/utils/listformat" +) + +const CONSUMER_TYPE = "HashiCorpVault" + +// identity properties. +const ( + ID_HOSTNAME = hostpath.ID_HOSTNAME + ID_SCHEMA = hostpath.ID_SCHEME + ID_PORT = hostpath.ID_PORT + ID_PATHPREFIX = hostpath.ID_PATHPREFIX + ID_MOUNTPATH = "mountPath" + ID_NAMESPACE = "namespace" +) + +// credential properties. +const ( + ATTR_AUTHMETH = "authmeth" + ATTR_TOKEN = cpi.ATTR_TOKEN + ATTR_ROLEID = "roleid" + ATTR_SECRETID = "secretid" +) + +const ( + AUTH_APPROLE = "approle" + AUTH_TOKEN = "token" +) + +var identityMatcher = hostpath.IdentityMatcher(CONSUMER_TYPE) + +func IdentityMatcher(request, cur, id cpi.ConsumerIdentity) bool { + if id[ID_NAMESPACE] != request[ID_NAMESPACE] { + return false + } + if id[ID_MOUNTPATH] != "" && id[ID_MOUNTPATH] != request[ID_MOUNTPATH] { + return false + } + return identityMatcher(request, cur, id) +} + +func init() { + attrs := listformat.FormatListElements("", listformat.StringElementDescriptionList{ + ATTR_AUTHMETH, "auth method", + ATTR_TOKEN, "vault token", + ATTR_ROLEID, "applrole role id", + ATTR_SECRETID, "applrole secret id", + }) + ids := listformat.FormatListElements("", listformat.StringElementDescriptionList{ + ID_HOSTNAME, "vault server host", + ID_SCHEMA, "(optional) URL scheme", + ID_PORT, "(optional) server port", + ID_NAMESPACE, "vault namespace", + ID_MOUNTPATH, "mount path", + ID_PATHPREFIX, "path prefix for secret", + }) + cpi.RegisterStandardIdentity(CONSUMER_TYPE, identityMatcher, + `HashiCorp Vault credential matcher + +This matcher matches credentials for a HashiCorp vault instance. +It uses the following identity attributes: +`+ids, + attrs+` +The only supported auth methods, so far, are token and approle. +`) +} + +func GetConsumerId(serverurl string, namespace string, mountpath string, secretpath string) (cpi.ConsumerIdentity, error) { + if serverurl == "" { + return nil, errors.Newf("server address must be given") + } + u, err := url.Parse(serverurl) + if err != nil { + return nil, errors.ErrInvalidWrap(err, "server url", serverurl) + } + + host, port, err := net.SplitHostPort(u.Host) + if err != nil { + if strings.LastIndex(host, ":") >= 0 { + return nil, errors.ErrInvalidWrap(err, "server url", serverurl) + } + host = u.Host + } + + id := cpi.ConsumerIdentity{ + cpi.ID_TYPE: CONSUMER_TYPE, + ID_HOSTNAME: host, + } + if u.Scheme != "" { + id[ID_SCHEMA] = u.Scheme + } + if port != "" { + id[ID_PORT] = port + } + if namespace != "" { + id[ID_NAMESPACE] = namespace + } + if mountpath != "" { + id[ID_MOUNTPATH] = mountpath + } + + if secretpath != "" { + id[ID_PATHPREFIX] = secretpath + } + return id, nil +} + +func GetCredentials(ctx cpi.ContextProvider, serverurl, namespace string, mountpath, secretpath string) (cpi.Credentials, error) { + id, err := GetConsumerId(serverurl, namespace, mountpath, secretpath) + if err != nil { + return nil, err + } + return cpi.CredentialsForConsumer(ctx.CredentialsContext(), id, IdentityMatcher) +} diff --git a/api/credentials/extensions/repositories/vault/logging.go b/api/credentials/extensions/repositories/vault/logging.go new file mode 100644 index 000000000..fcb526166 --- /dev/null +++ b/api/credentials/extensions/repositories/vault/logging.go @@ -0,0 +1,10 @@ +package vault + +import ( + ocmlog "ocm.software/ocm/api/utils/logging" +) + +var ( + REALM = ocmlog.DefineSubRealm("HashiCorp Vault Access", "credentials", "vault") + log = ocmlog.DynamicLogger(REALM) +) diff --git a/api/credentials/extensions/repositories/vault/options.go b/api/credentials/extensions/repositories/vault/options.go new file mode 100644 index 000000000..8059ffade --- /dev/null +++ b/api/credentials/extensions/repositories/vault/options.go @@ -0,0 +1,96 @@ +package vault + +import ( + "github.com/mandelsoft/goutils/optionutils" + "golang.org/x/exp/slices" + + "ocm.software/ocm/api/utils" +) + +type Option = optionutils.Option[*Options] + +type Options struct { + Namespace string `json:"namespace,omitempty"` + MountPath string `json:"mountPath,omitempty"` + Path string `json:"path,omitempty"` + Secrets []string `json:"secrets,omitempty"` + PropgateConsumerIdentity bool `json:"propagateConsumerIdentity,omitempty"` +} + +var _ Option = (*Options)(nil) + +func (o *Options) ApplyTo(opts *Options) { + if o.Namespace != "" { + opts.Namespace = o.Namespace + } + if o.MountPath != "" { + opts.MountPath = o.MountPath + } + if o.Path != "" { + opts.Path = o.Path + } + if o.Secrets != nil { + opts.Secrets = slices.Clone(o.Secrets) + } + opts.PropgateConsumerIdentity = o.PropgateConsumerIdentity +} + +//////////////////////////////////////////////////////////////////////////////// + +type ns string + +func (o ns) ApplyTo(opts *Options) { + opts.Namespace = string(o) +} + +func WithNamespace(s string) Option { + return ns(s) +} + +//////////////////////////////////////////////////////////////////////////////// + +type m string + +func (o m) ApplyTo(opts *Options) { + opts.MountPath = string(o) +} + +func WithMountPath(s string) Option { + return m(s) +} + +//////////////////////////////////////////////////////////////////////////////// + +type p string + +func (o p) ApplyTo(opts *Options) { + opts.Path = string(o) +} + +func WithPath(s string) Option { + return p(s) +} + +//////////////////////////////////////////////////////////////////////////////// + +type sec []string + +func (o sec) ApplyTo(opts *Options) { + opts.Secrets = append(opts.Secrets, []string(o)...) +} + +func WithSecrets(s ...string) Option { + return sec(slices.Clone(s)) +} + +//////////////////////////////////////////////////////////////////////////////// + +type pr bool + +func (o pr) ApplyTo(opts *Options) { + opts.PropgateConsumerIdentity = bool(o) +} + +func WithPropagation(b ...bool) Option { + return pr(utils.OptionalDefaultedBool(true, b...)) +} diff --git a/api/credentials/extensions/repositories/vault/provider.go b/api/credentials/extensions/repositories/vault/provider.go new file mode 100644 index 000000000..414eb8d23 --- /dev/null +++ b/api/credentials/extensions/repositories/vault/provider.go @@ -0,0 +1,320 @@ +package vault + +import ( + "context" + "encoding/json" + "net/http" + "path" + "strings" + "sync" + "time" + + "github.com/hashicorp/vault-client-go" + "github.com/mandelsoft/goutils/errors" + "golang.org/x/exp/slices" + + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/credentials/extensions/repositories/vault/identity" + "ocm.software/ocm/api/credentials/internal" + common "ocm.software/ocm/api/utils/misc" +) + +const PROVIDER = "ocm.software/credentialprovider/" + Type + +const ( + CUSTOM_SECRETS = "secrets" + CUSTOM_CONSUMERID = "consumerId" +) + +type mapping struct { + Id cpi.ConsumerIdentity + Name string +} + +type credentialCache struct { + creds cpi.CredentialsSource + credentials map[string]cpi.DirectCredentials + consumer []*mapping +} + +func newCredentialCache(creds cpi.CredentialsSource) *credentialCache { + return &credentialCache{ + creds: creds, + credentials: map[string]cpi.DirectCredentials{}, + } +} + +type ConsumerProvider struct { + lock sync.Mutex + repository *Repository + cache *credentialCache + + updated bool +} + +var ( + _ cpi.ConsumerProvider = (*ConsumerProvider)(nil) + _ cpi.ConsumerIdentityProvider = (*ConsumerProvider)(nil) +) + +func NewConsumerProvider(repo *Repository) (*ConsumerProvider, error) { + src, err := repo.ctx.GetCredentialsForConsumer(repo.id) + if err != nil { + return nil, err + } + return &ConsumerProvider{ + cache: newCredentialCache(src), + repository: repo, + }, nil +} + +func (p *ConsumerProvider) String() string { + return p.repository.id.String() +} + +func (p *ConsumerProvider) GetConsumerId(uctx ...internal.UsageContext) internal.ConsumerIdentity { + return p.repository.GetConsumerId() +} + +func (p *ConsumerProvider) GetIdentityMatcher() string { + return p.repository.GetIdentityMatcher() +} + +func (p *ConsumerProvider) update(ectx cpi.EvaluationContext) error { + if p.updated { + return nil + } + credsrc, err := cpi.GetCredentialsForConsumer(p.repository.ctx, ectx, p.repository.id, identity.IdentityMatcher) + if err != nil { + return err + } + creds, err := credsrc.Credentials(p.repository.ctx) + if err != nil { + return err + } + err = p.validateCreds(creds) + if err != nil { + return err + } + + ctx := context.Background() + + client, err := vault.New( + vault.WithAddress(p.repository.spec.ServerURL), + vault.WithRequestTimeout(30*time.Second), + ) + if err != nil { + return err + } + + // vault.WithMountPath("piper/PIPELINE-GROUP-4953/PIPELINE-25042/appRoleCredentials"), + token, err := p.getToken(ctx, client, creds) + if err != nil { + return err + } + + if err := client.SetToken(token); err != nil { + return err + } + if err := client.SetNamespace(p.repository.spec.Namespace); err != nil { + return err + } + + cache := newCredentialCache(credsrc) + + // TODO: support for pure path based access for other secret engine types + secrets := slices.Clone(p.repository.spec.Secrets) + if len(secrets) == 0 { + s, err := client.Secrets.KvV2List(ctx, p.repository.spec.Path, + vault.WithMountPath(p.repository.spec.MountPath)) + if err != nil { + p.error(err, "error listing secrets", "") + return err + } + for _, k := range s.Data.Keys { + if !strings.HasSuffix(k, "/") { + secrets = append(secrets, k) + } + } + } + for i := 0; i < len(secrets); i++ { + n := secrets[i] + creds, id, list, err := p.read(ctx, client, n) + p.error(err, "error reading vault secret", n) + if err == nil { + for _, a := range list { + if !slices.Contains(secrets, a) { + secrets = append(secrets, a) + } + } + if len(id) > 0 { + cache.consumer = append(cache.consumer, &mapping{ + Id: cpi.ConsumerIdentity(id), + Name: n, + }) + } + if len(creds) > 0 { + cache.credentials[n] = cpi.DirectCredentials(creds) + } + } + } + p.cache = cache + p.updated = true + return nil +} + +func (p *ConsumerProvider) validateCreds(creds cpi.Credentials) error { + m := creds.GetProperty(identity.ATTR_AUTHMETH) + if m == "" { + return errors.ErrRequired(identity.ATTR_AUTHMETH) + } + meth := methods.Get(m) + if meth == nil { + return errors.ErrInvalid(identity.ATTR_AUTHMETH, m) + } + return meth.Validate(creds) +} + +func (p *ConsumerProvider) getToken(ctx context.Context, client *vault.Client, creds cpi.Credentials) (string, error) { + m := creds.GetProperty(identity.ATTR_AUTHMETH) + return methods.Get(m).GetToken(ctx, client, p.repository.spec.Namespace, creds) +} + +func (p *ConsumerProvider) error(err error, msg string, secret string, keypairs ...interface{}) { + if err == nil { + return + } + f := log.Info + var v *vault.ResponseError + if errors.As(err, &v) && v.StatusCode != http.StatusNotFound { + f = log.Error + } + f(msg, append(keypairs, + "server", p.repository.spec.ServerURL, + "namespace", p.repository.spec.Namespace, + "engine", p.repository.spec.MountPath, + "path", path.Join(p.repository.spec.Path, secret), + "error", err.Error(), + )..., + ) +} + +func (p *ConsumerProvider) read(ctx context.Context, client *vault.Client, secret string) (common.Properties, common.Properties, []string, error) { + // read the secret + + secret = path.Join(p.repository.spec.Path, secret) + s, err := client.Secrets.KvV2Read(ctx, secret, + vault.WithMountPath(p.repository.spec.MountPath)) + if err != nil { + return nil, nil, nil, err + } + + var id common.Properties + var list []string + props := getProps(s.Data.Data) + + if meta, ok := s.Data.Metadata["custom_metadata"].(map[string]interface{}); ok { + sub := false + if cid := meta[CUSTOM_CONSUMERID]; cid != nil { + id = common.Properties{} + if err := json.Unmarshal([]byte(cid.(string)), &id); err != nil { + id = nil + } + sub = true + } + if cid := meta[CUSTOM_SECRETS]; cid != nil { + if s, ok := meta[CUSTOM_SECRETS].(string); ok { + for _, e := range strings.Split(s, ",") { + e = strings.TrimSpace(e) + if e != "" { + list = append(list, e) + } + } + } + sub = true + } + if _, ok := meta[cpi.ID_TYPE]; !sub && ok { + id = getProps(meta) + } + } + return props, id, list, nil +} + +func getProps(data map[string]interface{}) common.Properties { + props := common.Properties{} + for k, v := range data { + if s, ok := v.(string); ok { + props[k] = s + } + } + return props +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ConsumerProvider interface + +func (p *ConsumerProvider) Unregister(id cpi.ProviderIdentity) { +} + +func (p *ConsumerProvider) Match(ectx cpi.EvaluationContext, req cpi.ConsumerIdentity, cur cpi.ConsumerIdentity, m cpi.IdentityMatcher) (cpi.CredentialsSource, cpi.ConsumerIdentity) { + return p.get(ectx, req, cur, m) +} + +func (p *ConsumerProvider) Get(req cpi.ConsumerIdentity) (cpi.CredentialsSource, bool) { + creds, _ := p.get(nil, req, nil, cpi.CompleteMatch) + return creds, creds != nil +} + +func (p *ConsumerProvider) get(ectx cpi.EvaluationContext, req cpi.ConsumerIdentity, cur cpi.ConsumerIdentity, m cpi.IdentityMatcher) (cpi.CredentialsSource, cpi.ConsumerIdentity) { + if req.Equals(p.repository.id) { + return nil, cur + } + + p.lock.Lock() + defer p.lock.Unlock() + + err := p.update(ectx) + if err != nil { + log.Info("error accessing credentials provider", "error", err) + } + + var creds cpi.CredentialsSource + + for _, a := range p.cache.consumer { + if m(req, cur, a.Id) { + cur = a.Id + creds = p.cache.credentials[a.Name] + } + } + return creds, cur +} + +//////////////////////////////////////////////////////////////////////////////// +// lookup + +func (c *ConsumerProvider) ExistsCredentials(name string) (bool, error) { + c.lock.Lock() + defer c.lock.Unlock() + + err := c.update(nil) + if err != nil { + return false, err + } + _, ok := c.cache.credentials[name] + return ok, nil +} + +func (c *ConsumerProvider) LookupCredentials(name string) (cpi.Credentials, error) { + c.lock.Lock() + defer c.lock.Unlock() + + err := c.update(nil) + if err != nil { + return nil, err + } + src, ok := c.cache.credentials[name] + if ok { + return src, nil + } + return nil, nil +} diff --git a/pkg/contexts/credentials/repositories/vault/repo_int_test.go b/api/credentials/extensions/repositories/vault/repo_int_test.go similarity index 97% rename from pkg/contexts/credentials/repositories/vault/repo_int_test.go rename to api/credentials/extensions/repositories/vault/repo_int_test.go index bdabe36c0..62bc34d9e 100644 --- a/pkg/contexts/credentials/repositories/vault/repo_int_test.go +++ b/api/credentials/extensions/repositories/vault/repo_int_test.go @@ -16,12 +16,12 @@ import ( "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" - "github.com/open-component-model/ocm/pkg/contexts/credentials/identity/hostpath" - 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" + "ocm.software/ocm/api/credentials" + me "ocm.software/ocm/api/credentials/extensions/repositories/vault" + "ocm.software/ocm/api/credentials/extensions/repositories/vault/identity" + "ocm.software/ocm/api/credentials/identity/hostpath" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" ) type vaultMode string diff --git a/api/credentials/extensions/repositories/vault/repo_test.go b/api/credentials/extensions/repositories/vault/repo_test.go new file mode 100644 index 000000000..c1b5b07f3 --- /dev/null +++ b/api/credentials/extensions/repositories/vault/repo_test.go @@ -0,0 +1,70 @@ +package vault_test + +import ( + "encoding/json" + "fmt" + "reflect" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/credentials" + me "ocm.software/ocm/api/credentials/extensions/repositories/vault" + "ocm.software/ocm/api/credentials/extensions/repositories/vault/identity" + common "ocm.software/ocm/api/utils/misc" +) + +const ( + VAULT_ADDRESS = "127.0.0.1:8200" + VAULT_HTTP_URL = "http://" + VAULT_ADDRESS + VAULT_NAMESPACE = "test-namespace" + VAULT_MOUNT_PATH = "secret" + VAULT_PATH_REPO1 = "mysecrets/repo1" + VAULT_PATH_REPO2 = "mysecrets/repo2" +) + +var _ = Describe("", func() { + Context("serialization and deserialization", func() { + DefaultContext := credentials.New() + + specdata := fmt.Sprintf("{\"type\": %q, \"serverURL\": %q, \"namespace\": %q, \"mountPath\": %q, \"path\": %q, \"secrets\": [\"secret1\", \"secret2\", \"secret3\"], \"propagateConsumerIdentity\": true }", me.Type, "http://"+VAULT_ADDRESS, VAULT_NAMESPACE, VAULT_MOUNT_PATH, VAULT_PATH_REPO1) + spec := me.NewRepositorySpec("http://"+VAULT_ADDRESS, me.WithNamespace(VAULT_NAMESPACE), me.WithMountPath(VAULT_MOUNT_PATH), me.WithPath(VAULT_PATH_REPO1), me.WithSecrets("secret1", "secret2", "secret3"), me.WithPropagation()) + + specdata2 := fmt.Sprintf("{\"type\": %q, \"serverURL\": %q }", me.Type, "http://"+VAULT_ADDRESS) + spec2 := me.NewRepositorySpec("http://" + VAULT_ADDRESS) + + It("serializes repo spec", func() { + data := Must(json.Marshal(spec)) + Expect(data).To(YAMLEqual([]byte(specdata))) + + data = Must(json.Marshal(spec2)) + Expect(data).To(YAMLEqual([]byte(specdata2))) + }) + + It("deserializes repo spec", func() { + localspec := Must(DefaultContext.RepositorySpecForConfig([]byte(specdata), nil)) + Expect(reflect.TypeOf(localspec).String()).To(Equal("*vault.RepositorySpec")) + Expect(localspec).To(Equal(spec)) + + localspec = Must(DefaultContext.RepositorySpecForConfig([]byte(specdata2), nil)) + Expect(reflect.TypeOf(localspec).String()).To(Equal("*vault.RepositorySpec")) + Expect(localspec).To(Equal(spec2)) + }) + + It("resolves repository", func() { + // Since vault always requires credentials to be accessed, RepositoryForConfig checks whether credentials + // for a corresponding consumer exist. Thus, creating such credentials is required to test the method even + // though they are not used + consumerId := Must(identity.GetConsumerId(VAULT_HTTP_URL, VAULT_NAMESPACE, VAULT_MOUNT_PATH, VAULT_PATH_REPO1)) + creds := credentials.NewCredentials(common.Properties{ + identity.ATTR_AUTHMETH: identity.AUTH_TOKEN, + identity.ATTR_TOKEN: "token", + }) + DefaultContext.SetCredentialsForConsumer(consumerId, creds) + + repo := Must(DefaultContext.RepositoryForConfig([]byte(specdata), nil)) + Expect(repo).ToNot(BeNil()) + }) + }) +}) diff --git a/api/credentials/extensions/repositories/vault/repository.go b/api/credentials/extensions/repositories/vault/repository.go new file mode 100644 index 000000000..045ab850d --- /dev/null +++ b/api/credentials/extensions/repositories/vault/repository.go @@ -0,0 +1,66 @@ +package vault + +import ( + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/credentials/extensions/repositories/vault/identity" + "ocm.software/ocm/api/credentials/internal" +) + +type Repository struct { + ctx cpi.Context + spec *RepositorySpec + id cpi.ConsumerIdentity + provider *ConsumerProvider +} + +var ( + _ cpi.Repository = (*Repository)(nil) + _ cpi.ConsumerIdentityProvider = (*Repository)(nil) +) + +func NewRepository(ctx cpi.Context, spec *RepositorySpec) (*Repository, error) { + id, err := identity.GetConsumerId(spec.ServerURL, spec.Namespace, spec.MountPath, spec.Path) + if err != nil { + return nil, err + } + r := &Repository{ + ctx: ctx, + spec: spec, + id: id, + } + if spec.ServerURL == "" { + return nil, errors.ErrInvalid("server url") + } + r.provider, err = NewConsumerProvider(r) + if err != nil { + return nil, err + } + if spec.PropgateConsumerIdentity { + ctx.RegisterConsumerProvider(spec.GetKey(), r.provider) + } + return r, err +} + +var _ cpi.Repository = &Repository{} + +func (r *Repository) ExistsCredentials(name string) (bool, error) { + return r.provider.ExistsCredentials(name) +} + +func (r *Repository) LookupCredentials(name string) (cpi.Credentials, error) { + return r.provider.LookupCredentials(name) +} + +func (r *Repository) WriteCredentials(name string, creds cpi.Credentials) (cpi.Credentials, error) { + return nil, errors.ErrNotSupported("write", "credentials", Type) +} + +func (r *Repository) GetConsumerId(uctx ...internal.UsageContext) internal.ConsumerIdentity { + return r.id +} + +func (r *Repository) GetIdentityMatcher() string { + return identity.CONSUMER_TYPE +} diff --git a/pkg/contexts/credentials/repositories/vault/suite_test.go b/api/credentials/extensions/repositories/vault/suite_test.go similarity index 100% rename from pkg/contexts/credentials/repositories/vault/suite_test.go rename to api/credentials/extensions/repositories/vault/suite_test.go diff --git a/api/credentials/extensions/repositories/vault/type.go b/api/credentials/extensions/repositories/vault/type.go new file mode 100644 index 000000000..f0512a180 --- /dev/null +++ b/api/credentials/extensions/repositories/vault/type.go @@ -0,0 +1,82 @@ +package vault + +import ( + "encoding/json" + "fmt" + + "github.com/mandelsoft/goutils/optionutils" + "golang.org/x/exp/slices" + + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/credentials/extensions/repositories/vault/identity" + "ocm.software/ocm/api/credentials/internal" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + Type = "HashiCorpVault" + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +func init() { + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](Type)) + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1, cpi.WithDescription(usage), cpi.WithFormatSpec(format))) +} + +// RepositorySpec describes a docker config based credential repository interface. +type RepositorySpec struct { + runtime.ObjectVersionedType `json:",inline"` + ServerURL string `json:"serverURL"` + Options `json:",inline"` +} + +var _ cpi.ConsumerIdentityProvider = (*RepositorySpec)(nil) + +// NewRepositorySpec creates a new memory RepositorySpec. +func NewRepositorySpec(url string, opts ...Option) *RepositorySpec { + return &RepositorySpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(Type), + ServerURL: url, + Options: *optionutils.EvalOptions(opts...), + } +} + +func (a *RepositorySpec) GetType() string { + return Type +} + +func (a *RepositorySpec) Repository(ctx cpi.Context, creds cpi.Credentials) (cpi.Repository, error) { + r := ctx.GetAttributes().GetOrCreateAttribute(ATTR_REPOS, newRepositories) + repos, ok := r.(*Repositories) + if !ok { + return nil, fmt.Errorf("failed to assert type %T to Repositories", r) + } + spec := *a + spec.Secrets = slices.Clone(a.Secrets) + if spec.MountPath == "" { + spec.MountPath = "secret" + } + return repos.GetRepository(ctx, &spec) +} + +func (a *RepositorySpec) GetKey() cpi.ProviderIdentity { + spec := *a + spec.PropgateConsumerIdentity = false + data, err := json.Marshal(&spec) + if err == nil { + return cpi.ProviderIdentity(data) + } + return cpi.ProviderIdentity(spec.ServerURL) +} + +func (a *RepositorySpec) GetConsumerId(uctx ...internal.UsageContext) internal.ConsumerIdentity { + id, err := identity.GetConsumerId(a.ServerURL, a.Namespace, a.MountPath, a.Path) + if err != nil { + return nil + } + return id +} + +func (a *RepositorySpec) GetIdentityMatcher() string { + return identity.CONSUMER_TYPE +} diff --git a/api/credentials/gc_test.go b/api/credentials/gc_test.go new file mode 100644 index 000000000..260c1164a --- /dev/null +++ b/api/credentials/gc_test.go @@ -0,0 +1,34 @@ +package credentials_test + +import ( + "runtime" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + me "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/utils/runtimefinalizer" +) + +var _ = Describe("area test", func() { + It("can be garbage collected", func() { + ctx := me.New() + + r := runtimefinalizer.GetRuntimeFinalizationRecorder(ctx) + Expect(r).NotTo(BeNil()) + + runtime.GC() + time.Sleep(time.Second) + ctx.GetType() + Expect(r.Get()).To(BeNil()) + + ctx = nil + for i := 0; i < 100; i++ { + runtime.GC() + time.Sleep(time.Millisecond) + } + + Expect(r.Get()).To(ContainElement(ContainSubstring(me.CONTEXT_TYPE))) + }) +}) diff --git a/api/credentials/identity/hostpath/id_test.go b/api/credentials/identity/hostpath/id_test.go new file mode 100644 index 000000000..eca34ddc1 --- /dev/null +++ b/api/credentials/identity/hostpath/id_test.go @@ -0,0 +1,204 @@ +package hostpath_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/credentials/identity/hostpath" +) + +func IdentityMatcher(pattern, cur, id cpi.ConsumerIdentity) bool { + return hostpath.IdentityMatcher("OCIRegistry")(pattern, cur, id) +} + +var _ = Describe("ctf management", func() { + Context("with path", func() { + pat := credentials.ConsumerIdentity{ + hostpath.ID_HOSTNAME: "host", + hostpath.ID_PATHPREFIX: "a/b", + hostpath.ID_PORT: "4711", + hostpath.ID_SCHEME: "scheme://", + } + + It("complete", func() { + id := credentials.ConsumerIdentity{ + hostpath.ID_HOSTNAME: "host", + hostpath.ID_PATHPREFIX: "a/b", + hostpath.ID_PORT: "4711", + hostpath.ID_SCHEME: "scheme://", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) + Expect(IdentityMatcher(pat, id, id)).To(BeFalse()) + }) + + It("path prefix", func() { + id := credentials.ConsumerIdentity{ + hostpath.ID_HOSTNAME: "host", + hostpath.ID_PATHPREFIX: "a", + hostpath.ID_PORT: "4711", + hostpath.ID_SCHEME: "scheme://", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) + Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + It("different prefix", func() { + id := credentials.ConsumerIdentity{ + hostpath.ID_HOSTNAME: "host", + hostpath.ID_PATHPREFIX: "b", + hostpath.ID_PORT: "4711", + hostpath.ID_SCHEME: "scheme://", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) + Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + It("longer prefix", func() { + id := credentials.ConsumerIdentity{ + hostpath.ID_HOSTNAME: "host", + hostpath.ID_PATHPREFIX: "a/b/c", + hostpath.ID_PORT: "4711", + hostpath.ID_SCHEME: "scheme://", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) + Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + It("missing path", func() { + id := credentials.ConsumerIdentity{ + hostpath.ID_HOSTNAME: "host", + hostpath.ID_PORT: "4711", + hostpath.ID_SCHEME: "scheme://", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) + Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + It("missing port", func() { + id := credentials.ConsumerIdentity{ + hostpath.ID_HOSTNAME: "host", + hostpath.ID_PATHPREFIX: "a/b", + hostpath.ID_SCHEME: "scheme://", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) + Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) + + Expect(IdentityMatcher(id, nil, pat)).To(BeTrue()) // accept additional port as fallback + Expect(IdentityMatcher(id, id, pat)).To(BeFalse()) // but not to replace more general match + }) + It("different port", func() { + id := credentials.ConsumerIdentity{ + hostpath.ID_HOSTNAME: "host", + hostpath.ID_PATHPREFIX: "a/b", + hostpath.ID_PORT: "0815", + hostpath.ID_SCHEME: "scheme://", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) + Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + + It("different host", func() { + id := credentials.ConsumerIdentity{ + hostpath.ID_HOSTNAME: "other", + hostpath.ID_PATHPREFIX: "a/b", + hostpath.ID_PORT: "4711", + hostpath.ID_SCHEME: "scheme://", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) + Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + It("no host", func() { + id := credentials.ConsumerIdentity{ + hostpath.ID_PATHPREFIX: "a/b", + hostpath.ID_PORT: "4711", + hostpath.ID_SCHEME: "scheme://", + } + Expect(IdentityMatcher(id, nil, pat)).To(BeTrue()) + Expect(IdentityMatcher(pat, id, id)).To(BeFalse()) + Expect(IdentityMatcher(pat, id, pat)).To(BeTrue()) + }) + + It("different scheme", func() { + id := credentials.ConsumerIdentity{ + hostpath.ID_HOSTNAME: "host", + hostpath.ID_PATHPREFIX: "a/b", + hostpath.ID_PORT: "4711", + hostpath.ID_SCHEME: "otherscheme://", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) + Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + It("no scheme", func() { + id := credentials.ConsumerIdentity{ + hostpath.ID_HOSTNAME: "host", + hostpath.ID_PATHPREFIX: "a/b", + hostpath.ID_PORT: "4711", + } + Expect(IdentityMatcher(id, nil, pat)).To(BeTrue()) + Expect(IdentityMatcher(pat, id, id)).To(BeFalse()) + Expect(IdentityMatcher(pat, id, pat)).To(BeTrue()) + }) + }) + + Context("without path", func() { + pat := credentials.ConsumerIdentity{ + hostpath.ID_HOSTNAME: "host", + hostpath.ID_PORT: "4711", + hostpath.ID_SCHEME: "scheme://", + } + + It("complete", func() { + id := credentials.ConsumerIdentity{ + hostpath.ID_HOSTNAME: "host", + hostpath.ID_PORT: "4711", + hostpath.ID_SCHEME: "scheme://", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) + Expect(IdentityMatcher(pat, id, id)).To(BeFalse()) + }) + + It("different prefix", func() { + id := credentials.ConsumerIdentity{ + hostpath.ID_HOSTNAME: "host", + hostpath.ID_PORT: "4711", + hostpath.ID_PATHPREFIX: "b", + hostpath.ID_SCHEME: "scheme://", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) + Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + It("missing port", func() { + id := credentials.ConsumerIdentity{ + hostpath.ID_HOSTNAME: "host", + hostpath.ID_SCHEME: "scheme://", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) + Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + It("different port", func() { + id := credentials.ConsumerIdentity{ + hostpath.ID_HOSTNAME: "host", + hostpath.ID_PORT: "0815", + hostpath.ID_SCHEME: "scheme://", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) + Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + + It("different scheme", func() { + id := credentials.ConsumerIdentity{ + hostpath.ID_HOSTNAME: "host", + hostpath.ID_PORT: "4711", + hostpath.ID_SCHEME: "otherscheme://", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) + Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + It("no scheme", func() { + id := credentials.ConsumerIdentity{ + hostpath.ID_HOSTNAME: "host", + hostpath.ID_PORT: "4711", + } + Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) + Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) + }) + }) +}) diff --git a/api/credentials/identity/hostpath/identity.go b/api/credentials/identity/hostpath/identity.go new file mode 100644 index 000000000..4a0369ced --- /dev/null +++ b/api/credentials/identity/hostpath/identity.go @@ -0,0 +1,148 @@ +package hostpath + +import ( + "net/url" + "strings" + + "ocm.software/ocm/api/credentials/cpi" +) + +// IDENTITY_TYPE is the identity of this matcher. +const IDENTITY_TYPE = "hostpath" + +// ID_TYPE is the type of the consumer. +const ID_TYPE = cpi.ID_TYPE + +// ID_HOSTNAME is a hostname. +const ID_HOSTNAME = "hostname" + +// ID_PORT is a port. +const ID_PORT = "port" + +// ID_PATHPREFIX is the path prefix below the host. +const ID_PATHPREFIX = "pathprefix" + +// ID_SCHEME is the scheme prefix. +const ID_SCHEME = "scheme" + +func init() { + cpi.RegisterStandardIdentityMatcher(IDENTITY_TYPE, Matcher, `Host and path based credential matcher + +This matcher works on the following properties: + +- *`+ID_TYPE+`* (required if set in pattern): the identity type +- *`+ID_HOSTNAME+`* (required if set in pattern): the hostname of a server +- *`+ID_SCHEME+`* (optional): the URL scheme of a server +- *`+ID_PORT+`* (optional): the port of a server +- *`+ID_PATHPREFIX+`* (optional): a path prefix to match. The + element with the most matching path components is selected (separator is /). +`) +} + +var Matcher = IdentityMatcher("") + +func Match(identityType string, request, cur, id cpi.ConsumerIdentity) (match bool, better bool) { + if request[ID_TYPE] != "" && request[ID_TYPE] != id[ID_TYPE] { + return false, false + } + + if identityType != "" && request[ID_TYPE] != "" && identityType != request[ID_TYPE] { + return false, false + } + + if request[ID_HOSTNAME] != "" && id[ID_HOSTNAME] != "" && request[ID_HOSTNAME] != id[ID_HOSTNAME] { + return false, false + } + + if request[ID_PORT] != "" { + if id[ID_PORT] != "" && id[ID_PORT] != request[ID_PORT] { + return false, false + } + } + + if request[ID_SCHEME] != "" { + if id[ID_SCHEME] != "" && id[ID_SCHEME] != request[ID_SCHEME] { + return false, false + } + } + + if request[ID_PATHPREFIX] != "" { + if id[ID_PATHPREFIX] != "" { + if len(id[ID_PATHPREFIX]) > len(request[ID_PATHPREFIX]) { + return false, false + } + pcomps := strings.Split(request[ID_PATHPREFIX], "/") + icomps := strings.Split(id[ID_PATHPREFIX], "/") + if len(icomps) > len(pcomps) { + return false, false + } + for i := range icomps { + if pcomps[i] != icomps[i] { + return false, false + } + } + } + } else { + if id[ID_PATHPREFIX] != "" { + return false, false + } + } + + // ok now it basically matches, check against current match + if len(cur) == 0 { + return true, true + } + + if cur[ID_HOSTNAME] == "" && id[ID_HOSTNAME] != "" { + return true, true + } + if cur[ID_PORT] == "" && (id[ID_PORT] != "" && request[ID_PORT] != "") { + return true, true + } + if cur[ID_SCHEME] == "" && (id[ID_SCHEME] != "" && request[ID_SCHEME] != "") { + return true, true + } + + if len(cur[ID_PATHPREFIX]) < len(id[ID_PATHPREFIX]) { + return true, true + } + return true, false +} + +func IdentityMatcher(identityType string) cpi.IdentityMatcher { + return func(request, cur, id cpi.ConsumerIdentity) bool { + _, better := Match(identityType, request, cur, id) + return better + } +} + +func GetConsumerIdentity(typ, _url string) cpi.ConsumerIdentity { + u, err := url.Parse(_url) + if err != nil { + return nil + } + + id := cpi.NewConsumerIdentity(typ) + if u.Host != "" { + parts := strings.Split(u.Host, ":") + if len(parts) > 1 { + id[ID_PORT] = parts[1] + } else { + switch u.Scheme { + case "https": + id[ID_PORT] = "443" + case "http": + id[ID_PORT] = "80" + } + } + id[ID_HOSTNAME] = parts[0] + } + if u.Scheme != "" { + id[ID_SCHEME] = u.Scheme + } + path := strings.Trim(u.Path, "/") + if path != "" { + id[ID_PATHPREFIX] = path + } + return id +} diff --git a/pkg/contexts/credentials/identity/hostpath/suite_test.go b/api/credentials/identity/hostpath/suite_test.go similarity index 100% rename from pkg/contexts/credentials/identity/hostpath/suite_test.go rename to api/credentials/identity/hostpath/suite_test.go diff --git a/api/credentials/init.go b/api/credentials/init.go new file mode 100644 index 000000000..6c8a0fb88 --- /dev/null +++ b/api/credentials/init.go @@ -0,0 +1,7 @@ +package credentials + +import ( + _ "ocm.software/ocm/api/credentials/builtin" + _ "ocm.software/ocm/api/credentials/config" + _ "ocm.software/ocm/api/credentials/extensions/repositories" +) diff --git a/api/credentials/interface.go b/api/credentials/interface.go new file mode 100644 index 000000000..b0aa3f204 --- /dev/null +++ b/api/credentials/interface.go @@ -0,0 +1,134 @@ +package credentials + +import ( + "context" + + "ocm.software/ocm/api/credentials/extensions/repositories/directcreds" + "ocm.software/ocm/api/credentials/internal" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + KIND_CREDENTIALS = internal.KIND_CREDENTIALS + KIND_CONSUMER = internal.KIND_CONSUMER + KIND_REPOSITORY = internal.KIND_REPOSITORY +) + +const CONTEXT_TYPE = internal.CONTEXT_TYPE + +const AliasRepositoryType = internal.AliasRepositoryType + +type ( + Context = internal.Context + ContextProvider = internal.ContextProvider + RepositoryTypeScheme = internal.RepositoryTypeScheme + Repository = internal.Repository + Credentials = internal.Credentials + CredentialsSource = internal.CredentialsSource + CredentialsChain = internal.CredentialsChain + CredentialsSpec = internal.CredentialsSpec + RepositorySpec = internal.RepositorySpec +) + +type ( + ConsumerIdentity = internal.ConsumerIdentity + ConsumerIdentityProvider = internal.ConsumerIdentityProvider + ProviderIdentity = internal.ProviderIdentity + UsageContext = internal.UsageContext + StringUsageContext = internal.StringUsageContext + IdentityMatcher = internal.IdentityMatcher + IdentityMatcherInfo = internal.IdentityMatcherInfo + IdentityMatcherInfos = internal.IdentityMatcherInfos + IdentityMatcherRegistry = internal.IdentityMatcherRegistry +) + +type ( + GenericRepositorySpec = internal.GenericRepositorySpec + GenericCredentialsSpec = internal.GenericCredentialsSpec + DirectCredentials = internal.DirectCredentials +) + +func DefaultContext() internal.Context { + return internal.DefaultContext +} + +func FromContext(ctx context.Context) Context { + return internal.FromContext(ctx) +} + +func FromProvider(p ContextProvider) Context { + return internal.FromProvider(p) +} + +func DefinedForContext(ctx context.Context) (Context, bool) { + return internal.DefinedForContext(ctx) +} + +func NewCredentialsSpec(name string, repospec RepositorySpec) CredentialsSpec { + return internal.NewCredentialsSpec(name, repospec) +} + +func NewGenericCredentialsSpec(name string, repospec *GenericRepositorySpec) CredentialsSpec { + return internal.NewGenericCredentialsSpec(name, repospec) +} + +func NewGenericRepositorySpec(data []byte, unmarshaler runtime.Unmarshaler) (RepositorySpec, error) { + return internal.NewGenericRepositorySpec(data, unmarshaler) +} + +func NewCredentials(props common.Properties) Credentials { + return internal.NewCredentials(props) +} + +func CredentialsFromList(props ...string) Credentials { + creds := DirectCredentials{} + for i := 1; i < len(props); i += 2 { + creds[props[i-1]] = props[i] + } + return creds +} + +func CredentialsSpecFromList(props ...string) CredentialsSpec { + creds := DirectCredentials{} + for i := 1; i < len(props); i += 2 { + creds[props[i-1]] = props[i] + } + return directcreds.NewCredentials(creds.Properties()) +} + +func ToGenericCredentialsSpec(spec CredentialsSpec) (*GenericCredentialsSpec, error) { + return internal.ToGenericCredentialsSpec(spec) +} + +func ToGenericRepositorySpec(spec RepositorySpec) (*GenericRepositorySpec, error) { + return internal.ToGenericRepositorySpec(spec) +} + +func ErrUnknownCredentials(name string) error { + return internal.ErrUnknownCredentials(name) +} + +// CredentialsForConsumer determine effective credentials for a consumer. +// If no credentials are configured no error and nil is returned. +// It evaluates a found credentials source for the consumer to determine the +// final credential properties. +func CredentialsForConsumer(ctx ContextProvider, id ConsumerIdentity, matchers ...IdentityMatcher) (Credentials, error) { + return internal.CredentialsForConsumer(ctx, id, false, matchers...) +} + +// RequiredCredentialsForConsumer like CredentialsForConsumer, but an errors is returned +// if no credentials are found. +func RequiredCredentialsForConsumer(ctx ContextProvider, id ConsumerIdentity, matchers ...IdentityMatcher) (Credentials, error) { + return internal.CredentialsForConsumer(ctx, id, true, matchers...) +} + +var ( + CompleteMatch = internal.CompleteMatch + NoMatch = internal.NoMatch + PartialMatch = internal.PartialMatch +) + +func NewConsumerIdentity(typ string, attrs ...string) ConsumerIdentity { + return internal.NewConsumerIdentity(typ, attrs...) +} diff --git a/api/credentials/internal/builder.go b/api/credentials/internal/builder.go new file mode 100644 index 000000000..a5940f228 --- /dev/null +++ b/api/credentials/internal/builder.go @@ -0,0 +1,79 @@ +package internal + +import ( + "context" + + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/datacontext" +) + +type Builder struct { + ctx context.Context + config config.Context + reposcheme RepositoryTypeScheme + matchers IdentityMatcherRegistry +} + +func (b *Builder) getContext() context.Context { + if b.ctx == nil { + return context.Background() + } + return b.ctx +} + +func (b Builder) WithContext(ctx context.Context) Builder { + b.ctx = ctx + return b +} + +func (b Builder) WithConfig(ctx config.Context) Builder { + b.config = ctx + return b +} + +func (b Builder) WithRepositoyTypeScheme(scheme RepositoryTypeScheme) Builder { + b.reposcheme = scheme + return b +} + +func (b Builder) WithStandardConumerMatchers(matchers IdentityMatcherRegistry) Builder { + b.matchers = matchers + return b +} + +func (b Builder) Bound() (Context, context.Context) { + c := b.New() + return c, context.WithValue(b.getContext(), key, c) +} + +func (b Builder) New(m ...datacontext.BuilderMode) Context { + mode := datacontext.Mode(m...) + ctx := b.getContext() + + if b.config == nil { + var ok bool + b.config, ok = config.DefinedForContext(ctx) + if !ok && mode != datacontext.MODE_SHARED { + b.config = config.New(mode) + } + } + if b.reposcheme == nil { + switch mode { + case datacontext.MODE_INITIAL: + b.reposcheme = NewRepositoryTypeScheme(nil) + case datacontext.MODE_CONFIGURED: + b.reposcheme = NewRepositoryTypeScheme(nil) + b.reposcheme.AddKnownTypes(DefaultRepositoryTypeScheme) + case datacontext.MODE_EXTENDED: + b.reposcheme = NewRepositoryTypeScheme(nil, DefaultRepositoryTypeScheme) + case datacontext.MODE_DEFAULTED: + fallthrough + case datacontext.MODE_SHARED: + b.reposcheme = DefaultRepositoryTypeScheme + } + } + if b.matchers == nil { + b.matchers = StandardIdentityMatchers + } + return datacontext.SetupContext(mode, newContext(b.config, b.reposcheme, b.matchers, b.config)) +} diff --git a/api/credentials/internal/builder_test.go b/api/credentials/internal/builder_test.go new file mode 100644 index 000000000..3f37c6b39 --- /dev/null +++ b/api/credentials/internal/builder_test.go @@ -0,0 +1,58 @@ +package internal_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/config" + local "ocm.software/ocm/api/credentials/internal" + "ocm.software/ocm/api/datacontext" +) + +var _ = Describe("builder test", func() { + It("creates local", func() { + ctx := local.Builder{}.New(datacontext.MODE_SHARED) + + Expect(ctx.AttributesContext()).To(BeIdenticalTo(datacontext.DefaultContext)) + Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) + Expect(ctx.RepositoryTypes()).To(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) + + Expect(ctx.ConfigContext().GetId()).To(BeIdenticalTo(config.DefaultContext().GetId())) + }) + + It("creates defaulted", func() { + ctx := local.Builder{}.New(datacontext.MODE_DEFAULTED) + + Expect(ctx.AttributesContext()).NotTo(BeIdenticalTo(datacontext.DefaultContext)) + Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) + Expect(ctx.RepositoryTypes()).To(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) + + Expect(ctx.ConfigContext().GetId()).NotTo(BeIdenticalTo(config.DefaultContext().GetType())) + Expect(ctx.ConfigContext().ConfigTypes()).To(BeIdenticalTo(config.DefaultContext().ConfigTypes())) + }) + + It("creates configured", func() { + ctx := local.Builder{}.New(datacontext.MODE_CONFIGURED) + + Expect(ctx.AttributesContext()).NotTo(BeIdenticalTo(datacontext.DefaultContext)) + Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) + Expect(ctx.RepositoryTypes()).NotTo(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) + Expect(ctx.RepositoryTypes().KnownTypeNames()).To(Equal(local.DefaultRepositoryTypeScheme.KnownTypeNames())) + + Expect(ctx.ConfigContext().GetId()).NotTo(BeIdenticalTo(config.DefaultContext().GetId())) + Expect(ctx.ConfigContext().ConfigTypes()).NotTo(BeIdenticalTo(config.DefaultContext().ConfigTypes())) + Expect(ctx.ConfigContext().ConfigTypes().KnownTypeNames()).To(Equal(config.DefaultContext().ConfigTypes().KnownTypeNames())) + }) + + It("creates iniial", func() { + ctx := local.Builder{}.New(datacontext.MODE_INITIAL) + + Expect(ctx.AttributesContext()).NotTo(BeIdenticalTo(datacontext.DefaultContext)) + Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) + Expect(ctx.RepositoryTypes()).NotTo(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) + Expect(len(ctx.RepositoryTypes().KnownTypeNames())).To(Equal(0)) + + Expect(ctx.ConfigContext()).NotTo(BeIdenticalTo(config.DefaultContext())) + Expect(len(ctx.ConfigContext().ConfigTypes().KnownTypeNames())).To(Equal(0)) + }) +}) diff --git a/pkg/contexts/credentials/internal/builtin.go b/api/credentials/internal/builtin.go similarity index 100% rename from pkg/contexts/credentials/internal/builtin.go rename to api/credentials/internal/builtin.go diff --git a/pkg/contexts/credentials/internal/const.go b/api/credentials/internal/const.go similarity index 100% rename from pkg/contexts/credentials/internal/const.go rename to api/credentials/internal/const.go diff --git a/pkg/contexts/credentials/internal/consumers.go b/api/credentials/internal/consumers.go similarity index 100% rename from pkg/contexts/credentials/internal/consumers.go rename to api/credentials/internal/consumers.go diff --git a/api/credentials/internal/context.go b/api/credentials/internal/context.go new file mode 100644 index 000000000..333b9e1d1 --- /dev/null +++ b/api/credentials/internal/context.go @@ -0,0 +1,322 @@ +package internal + +import ( + "context" + "fmt" + "reflect" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/generics" + "github.com/mandelsoft/goutils/maputils" + "golang.org/x/exp/maps" + + "ocm.software/ocm/api/config" + cfgcpi "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/api/utils/runtimefinalizer" +) + +// CONTEXT_TYPE is the global type for a credential context. +const CONTEXT_TYPE = "credentials" + datacontext.OCM_CONTEXT_SUFFIX + +// ProviderIdentity is used to uniquely identify a provider +// for a configured consumer id. If non-empty it +// must start with a DNSname identifying the origin of the +// provider followed by a slash and a local arbitrary identity. +type ProviderIdentity = runtimefinalizer.ObjectIdentity + +type ContextProvider interface { + CredentialsContext() Context +} + +type ConsumerProvider interface { + Unregister(id ProviderIdentity) + Get(id ConsumerIdentity) (CredentialsSource, bool) + Match(ectx EvaluationContext, id ConsumerIdentity, cur ConsumerIdentity, matcher IdentityMatcher) (CredentialsSource, ConsumerIdentity) +} + +type EvaluationContext *evaluationContext + +type evaluationContext struct { + data map[reflect.Type]interface{} +} + +func (e evaluationContext) String() string { + return fmt.Sprintf("%v", maputils.Transform(e.data, func(k reflect.Type, v interface{}) (string, string) { + return k.Name(), fmt.Sprintf("%v", v) + })) +} + +func GetEvaluationContextFor[T any](ectx EvaluationContext) T { + var _nil T + if ectx.data == nil { + return _nil + } + return generics.Cast[T](ectx.data[generics.TypeOf[T]()]) +} + +func SetEvaluationContextFor(ectx EvaluationContext, e any) EvaluationContext { + if ectx.data == nil { + ectx.data = map[reflect.Type]interface{}{} + } + n := &evaluationContext{maps.Clone(ectx.data)} + n.data[reflect.TypeOf(e)] = e + return n +} + +type Context interface { + datacontext.Context + ContextProvider + config.ContextProvider + + AttributesContext() datacontext.AttributesContext + RepositoryTypes() RepositoryTypeScheme + + RepositorySpecForConfig(data []byte, unmarshaler runtime.Unmarshaler) (RepositorySpec, error) + + RepositoryForSpec(spec RepositorySpec, creds ...CredentialsSource) (Repository, error) + RepositoryForConfig(data []byte, unmarshaler runtime.Unmarshaler, creds ...CredentialsSource) (Repository, error) + + CredentialsForSpec(spec CredentialsSpec, creds ...CredentialsSource) (Credentials, error) + CredentialsForConfig(data []byte, unmarshaler runtime.Unmarshaler, cred ...CredentialsSource) (Credentials, error) + + RegisterConsumerProvider(id ProviderIdentity, provider ConsumerProvider) + UnregisterConsumerProvider(id ProviderIdentity) + + GetCredentialsForConsumer(ConsumerIdentity, ...IdentityMatcher) (CredentialsSource, error) + getCredentialsForConsumer(EvaluationContext, ConsumerIdentity, ...IdentityMatcher) (CredentialsSource, error) + SetCredentialsForConsumer(identity ConsumerIdentity, creds CredentialsSource) + SetCredentialsForConsumerWithProvider(pid ProviderIdentity, identity ConsumerIdentity, creds CredentialsSource) + + SetAlias(name string, spec RepositorySpec, creds ...CredentialsSource) error + + ConsumerIdentityMatchers() IdentityMatcherRegistry +} + +var key = reflect.TypeOf(_context{}) + +// DefaultContext is the default context initialized by init functions. +var DefaultContext = Builder{}.New(datacontext.MODE_SHARED) + +// FromContext returns the Context to use for context.Context. +// This is either an explicit context or the default context. +func FromContext(ctx context.Context) Context { + c, _ := datacontext.ForContextByKey(ctx, key, DefaultContext) + return c.(Context) +} + +func FromProvider(p ContextProvider) Context { + if p == nil { + return nil + } + return p.CredentialsContext() +} + +func DefinedForContext(ctx context.Context) (Context, bool) { + c, ok := datacontext.ForContextByKey(ctx, key, DefaultContext) + if c != nil { + return c.(Context), ok + } + return nil, ok +} + +type _InternalContext = datacontext.InternalContext + +type _context struct { + _InternalContext + + sharedattributes datacontext.AttributesContext + updater cfgcpi.Updater + knownRepositoryTypes RepositoryTypeScheme + consumerIdentityMatchers IdentityMatcherRegistry + consumerProviders *consumerProviderRegistry +} + +var ( + _ Context = (*_context)(nil) + _ datacontext.ViewCreator[Context] = (*_context)(nil) +) + +// gcWrapper is used as garbage collectable +// wrapper for a context implementation +// to establish a runtime finalizer. +type gcWrapper struct { + datacontext.GCWrapper + *_context +} + +func newView(c *_context, ref ...bool) Context { + if utils.Optional(ref...) { + return datacontext.FinalizedContext[gcWrapper](c) + } + return c +} + +func (w *gcWrapper) SetContext(c *_context) { + w._context = c +} + +func newContext(configctx config.Context, reposcheme RepositoryTypeScheme, consumerMatchers IdentityMatcherRegistry, delegates datacontext.Delegates) Context { + c := &_context{ + sharedattributes: datacontext.PersistentContextRef(configctx.AttributesContext()), + knownRepositoryTypes: reposcheme, + consumerIdentityMatchers: consumerMatchers, + consumerProviders: newConsumerProviderRegistry(), + } + c._InternalContext = datacontext.NewContextBase(c, CONTEXT_TYPE, key, configctx.GetAttributes(), delegates) + c.updater = cfgcpi.NewUpdaterForFactory(datacontext.PersistentContextRef(configctx), c.CredentialsContext) + return newView(c, true) +} + +func (c *_context) CreateView() Context { + return newView(c, true) +} + +func (c *_context) CredentialsContext() Context { + return newView(c) +} + +func (c *_context) Update() error { + return c.updater.Update() +} + +func (c *_context) GetType() string { + return CONTEXT_TYPE +} + +func (c *_context) AttributesContext() datacontext.AttributesContext { + return c.sharedattributes +} + +func (c *_context) ConfigContext() config.Context { + return c.updater.GetContext() +} + +func (c *_context) RepositoryTypes() RepositoryTypeScheme { + return c.knownRepositoryTypes +} + +func (c *_context) RepositorySpecForConfig(data []byte, unmarshaler runtime.Unmarshaler) (RepositorySpec, error) { + return c.knownRepositoryTypes.Decode(data, unmarshaler) +} + +func (c *_context) RepositoryForSpec(spec RepositorySpec, creds ...CredentialsSource) (Repository, error) { + out := newView(c) + cred, err := CredentialsChain(creds).Credentials(out) + if err != nil { + return nil, err + } + c.Update() + return spec.Repository(out, cred) +} + +func (c *_context) RepositoryForConfig(data []byte, unmarshaler runtime.Unmarshaler, creds ...CredentialsSource) (Repository, error) { + spec, err := c.knownRepositoryTypes.Decode(data, unmarshaler) + if err != nil { + return nil, err + } + return c.RepositoryForSpec(spec, creds...) +} + +func (c *_context) CredentialsForSpec(spec CredentialsSpec, creds ...CredentialsSource) (Credentials, error) { + out := newView(c) + repospec := spec.GetRepositorySpec(out) + repo, err := c.RepositoryForSpec(repospec, creds...) + if err != nil { + return nil, err + } + return repo.LookupCredentials(spec.GetCredentialsName()) +} + +func (c *_context) CredentialsForConfig(data []byte, unmarshaler runtime.Unmarshaler, creds ...CredentialsSource) (Credentials, error) { + spec := &GenericCredentialsSpec{} + err := unmarshaler.Unmarshal(data, spec) + if err != nil { + return nil, err + } + return c.CredentialsForSpec(spec, creds...) +} + +var emptyIdentity = ConsumerIdentity{} + +func (c *_context) GetCredentialsForConsumer(identity ConsumerIdentity, matchers ...IdentityMatcher) (CredentialsSource, error) { + return c.getCredentialsForConsumer(nil, identity, matchers...) +} + +func (c *_context) getCredentialsForConsumer(ectx EvaluationContext, identity ConsumerIdentity, matchers ...IdentityMatcher) (CredentialsSource, error) { + err := c.Update() + if err != nil { + return nil, err + } + + if ectx == nil { + ectx = &evaluationContext{} + } + m := c.defaultMatcher(identity, matchers...) + var credsrc CredentialsSource + if m == nil { + credsrc, _ = c.consumerProviders.Get(identity) + } else { + credsrc, _ = c.consumerProviders.Match(ectx, identity, nil, m) + } + if credsrc == nil { + credsrc, _ = c.consumerProviders.Get(emptyIdentity) + } + if credsrc == nil { + return nil, ErrUnknownConsumer(identity.String()) + } + return credsrc, nil +} + +func (c *_context) defaultMatcher(id ConsumerIdentity, matchers ...IdentityMatcher) IdentityMatcher { + def := c.consumerIdentityMatchers.Get(id.Type()) + if def == nil { + def = PartialMatch + } + return mergeMatcher(def, andMatcher, matchers) +} + +func (c *_context) SetCredentialsForConsumer(identity ConsumerIdentity, creds CredentialsSource) { + c.Update() + c.consumerProviders.Set(identity, "", creds) +} + +func (c *_context) SetCredentialsForConsumerWithProvider(pid ProviderIdentity, identity ConsumerIdentity, creds CredentialsSource) { + c.Update() + c.consumerProviders.Set(identity, pid, creds) +} + +func (c *_context) ConsumerIdentityMatchers() IdentityMatcherRegistry { + return c.consumerIdentityMatchers +} + +func (c *_context) SetAlias(name string, spec RepositorySpec, creds ...CredentialsSource) error { + c.Update() + t := c.knownRepositoryTypes.GetType(AliasRepositoryType) + if t == nil { + return errors.ErrNotSupported("aliases") + } + if a, ok := t.(AliasRegistry); ok { + return a.SetAlias(c, name, spec, CredentialsChain(creds)) + } + return errors.ErrNotImplemented("interface", "AliasRegistry", reflect.TypeOf(t).String()) +} + +func (c *_context) RegisterConsumerProvider(id ProviderIdentity, provider ConsumerProvider) { + c.consumerProviders.Register(id, provider) +} + +func (c *_context) UnregisterConsumerProvider(id ProviderIdentity) { + c.consumerProviders.Unregister(id) +} + +/////////////////////////////////////// + +func GetCredentialsForConsumer(ctx Context, ectx EvaluationContext, identity ConsumerIdentity, matchers ...IdentityMatcher) (CredentialsSource, error) { + if ectx == nil { + ectx = &evaluationContext{} + } + return ctx.getCredentialsForConsumer(ectx, identity, matchers...) +} diff --git a/api/credentials/internal/cred_test.go b/api/credentials/internal/cred_test.go new file mode 100644 index 000000000..bf1233986 --- /dev/null +++ b/api/credentials/internal/cred_test.go @@ -0,0 +1,87 @@ +package internal_test + +import ( + "encoding/json" + "reflect" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/extensions/repositories/memory" + "ocm.software/ocm/api/credentials/internal" + common "ocm.software/ocm/api/utils/misc" +) + +var DefaultContext = credentials.New() + +var _ = Describe("generic credentials", func() { + props := common.Properties{ + "user": "USER", + "password": "PASSWORD", + } + credmemdata := "{\"credentialsName\":\"cred\",\"repoName\":\"test\",\"type\":\"Memory\"}" + memdata := "{\"repoName\":\"test\",\"type\":\"Memory\"}" + + _ = props + + It("de/serializes credentials spec", func() { + repospec := memory.NewRepositorySpec("test") + credspec := credentials.NewCredentialsSpec("cred", repospec) + + data, err := json.Marshal(credspec) + Expect(err).To(Succeed()) + Expect(data).To(Equal([]byte(credmemdata))) + + credspec = &internal.DefaultCredentialsSpec{} + err = json.Unmarshal(data, credspec) + Expect(err).To(Succeed()) + s := credspec.(*internal.DefaultCredentialsSpec) + Expect(reflect.TypeOf(s.RepositorySpec).String()).To(Equal("*memory.RepositorySpec")) + Expect(s.CredentialsName).To(Equal("cred")) + Expect(s.RepositorySpec.(*memory.RepositorySpec).RepositoryName).To(Equal("test")) + }) + + It("de/serializes generic credentials spec", func() { + credspec := &internal.GenericCredentialsSpec{} + + err := json.Unmarshal([]byte(credmemdata), credspec) + Expect(err).To(Succeed()) + + data, err := json.Marshal(credspec) + Expect(err).To(Succeed()) + Expect(data).To(Equal([]byte(credmemdata))) + }) + + It("de/serializes generic repository spec", func() { + credspec := &internal.GenericRepositorySpec{} + + err := json.Unmarshal([]byte(memdata), credspec) + Expect(err).To(Succeed()) + + data, err := json.Marshal(credspec) + Expect(err).To(Succeed()) + Expect(data).To(Equal([]byte(memdata))) + }) + + It("converts credentials spec to generic ones", func() { + repospec := memory.NewRepositorySpec("test") + credspec := credentials.NewCredentialsSpec("cred", repospec) + data, err := json.Marshal(credspec) + Expect(err).To(Succeed()) + + gen, err := credentials.ToGenericCredentialsSpec(credspec) + Expect(err).To(Succeed()) + + Expect(reflect.TypeOf(gen).String()).To(Equal("*internal.GenericCredentialsSpec")) + Expect(reflect.TypeOf(gen.RepositorySpec).String()).To(Equal("*internal.GenericRepositorySpec")) + + gen2, err := credentials.ToGenericCredentialsSpec(gen) + Expect(err).To(Succeed()) + Expect(gen2).To(BeIdenticalTo(gen)) + + data3, err := json.Marshal(gen) + Expect(err).To(Succeed()) + Expect(data3).To(Equal(data)) + }) +}) diff --git a/pkg/contexts/credentials/internal/credentials.go b/api/credentials/internal/credentials.go similarity index 100% rename from pkg/contexts/credentials/internal/credentials.go rename to api/credentials/internal/credentials.go diff --git a/pkg/contexts/credentials/internal/credentialsspec.go b/api/credentials/internal/credentialsspec.go similarity index 98% rename from pkg/contexts/credentials/internal/credentialsspec.go rename to api/credentials/internal/credentialsspec.go index f423c5001..e0e97c39c 100644 --- a/pkg/contexts/credentials/internal/credentialsspec.go +++ b/api/credentials/internal/credentialsspec.go @@ -6,7 +6,7 @@ import ( "github.com/modern-go/reflect2" - "github.com/open-component-model/ocm/pkg/runtime" + "ocm.software/ocm/api/utils/runtime" ) // CredentialsSpec describes a dedicated credential provided by some repository. diff --git a/pkg/contexts/credentials/internal/errors.go b/api/credentials/internal/errors.go similarity index 100% rename from pkg/contexts/credentials/internal/errors.go rename to api/credentials/internal/errors.go diff --git a/pkg/contexts/credentials/internal/identity.go b/api/credentials/internal/identity.go similarity index 100% rename from pkg/contexts/credentials/internal/identity.go rename to api/credentials/internal/identity.go diff --git a/api/credentials/internal/logging.go b/api/credentials/internal/logging.go new file mode 100644 index 000000000..d270b99e0 --- /dev/null +++ b/api/credentials/internal/logging.go @@ -0,0 +1,10 @@ +package internal + +import ( + ocmlog "ocm.software/ocm/api/utils/logging" +) + +var ( + REALM = ocmlog.DefineSubRealm("Credentials", "credentials") + log = ocmlog.DynamicLogger(REALM) +) diff --git a/api/credentials/internal/repository.go b/api/credentials/internal/repository.go new file mode 100644 index 000000000..337d2f4bc --- /dev/null +++ b/api/credentials/internal/repository.go @@ -0,0 +1,63 @@ +package internal + +import ( + "github.com/mandelsoft/goutils/set" + + common "ocm.software/ocm/api/utils/misc" +) + +type Repository interface { + ExistsCredentials(name string) (bool, error) + LookupCredentials(name string) (Credentials, error) + WriteCredentials(name string, creds Credentials) (Credentials, error) +} + +type Credentials interface { + CredentialsSource + ExistsProperty(name string) bool + GetProperty(name string) string + PropertyNames() set.Set[string] + Properties() common.Properties +} + +type DirectCredentials common.Properties + +var _ Credentials = (*DirectCredentials)(nil) + +func NewCredentials(props common.Properties) DirectCredentials { + if props == nil { + props = common.Properties{} + } else { + props = props.Copy() + } + return DirectCredentials(props) +} + +func (c DirectCredentials) ExistsProperty(name string) bool { + _, ok := c[name] + return ok +} + +func (c DirectCredentials) GetProperty(name string) string { + return c[name] +} + +func (c DirectCredentials) PropertyNames() set.Set[string] { + return common.Properties(c).Names() +} + +func (c DirectCredentials) Properties() common.Properties { + return common.Properties(c).Copy() +} + +func (c DirectCredentials) Credentials(Context, ...CredentialsSource) (Credentials, error) { + return c, nil +} + +func (c DirectCredentials) Copy() DirectCredentials { + return DirectCredentials(common.Properties(c).Copy()) +} + +func (c DirectCredentials) String() string { + return common.Properties(c).String() +} diff --git a/api/credentials/internal/repotypes.go b/api/credentials/internal/repotypes.go new file mode 100644 index 000000000..dafe999f3 --- /dev/null +++ b/api/credentials/internal/repotypes.go @@ -0,0 +1,138 @@ +package internal + +import ( + "encoding/json" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/generics" + "github.com/modern-go/reflect2" + + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/api/utils/runtime/descriptivetype" +) + +type RepositoryType interface { + descriptivetype.TypedObjectType[RepositorySpec] +} + +type RepositorySpec interface { + runtime.VersionedTypedObject + + Repository(Context, Credentials) (Repository, error) +} + +type ( + RepositorySpecDecoder = runtime.TypedObjectDecoder[RepositorySpec] + RepositoryTypeProvider = runtime.KnownTypesProvider[RepositorySpec, RepositoryType] +) + +type RepositoryTypeScheme interface { + descriptivetype.TypeScheme[RepositorySpec, RepositoryType] +} + +type _Scheme = descriptivetype.TypeScheme[RepositorySpec, RepositoryType] + +type repositoryTypeScheme struct { + _Scheme +} + +func NewRepositoryTypeScheme(defaultDecoder RepositorySpecDecoder, base ...RepositoryTypeScheme) RepositoryTypeScheme { + scheme := descriptivetype.MustNewDefaultTypeScheme[RepositorySpec, RepositoryType, RepositoryTypeScheme]("Credential provider", nil, &UnknownRepositorySpec{}, true, defaultDecoder, utils.Optional(base...)) + return &repositoryTypeScheme{scheme} +} + +func NewStrictRepositoryTypeScheme(base ...RepositoryTypeScheme) runtime.VersionedTypeRegistry[RepositorySpec, RepositoryType] { + scheme := descriptivetype.MustNewDefaultTypeScheme[RepositorySpec, RepositoryType, RepositoryTypeScheme]("Credential provider", nil, nil, false, nil, utils.Optional(base...)) + return &repositoryTypeScheme{scheme} +} + +func (t *repositoryTypeScheme) KnownTypes() runtime.KnownTypes[RepositorySpec, RepositoryType] { + return t._Scheme.KnownTypes() +} + +// DefaultRepositoryTypeScheme contains all globally known access serializer. +var DefaultRepositoryTypeScheme = NewRepositoryTypeScheme(nil) + +func RegisterRepositoryType(atype RepositoryType) { + DefaultRepositoryTypeScheme.Register(atype) +} + +func CreateRepositorySpec(t runtime.TypedObject) (RepositorySpec, error) { + return DefaultRepositoryTypeScheme.Convert(t) +} + +//////////////////////////////////////////////////////////////////////////////// + +type UnknownRepositorySpec struct { + runtime.UnstructuredVersionedTypedObject `json:",inline"` +} + +var ( + _ RepositorySpec = &UnknownRepositorySpec{} + _ runtime.Unknown = &UnknownRepositorySpec{} +) + +func (r *UnknownRepositorySpec) IsUnknown() bool { + return true +} + +func (r *UnknownRepositorySpec) Repository(Context, Credentials) (Repository, error) { + return nil, errors.ErrUnknown("repository type", r.GetType()) +} + +//////////////////////////////////////////////////////////////////////////////// + +type GenericRepositorySpec struct { + runtime.UnstructuredVersionedTypedObject `json:",inline"` +} + +var _ RepositorySpec = &GenericRepositorySpec{} + +func ToGenericRepositorySpec(spec RepositorySpec) (*GenericRepositorySpec, error) { + if reflect2.IsNil(spec) { + return nil, nil + } + if g, ok := spec.(*GenericRepositorySpec); ok { + return g, nil + } + data, err := json.Marshal(spec) + if err != nil { + return nil, err + } + return newGenericRepositorySpec(data, runtime.DefaultJSONEncoding) +} + +func NewGenericRepositorySpec(data []byte, unmarshaler runtime.Unmarshaler) (RepositorySpec, error) { + return generics.CastPointerR[RepositorySpec](newGenericRepositorySpec(data, unmarshaler)) +} + +func newGenericRepositorySpec(data []byte, unmarshaler runtime.Unmarshaler) (*GenericRepositorySpec, error) { + unstr := &runtime.UnstructuredVersionedTypedObject{} + if unmarshaler == nil { + unmarshaler = runtime.DefaultYAMLEncoding + } + err := unmarshaler.Unmarshal(data, unstr) + if err != nil { + return nil, err + } + return &GenericRepositorySpec{*unstr}, nil +} + +func (s *GenericRepositorySpec) Evaluate(ctx Context) (RepositorySpec, error) { + raw, err := s.GetRaw() + if err != nil { + return nil, err + } + return ctx.RepositoryTypes().Decode(raw, runtime.DefaultJSONEncoding) +} + +func (s *GenericRepositorySpec) Repository(ctx Context, creds Credentials) (Repository, error) { + spec, err := s.Evaluate(ctx) + if err != nil { + return nil, err + } + return spec.Repository(ctx, creds) +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/pkg/contexts/credentials/internal/suite_test.go b/api/credentials/internal/suite_test.go similarity index 100% rename from pkg/contexts/credentials/internal/suite_test.go rename to api/credentials/internal/suite_test.go diff --git a/pkg/contexts/credentials/internal/utils.go b/api/credentials/internal/utils.go similarity index 100% rename from pkg/contexts/credentials/internal/utils.go rename to api/credentials/internal/utils.go diff --git a/pkg/contexts/credentials/suite_test.go b/api/credentials/suite_test.go similarity index 100% rename from pkg/contexts/credentials/suite_test.go rename to api/credentials/suite_test.go diff --git a/pkg/contexts/credentials/usage.go b/api/credentials/usage.go similarity index 100% rename from pkg/contexts/credentials/usage.go rename to api/credentials/usage.go diff --git a/api/credentials/utils.go b/api/credentials/utils.go new file mode 100644 index 000000000..ba5295ef6 --- /dev/null +++ b/api/credentials/utils.go @@ -0,0 +1,139 @@ +package credentials + +import ( + "crypto/tls" + "crypto/x509" + "strings" + + "github.com/mandelsoft/goutils/errors" + "github.com/texttheater/golang-levenshtein/levenshtein" + + "ocm.software/ocm/api/credentials/internal" + "ocm.software/ocm/api/datacontext/attrs/rootcertsattr" + "ocm.software/ocm/api/utils" +) + +func GetProvidedConsumerId(obj interface{}, uctx ...UsageContext) ConsumerIdentity { + return utils.UnwrappingCall(obj, func(provider ConsumerIdentityProvider) ConsumerIdentity { + return provider.GetConsumerId(uctx...) + }) +} + +func GetProvidedIdentityMatcher(obj interface{}) string { + return utils.UnwrappingCall(obj, func(provider ConsumerIdentityProvider) string { + return provider.GetIdentityMatcher() + }) +} + +func CredentialsFor(ctx ContextProvider, obj interface{}, uctx ...UsageContext) (Credentials, error) { + id := GetProvidedConsumerId(obj, uctx...) + if id == nil { + return nil, errors.ErrNotSupported(KIND_CONSUMER) + } + return CredentialsForConsumer(ctx, id) +} + +func GetRootCAs(ctx ContextProvider, creds Credentials) (*x509.CertPool, error) { + var rootCAs *x509.CertPool + var err error + + if creds != nil { + c := creds.GetProperty(internal.ATTR_CERTIFICATE_AUTHORITY) + if c != "" { + rootCAs = x509.NewCertPool() + rootCAs.AppendCertsFromPEM([]byte(c)) + } + } + if rootCAs == nil { + if ctx != nil { + rootCAs = rootcertsattr.Get(ctx.CredentialsContext()).GetRootCertPool(true) + } else { + rootCAs, err = x509.SystemCertPool() + if err != nil { + return nil, err + } + } + } + return rootCAs, nil +} + +func GetClientCerts(ctx ContextProvider, creds Credentials) ([]tls.Certificate, error) { + if creds != nil { + cert := creds.GetProperty(internal.ATTR_CERTIFICATE) + priv := creds.GetProperty(internal.ATTR_PRIVATE_KEY) + if cert == "" && priv == "" { + return nil, nil + } + if cert == "" || priv == "" { + return nil, errors.New("both, private key and certificate are required for tls client authentication") + } + if cert != "" && priv != "" { + tlsCert, err := tls.X509KeyPair([]byte(cert), []byte(priv)) + if err != nil { + return nil, err + } + return []tls.Certificate{tlsCert}, nil + } + } + return nil, nil +} + +func GuessConsumerType(ctxp ContextProvider, spec string) string { + matchers := ctxp.CredentialsContext().ConsumerIdentityMatchers() + lspec := strings.ToLower(spec) + + if matchers.Get(spec) == nil { + fix := "" + for _, i := range matchers.List() { + idx := strings.Index(i.Type, ".") + if idx > 0 && i.Type[:idx] == spec { + fix = i.Type + break + } + } + if fix == "" { + for _, i := range matchers.List() { + if strings.ToLower(i.Type) == lspec { + fix = i.Type + break + } + } + } + if fix == "" { + for _, i := range matchers.List() { + idx := strings.Index(i.Type, ".") + if idx > 0 && strings.ToLower(i.Type[:idx]) == lspec { + fix = i.Type + break + } + } + } + if fix == "" { + min := -1 + for _, i := range matchers.List() { + idx := strings.Index(i.Type, ".") + if idx > 0 { + d := levenshtein.DistanceForStrings([]rune(lspec), []rune(strings.ToLower(i.Type[:idx])), levenshtein.DefaultOptions) + if d < 5 && fix == "" || min > d { + fix = i.Type + min = d + } + } + } + } + if fix == "" { + min := -1 + for _, i := range matchers.List() { + d := levenshtein.DistanceForStrings([]rune(lspec), []rune(strings.ToLower(i.Type)), levenshtein.DefaultOptions) + if d < 5 && fix == "" || min > d { + fix = i.Type + min = d + } + } + } + if fix != "" { + return fix + } + } + return spec +} diff --git a/api/datacontext/action/api/action_test.go b/api/datacontext/action/api/action_test.go new file mode 100644 index 000000000..a3ba0d119 --- /dev/null +++ b/api/datacontext/action/api/action_test.go @@ -0,0 +1,108 @@ +package api_test + +import ( + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/identity/hostpath" + "ocm.software/ocm/api/datacontext/action/api" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" +) + +const NAME = "testAction" + +const CONSUMER_TYPE = "TestAction" + +const ID_HOSTNAME = hostpath.ID_HOSTNAME + +func RegisterAction(registry api.ActionTypeRegistry) { + registry.RegisterAction(NAME, "test action", "nothing special", []string{ID_HOSTNAME}) + + registry.RegisterActionType(api.NewActionType[*ActionSpec, *ActionResult](NAME, "v1")) + registry.RegisterActionType(api.NewActionTypeByConverter[*ActionSpec, *ActionSpecV2, *ActionResult, *ActionResultV2](NAME, "v2", convertSpecV2{}, convertResultV2{})) +} + +func NewActionSpec(field string) api.ActionSpec { + return &ActionSpec{ + ObjectVersionedType: runtime.NewVersionedObjectType(runtime.TypeName(NAME, "v1")), + Field: field, + } +} + +func NewActionResult(msg string) api.ActionResult { + return &ActionResult{ + ObjectVersionedType: runtime.NewVersionedObjectType(runtime.TypeName(NAME, "v1")), + Message: msg, + } +} + +//////////////////////////////////////////////////////////////////////////////// +// internal version + +type ActionSpec struct { + runtime.ObjectVersionedType `json:",inline"` + Field string `json:"field"` +} + +func (a *ActionSpec) Selector() api.Selector { + return api.Selector(a.Field) +} + +func (a *ActionSpec) GetConsumerAttributes() common.Properties { + return common.Properties(credentials.NewConsumerIdentity(CONSUMER_TYPE, + ID_HOSTNAME, a.Field, + )) +} + +type ActionResult struct { + runtime.ObjectVersionedType `json:",inline"` + Message string `json:"message"` +} + +func (r ActionResult) GetMessage() string { + return r.Message +} + +//////////////////////////////////////////////////////////////////////////////// +// external version + +type ActionSpecV2 struct { + runtime.ObjectVersionedType `json:",inline"` + Data string `json:"data"` +} + +type ActionResultV2 struct { + runtime.ObjectVersionedType `json:",inline"` + Data string `json:"data"` +} + +type convertSpecV2 struct{} + +func (c convertSpecV2) ConvertFrom(in *ActionSpec) (*ActionSpecV2, error) { + return &ActionSpecV2{ + ObjectVersionedType: runtime.NewVersionedObjectType(runtime.TypeName(NAME, "v2")), + Data: in.Field, + }, nil +} + +func (c convertSpecV2) ConvertTo(in *ActionSpecV2) (*ActionSpec, error) { + return &ActionSpec{ + ObjectVersionedType: runtime.NewVersionedObjectType(runtime.TypeName(NAME, "v2")), + Field: in.Data, + }, nil +} + +type convertResultV2 struct{} + +func (c convertResultV2) ConvertFrom(in *ActionResult) (*ActionResultV2, error) { + return &ActionResultV2{ + ObjectVersionedType: runtime.NewVersionedObjectType(runtime.TypeName(NAME, "v2")), + Data: in.Message, + }, nil +} + +func (c convertResultV2) ConvertTo(in *ActionResultV2) (*ActionResult, error) { + return &ActionResult{ + ObjectVersionedType: runtime.NewVersionedObjectType(runtime.TypeName(NAME, "v2")), + Message: in.Data, + }, nil +} diff --git a/api/datacontext/action/api/interface.go b/api/datacontext/action/api/interface.go new file mode 100644 index 000000000..545ae7985 --- /dev/null +++ b/api/datacontext/action/api/interface.go @@ -0,0 +1,84 @@ +package api + +import ( + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" +) + +type Action interface { + Name() string + Description() string + Usage() string + ConsumerAttributes() []string + GetVersion(string) ActionType + SupportedVersions() []string +} + +//////////////////////////////////////////////////////////////////////////////// +// Action Specification + +type Selector string + +func (s Selector) ApplyActionHandlerOptionTo(opts *Options) { + opts.Selectors = append(opts.Selectors, s) +} + +type ActionSpec interface { + runtime.VersionedTypedObject + SetVersion(string) + Selector() Selector + GetConsumerAttributes() common.Properties +} + +type ActionSpecType runtime.VersionedTypedObjectType[ActionSpec] + +//////////////////////////////////////////////////////////////////////////////// +// Action Result + +type ActionResult interface { + runtime.VersionedTypedObject + SetVersion(string) + SetType(string) + GetMessage() string +} + +// CommonResult is the minimal action result. +type CommonResult struct { + runtime.ObjectVersionedType `json:",inline"` + Message string `json:"message,omitempty"` +} + +func (r *CommonResult) GetMessage() string { + return r.Message +} + +func (r *CommonResult) SetType(typ string) { + r.Type = typ +} + +//////////////////////////////////////////////////////////////////////////////// +// Action Type + +type ActionResultType runtime.VersionedTypedObjectType[ActionResult] + +type ActionType interface { + runtime.VersionedTypedObject + SpecificationType() ActionSpecType + ResultType() ActionResultType +} + +//////////////////////////////////////////////////////////////////////////////// +// Options Type + +type Option interface { + ApplyActionHandlerOptionTo(*Options) +} + +type Options struct { + Action string + Selectors []Selector + Priority int + Versions []string +} + +var _ Option = (*Options)(nil) diff --git a/pkg/contexts/datacontext/action/api/options.go b/api/datacontext/action/api/options.go similarity index 100% rename from pkg/contexts/datacontext/action/api/options.go rename to api/datacontext/action/api/options.go diff --git a/api/datacontext/action/api/registry.go b/api/datacontext/action/api/registry.go new file mode 100644 index 000000000..bf2ceee6d --- /dev/null +++ b/api/datacontext/action/api/registry.go @@ -0,0 +1,235 @@ +package api + +import ( + "fmt" + "sync" + + "github.com/mandelsoft/goutils/errors" + "golang.org/x/exp/slices" + + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + KIND_ACTION = "action" + KIND_ACTIONTYPE = "action type" +) + +type ActionTypeRegistry interface { + RegisterAction(name string, description string, usage string, attrs []string) error + RegisterActionType(typ ActionType) error + + DecodeActionSpec(data []byte, unmarshaler runtime.Unmarshaler) (ActionSpec, error) + EncodeActionSpec(spec ActionSpec, marshaler runtime.Marshaler) ([]byte, error) + + DecodeActionResult(data []byte, unmarshaler runtime.Unmarshaler) (ActionResult, error) + EncodeActionResult(spec ActionResult, marshaler runtime.Marshaler) ([]byte, error) + + GetAction(name string) Action + SupportedActionVersions(name string) []string + + Copy() ActionTypeRegistry +} + +type action struct { + lock sync.Mutex + name string + shortdesc string + usage string + attributes []string + types map[string]ActionType +} + +var _ Action = (*action)(nil) + +func (a *action) Name() string { + return a.name +} + +func (a *action) Description() string { + return a.shortdesc +} + +func (a *action) Usage() string { + return a.usage +} + +func (a *action) ConsumerAttributes() []string { + return a.attributes +} + +func (a *action) GetVersion(v string) ActionType { + a.lock.Lock() + defer a.lock.Unlock() + return a.types[v] +} + +func (a *action) SupportedVersions() []string { + return utils.StringMapKeys(a.types) +} + +type actionRegistry struct { + lock sync.Mutex + actions map[string]*action + actionspecs runtime.TypeScheme[ActionSpec, ActionSpecType] + resultspecs runtime.TypeScheme[ActionResult, ActionResultType] +} + +func NewActionTypeRegistry() ActionTypeRegistry { + return &actionRegistry{ + actions: map[string]*action{}, + actionspecs: runtime.NewTypeScheme[ActionSpec, ActionSpecType](), + resultspecs: runtime.NewTypeScheme[ActionResult, ActionResultType](), + } +} + +func (r *actionRegistry) Copy() ActionTypeRegistry { + r.lock.Lock() + defer r.lock.Unlock() + + actions := map[string]*action{} + + for k, v := range r.actions { + v.lock.Lock() + a := action{ + name: v.name, + shortdesc: v.shortdesc, + usage: v.usage, + attributes: v.attributes, + } + a.types = map[string]ActionType{} + for _, t := range v.types { + a.types[t.GetType()] = t + } + v.lock.Unlock() + actions[k] = &a + } + actionspecs := runtime.NewTypeScheme[ActionSpec, ActionSpecType]() + actionspecs.AddKnownTypes(r.actionspecs) + resultspecs := runtime.NewTypeScheme[ActionResult, ActionResultType]() + resultspecs.AddKnownTypes(r.resultspecs) + return &actionRegistry{ + actions: actions, + actionspecs: actionspecs, + resultspecs: resultspecs, + } +} + +func (r *actionRegistry) RegisterAction(name string, description string, usage string, attrs []string) error { + r.lock.Lock() + defer r.lock.Unlock() + + ai := r.actions[name] + if ai != nil { + return errors.ErrAlreadyExists(KIND_ACTION, name) + } + + ai = &action{ + name: name, + shortdesc: description, + usage: usage, + attributes: slices.Clone(attrs), + types: map[string]ActionType{}, + } + r.actions[name] = ai + return nil +} + +func (r *actionRegistry) RegisterActionType(typ ActionType) error { + r.lock.Lock() + defer r.lock.Unlock() + + k := typ.GetKind() + + ai := r.actions[k] + if ai == nil { + return errors.ErrNotFound(KIND_ACTION, k) + } + + if typ.SpecificationType().GetType() != typ.ResultType().GetType() { + return errors.ErrInvalidWrap(fmt.Errorf("version mismatch: request[%s]!=result[%s]", typ.SpecificationType().GetType(), typ.ResultType().GetType()), KIND_ACTIONTYPE, k) + } + if typ.SpecificationType().GetKind() != k { + return errors.ErrInvalidWrap(fmt.Errorf("kind mismatch in types: %s", typ.SpecificationType().GetType()), KIND_ACTIONTYPE, k) + } + ai.types[typ.GetVersion()] = typ + ai.lock.Lock() + defer ai.lock.Unlock() + r.actionspecs.Register(typ.SpecificationType()) + r.resultspecs.Register(typ.ResultType()) + return nil +} + +func (r *actionRegistry) GetAction(name string) Action { + r.lock.Lock() + defer r.lock.Unlock() + + return r.actions[name] +} + +func (r *actionRegistry) DecodeActionSpec(data []byte, unmarshaler runtime.Unmarshaler) (ActionSpec, error) { + return r.actionspecs.Decode(data, unmarshaler) +} + +func (r *actionRegistry) DecodeActionResult(data []byte, unmarshaler runtime.Unmarshaler) (ActionResult, error) { + return r.resultspecs.Decode(data, unmarshaler) +} + +func (r *actionRegistry) EncodeActionSpec(spec ActionSpec, marshaler runtime.Marshaler) ([]byte, error) { + return r.actionspecs.Encode(spec, marshaler) +} + +func (r *actionRegistry) EncodeActionResult(spec ActionResult, marshaler runtime.Marshaler) ([]byte, error) { + return r.resultspecs.Encode(spec, marshaler) +} + +func (r *actionRegistry) SupportedActionVersions(name string) []string { + r.lock.Lock() + defer r.lock.Unlock() + a := r.actions[name] + if a == nil { + return nil + } + return a.SupportedVersions() +} + +//////////////////////////////////////////////////////////////////////////////// + +var registry = NewActionTypeRegistry() + +func DefaultRegistry() ActionTypeRegistry { + return registry +} + +func RegisterAction(name string, description string, usage string, attrs []string) error { + return registry.RegisterAction(name, description, usage, attrs) +} + +func RegisterType(typ ActionType) error { + return registry.RegisterActionType(typ) +} + +func GetAction(name string) Action { + return registry.GetAction(name) +} + +func DecodeActionSpec(data []byte, unmarshaler runtime.Unmarshaler) (ActionSpec, error) { + return registry.DecodeActionSpec(data, unmarshaler) +} + +func EncodeActionSpec(spec ActionSpec, marshaler runtime.Marshaler) ([]byte, error) { + return registry.EncodeActionSpec(spec, marshaler) +} + +func DecodeActionResult(data []byte, unmarshaler runtime.Unmarshaler) (ActionResult, error) { + return registry.DecodeActionResult(data, unmarshaler) +} + +func EncodeActionResult(spec ActionResult, marshaler runtime.Marshaler) ([]byte, error) { + return registry.EncodeActionResult(spec, marshaler) +} + +func SupportedActionVersions(name string) []string { + return registry.SupportedActionVersions(name) +} diff --git a/api/datacontext/action/api/registry_test.go b/api/datacontext/action/api/registry_test.go new file mode 100644 index 000000000..e9c694670 --- /dev/null +++ b/api/datacontext/action/api/registry_test.go @@ -0,0 +1,100 @@ +package api_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/datacontext/action/api" + "ocm.software/ocm/api/datacontext/action/handlers" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" +) + +type Handler struct { + spec api.ActionSpec + creds common.Properties +} + +func (h *Handler) Handle(spec api.ActionSpec, creds common.Properties) (api.ActionResult, error) { + h.spec = spec + h.creds = creds + r := NewActionResult(spec.(*ActionSpec).Field) + r.SetVersion("v2") + return r, nil +} + +var _ handlers.ActionHandler = &Handler{} + +var _ = Describe("action registry", func() { + var registry api.ActionTypeRegistry + + BeforeEach(func() { + registry = api.NewActionTypeRegistry() + RegisterAction(registry) + }) + + Context("plain", func() { + It("registers", func() { + Expect(registry.SupportedActionVersions(NAME)).To(Equal([]string{"v1", "v2"})) + }) + + It("encoding spec v1", func() { + spec := NewActionSpec("acme.com") + spec.SetVersion("v1") + data := Must(registry.EncodeActionSpec(spec, runtime.DefaultJSONEncoding)) + Expect(string(data)).To(Equal(`{"type":"testAction/v1","field":"acme.com"}`)) + d := Must(registry.DecodeActionSpec(data, runtime.DefaultJSONEncoding)) + Expect(d).To(Equal(spec)) + }) + It("encoding spec v2", func() { + spec := NewActionSpec("acme.com") + spec.SetVersion("v2") + data := Must(registry.EncodeActionSpec(spec, runtime.DefaultJSONEncoding)) + Expect(string(data)).To(Equal(`{"type":"testAction/v2","data":"acme.com"}`)) + d := Must(registry.DecodeActionSpec(data, runtime.DefaultJSONEncoding)) + Expect(d).To(Equal(spec)) + }) + + It("encoding result v1", func() { + spec := NewActionResult("successful") + spec.SetVersion("v1") + data := Must(registry.EncodeActionResult(spec, runtime.DefaultJSONEncoding)) + Expect(string(data)).To(Equal(`{"type":"testAction/v1","message":"successful"}`)) + d := Must(registry.DecodeActionResult(data, runtime.DefaultJSONEncoding)) + Expect(d).To(Equal(spec)) + }) + It("encoding result v2", func() { + spec := NewActionResult("successful") + spec.SetVersion("v2") + data := Must(registry.EncodeActionResult(spec, runtime.DefaultJSONEncoding)) + Expect(string(data)).To(Equal(`{"type":"testAction/v2","data":"successful"}`)) + d := Must(registry.DecodeActionResult(data, runtime.DefaultJSONEncoding)) + Expect(d).To(Equal(spec)) + }) + }) + + Context("data context", func() { + var ctx datacontext.Context + var handler *Handler + + BeforeEach(func() { + handler = &Handler{} + ctx = datacontext.NewWithActions(nil, handlers.NewRegistry(registry)) + Expect(ctx.GetActions().GetActionTypes()).To(BeIdenticalTo(registry)) + MustBeSuccessful(ctx.GetActions().Register(handler, handlers.ForAction(NAME), handlers.WithVersions("v2"), handlers.ForSelectors(".*\\.com"))) + }) + + It("", func() { + spec := NewActionSpec("acme.com") + creds := common.Properties{"alice": "bob"} + r := Must(ctx.GetActions().Execute(spec, creds)) + Expect(handler.spec).To(Equal(spec)) + Expect(handler.creds).To(Equal(creds)) + rs := NewActionResult("acme.com") + rs.SetVersion("v2") + Expect(r).To(Equal(rs)) + }) + }) +}) diff --git a/pkg/contexts/datacontext/action/api/suite_test.go b/api/datacontext/action/api/suite_test.go similarity index 100% rename from pkg/contexts/datacontext/action/api/suite_test.go rename to api/datacontext/action/api/suite_test.go diff --git a/api/datacontext/action/api/utils.go b/api/datacontext/action/api/utils.go new file mode 100644 index 000000000..52535f92f --- /dev/null +++ b/api/datacontext/action/api/utils.go @@ -0,0 +1,38 @@ +package api + +import ( + "ocm.software/ocm/api/utils/runtime" +) + +type _Object = runtime.ObjectVersionedTypedObject + +type actionType struct { + _Object + spectype ActionSpecType + restype ActionResultType +} + +var _ ActionType = (*actionType)(nil) + +func NewActionType[IS ActionSpec, IR ActionResult](kind, version string) ActionType { + return NewActionTypeByConverter[IS, IS, IR, IR](kind, version, runtime.IdentityConverter[IS]{}, runtime.IdentityConverter[IR]{}) +} + +func NewActionTypeByConverter[IS ActionSpec, VS runtime.TypedObject, IR ActionResult, VR runtime.TypedObject](kind, version string, specconv runtime.Converter[IS, VS], resconv runtime.Converter[IR, VR]) ActionType { + name := runtime.TypeName(kind, version) + st := runtime.NewVersionedTypedObjectTypeByConverter[ActionSpec, IS, VS](name, specconv) + rt := runtime.NewVersionedTypedObjectTypeByConverter[ActionResult, IR, VR](name, resconv) + return &actionType{ + _Object: runtime.NewVersionedTypedObject(kind, version), + spectype: st, + restype: rt, + } +} + +func (a *actionType) SpecificationType() ActionSpecType { + return a.spectype +} + +func (a *actionType) ResultType() ActionResultType { + return a.restype +} diff --git a/api/datacontext/action/handlers/options.go b/api/datacontext/action/handlers/options.go new file mode 100644 index 000000000..9928a0d0c --- /dev/null +++ b/api/datacontext/action/handlers/options.go @@ -0,0 +1,72 @@ +package handlers + +import ( + "golang.org/x/exp/slices" + + "ocm.software/ocm/api/datacontext/action/api" +) + +type ( + Option = api.Option + Options = api.Options +) + +func NewOptions(opts ...Option) *Options { + return api.NewOptions(opts...) +} + +//////////////////////////////////////////////////////////////////////////////// + +type kind struct { + action string +} + +func ForAction(a string) Option { + return kind{a} +} + +func (o kind) ApplyActionHandlerOptionTo(opts *Options) { + opts.Action = o.action +} + +//////////////////////////////////////////////////////////////////////////////// + +type prio struct { + prio int +} + +func WithPrio(p int) Option { + return prio{p} +} + +func (o prio) ApplyActionHandlerOptionTo(opts *Options) { + opts.Priority = o.prio +} + +//////////////////////////////////////////////////////////////////////////////// + +type selectors struct { + selectors []api.Selector +} + +func ForSelectors(s ...api.Selector) Option { + return selectors{s} +} + +func (o selectors) ApplyActionHandlerOptionTo(opts *Options) { + opts.Selectors = append(opts.Selectors, o.selectors...) +} + +//////////////////////////////////////////////////////////////////////////////// + +type versions struct { + versions []string +} + +func WithVersions(vers ...string) Option { + return versions{slices.Clone(vers)} +} + +func (o versions) ApplyActionHandlerOptionTo(opts *Options) { + opts.Versions = append(opts.Versions, o.versions...) +} diff --git a/api/datacontext/action/handlers/registry.go b/api/datacontext/action/handlers/registry.go new file mode 100644 index 000000000..7751f1f19 --- /dev/null +++ b/api/datacontext/action/handlers/registry.go @@ -0,0 +1,233 @@ +package handlers + +import ( + "fmt" + "regexp" + "sort" + "strings" + "sync" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/general" + "golang.org/x/exp/slices" + + "ocm.software/ocm/api/datacontext/action/api" + "ocm.software/ocm/api/utils" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/registrations" + "ocm.software/ocm/api/utils/semverutils" +) + +var defaultHandlers = NewRegistry(api.DefaultRegistry()) + +func DefaultRegistry() Registry { + return defaultHandlers +} + +//////////////////////////////////////////////////////////////////////////////// + +type ActionsProvider interface { + GetActions() Registry +} + +type ActionHandler interface { + Handle(api.ActionSpec, common.Properties) (api.ActionResult, error) +} + +type ActionHandlerMatch struct { + Handler ActionHandler + Version string + Priority int +} + +type ( + Target = ActionsProvider + HandlerConfig = registrations.HandlerConfig + HandlerRegistrationHandler = registrations.HandlerRegistrationHandler[Target, Option] + HandlerRegistrationRegistry = registrations.HandlerRegistrationRegistry[Target, Option] +) + +func NewHandlerRegistrationRegistry(base ...HandlerRegistrationRegistry) HandlerRegistrationRegistry { + return registrations.NewHandlerRegistrationRegistry[Target, Option](base...) +} + +type Registry interface { + registrations.HandlerRegistrationRegistry[Target, Option] + + GetActionTypes() api.ActionTypeRegistry + + Register(h ActionHandler, opts ...Option) error + Execute(spec api.ActionSpec, creds common.Properties) (api.ActionResult, error) + Get(spec api.ActionSpec, possible ...string) []ActionHandlerMatch + AddTo(t Registry) +} + +type registration struct { + handler ActionHandler + versions []string + priority int +} + +var _ Option = (*registration)(nil) + +func (r *registration) ApplyActionHandlerOptionTo(opts *api.Options) { + opts.Priority = r.priority +} + +type registry struct { + registrations.HandlerRegistrationRegistry[Target, Option] + types api.ActionTypeRegistry + + lock sync.Mutex + base Registry + registrations map[string]map[api.Selector]*registration +} + +var _ Registry = (*registry)(nil) + +func NewRegistry(types api.ActionTypeRegistry, base ...Registry) Registry { + b := utils.Optional(base...) + if types == nil { + if b == nil { + types = api.DefaultRegistry() + } else { + types = b.GetActionTypes() + } + } + r := ®istry{ + base: b, + types: types, + registrations: map[string]map[api.Selector]*registration{}, + HandlerRegistrationRegistry: NewHandlerRegistrationRegistry(b), + } + return r +} + +func (r *registry) GetActionTypes() api.ActionTypeRegistry { + return r.types +} + +func (r *registry) AddTo(t Registry) { + r.lock.Lock() + defer r.lock.Unlock() + + if r.base != nil { + r.base.AddTo(t) + } + for k, sel := range r.registrations { + for s, reg := range sel { + t.Register(reg.handler, ForAction(k), WithVersions(reg.versions...), s, reg) + } + } +} + +func (r *registry) Register(h ActionHandler, olist ...Option) error { + r.lock.Lock() + defer r.lock.Unlock() + + opts := NewOptions(olist...) + if opts.Action == "" { + return fmt.Errorf("action kind required for action handler registration") + } + + kinds := r.registrations[opts.Action] + if kinds == nil { + kinds = map[api.Selector]*registration{} + r.registrations[opts.Action] = kinds + } + + versions := opts.Versions + if versions == nil { + versions = r.types.SupportedActionVersions(opts.Action) + } + versions = slices.Clone(versions) + if err := semverutils.SortVersions(versions); err != nil { + return errors.Wrapf(err, "invalid version set") + } + reg := ®istration{ + handler: h, + versions: versions, + priority: general.Conditional(opts.Priority >= 0, opts.Priority, 10), + } + + for _, s := range opts.Selectors { + kinds[s] = reg + } + return nil +} + +func (r *registry) Execute(spec api.ActionSpec, creds common.Properties) (api.ActionResult, error) { + result := r.Get(spec) + sort.SliceStable(result, func(a, b int) bool { + return result[a].Priority < result[b].Priority + }) + if len(result) > 0 { + spec.SetVersion(result[0].Version) + return result[0].Handler.Handle(spec, creds) + } + return nil, nil +} + +func (r *registry) Get(spec api.ActionSpec, possible ...string) []ActionHandlerMatch { + if len(possible) == 0 { + possible = r.GetActionTypes().SupportedActionVersions(spec.GetKind()) + } + + r.lock.Lock() + defer r.lock.Unlock() + + var result []ActionHandlerMatch + + if kinds := r.registrations[spec.GetKind()]; kinds != nil { + // first, check direct selctor match + if reg := kinds[spec.Selector()]; reg != nil { + if len(reg.versions) != 0 { + if v := MatchVersion(r.types.SupportedActionVersions(spec.GetKind()), reg.versions); v != "" { + result = append(result, ActionHandlerMatch{Handler: reg.handler, Version: v, Priority: reg.priority}) + } + } + } else { + // second, try registrations as regexp matcher + for sel, reg := range kinds { + s := string(sel) + e, err := regexp.Compile(s) + if err == nil { + t := strings.Trim(s, "^$") + if t == s { + e, err = regexp.Compile("^" + s + "$") + } + } + if err == nil { + if e.MatchString(string(spec.Selector())) { + if v := MatchVersion(r.types.SupportedActionVersions(spec.GetKind()), reg.versions); v != "" { + result = append(result, ActionHandlerMatch{Handler: reg.handler, Version: v, Priority: reg.priority}) + } + } + } + } + } + } + + if r.base != nil { + result = append(result, r.base.Get(spec, possible...)...) + } + return result +} + +func MatchVersion(possible []string, avail []string) string { + p := slices.Clone(possible) + a := slices.Clone(avail) + + semverutils.SortVersions(p) + semverutils.SortVersions(a) + f := "" + for _, v := range p { + for _, c := range a { + if v == c { + f = c + break + } + } + } + return f +} diff --git a/api/datacontext/action/type.go b/api/datacontext/action/type.go new file mode 100644 index 000000000..fb74805bd --- /dev/null +++ b/api/datacontext/action/type.go @@ -0,0 +1,20 @@ +package action + +import ( + "ocm.software/ocm/api/datacontext/action/api" +) + +const KIND_ACTION = api.KIND_ACTION + +type ( + Selector = api.Selector + Action = api.Action + ActionSpec = api.ActionSpec + ActionResult = api.ActionResult + ActionType = api.ActionType + ActionTypeRegistry = api.ActionTypeRegistry +) + +func DefaultRegistry() ActionTypeRegistry { + return api.DefaultRegistry() +} diff --git a/pkg/contexts/datacontext/attrs.go b/api/datacontext/attrs.go similarity index 97% rename from pkg/contexts/datacontext/attrs.go rename to api/datacontext/attrs.go index a68503c64..80cc13e49 100644 --- a/pkg/contexts/datacontext/attrs.go +++ b/api/datacontext/attrs.go @@ -6,8 +6,8 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/runtime" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" ) type AttributeType interface { diff --git a/api/datacontext/attrs/clicfgattr/attr.go b/api/datacontext/attrs/clicfgattr/attr.go new file mode 100644 index 000000000..41deb2cc4 --- /dev/null +++ b/api/datacontext/attrs/clicfgattr/attr.go @@ -0,0 +1,70 @@ +package clicfgattr + +import ( + "encoding/json" + "fmt" + + "sigs.k8s.io/yaml" + + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ATTR_KEY = "ocm.software/cliconfig" + ATTR_SHORT = "cliconfig" +) + +func init() { + datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) +} + +type AttributeType struct{} + +func (a AttributeType) Name() string { + return ATTR_KEY +} + +func (a AttributeType) Description() string { + return ` +*cliconfigr* Configuration Object passed to command line pluging. +` +} + +func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { + switch c := v.(type) { + case config.Config: + return json.Marshal(v) + case []byte: + if _, err := a.Decode(c, nil); err != nil { + return nil, err + } + return c, nil + default: + return nil, fmt.Errorf("config object required") + } +} + +func (a AttributeType) Decode(data []byte, _ runtime.Unmarshaler) (interface{}, error) { + var c config.GenericConfig + err := yaml.Unmarshal(data, &c) + if err != nil { + return nil, err + } + return &c, nil +} + +//////////////////////////////////////////////////////////////////////////////// + +func Get(ctx datacontext.Context) config.Config { + v := ctx.GetAttributes().GetAttribute(ATTR_KEY) + if v == nil { + return nil + } + return v.(config.Config) +} + +func Set(ctx datacontext.Context, c config.Config) { + ctx.GetAttributes().SetAttribute(ATTR_KEY, c) +} diff --git a/api/datacontext/attrs/init.go b/api/datacontext/attrs/init.go new file mode 100644 index 000000000..9b87f58cb --- /dev/null +++ b/api/datacontext/attrs/init.go @@ -0,0 +1,8 @@ +package attrs + +import ( + _ "ocm.software/ocm/api/datacontext/attrs/logforward" + _ "ocm.software/ocm/api/datacontext/attrs/rootcertsattr" + _ "ocm.software/ocm/api/datacontext/attrs/tmpcache" + _ "ocm.software/ocm/api/datacontext/attrs/vfsattr" +) diff --git a/api/datacontext/attrs/logforward/attr.go b/api/datacontext/attrs/logforward/attr.go new file mode 100644 index 000000000..1fb339645 --- /dev/null +++ b/api/datacontext/attrs/logforward/attr.go @@ -0,0 +1,66 @@ +package logforward + +import ( + "encoding/json" + "fmt" + + logcfg "github.com/mandelsoft/logging/config" + "sigs.k8s.io/yaml" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ATTR_KEY = "github.com/mandelsoft/logforward" + ATTR_SHORT = "logfwd" +) + +func init() { + datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) +} + +type AttributeType struct{} + +func (a AttributeType) Name() string { + return ATTR_KEY +} + +func (a AttributeType) Description() string { + return ` +*logconfig* Logging config structure used for config forwarding +This attribute is used to specify a logging configuration intended +to be forwarded to other tools. +(For example: TOI passes this config to the executor) +` +} + +func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { + if _, ok := v.(*logcfg.Config); !ok { + return nil, fmt.Errorf("logging config required") + } + return json.Marshal(v) +} + +func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { + var c logcfg.Config + err := yaml.Unmarshal(data, &c) + if err != nil { + return nil, err + } + return &c, nil +} + +//////////////////////////////////////////////////////////////////////////////// + +func Get(ctx datacontext.Context) *logcfg.Config { + v := ctx.GetAttributes().GetAttribute(ATTR_KEY) + if v == nil { + return nil + } + return v.(*logcfg.Config) +} + +func Set(ctx datacontext.Context, c *logcfg.Config) { + ctx.GetAttributes().SetAttribute(ATTR_KEY, c) +} diff --git a/api/datacontext/attrs/rootcertsattr/attr.go b/api/datacontext/attrs/rootcertsattr/attr.go new file mode 100644 index 000000000..c5105d717 --- /dev/null +++ b/api/datacontext/attrs/rootcertsattr/attr.go @@ -0,0 +1,148 @@ +package rootcertsattr + +import ( + "crypto/x509" + "encoding/json" + "sync" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/tech/signing/signutils" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ATTR_KEY = "github.com/mandelsoft/ocm/rootcerts" + ATTR_SHORT = "rootcerts" +) + +type ( + Context = datacontext.AttributesContext + ContextProvider = datacontext.ContextProvider +) + +func init() { + datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) +} + +type AttributeType struct{} + +func (a AttributeType) Name() string { + return ATTR_KEY +} + +func (a AttributeType) Description() string { + return ` +*JSON* +General root certificate settings given as JSON document with the following +format: + +
+{
+  "rootCertificates"": [
+     {
+       "data": ""<base64>"
+     },
+     {
+       "path": ""<file path>"
+     }
+  ],
+
+ +One of following data fields are possible: +- data: base64 encoded binary data +- stringdata: plain text data +- path: a file path to read the data from +` +} + +func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { + attr, ok := v.(*Attribute) + if !ok { + return nil, errors.ErrInvalid("certificate attribute") + } + cfg := New() + + attr.lock.Lock() + defer attr.lock.Unlock() + + for _, c := range attr.rootCertificates { + data := signutils.CertificateToPem(c) + cfg.AddRootCertificateData(data) + } + + return json.Marshal(cfg) +} + +func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { + var value Config + err := unmarshaller.Unmarshal(data, &value) + if err != nil { + return nil, err + } + + attr := &Attribute{} + err = value.ApplyToAttribute(attr) + if err != nil { + return nil, err + } + return attr, nil +} + +//////////////////////////////////////////////////////////////////////////////// + +type Attribute struct { + lock sync.Mutex + rootCertificates []*x509.Certificate +} + +func (a *Attribute) RegisterRootCertificates(cert signutils.GenericCertificateChain) error { + certs, err := signutils.GetCertificateChain(cert, false) + if err != nil { + return err + } + + a.lock.Lock() + defer a.lock.Unlock() + + a.rootCertificates = append(a.rootCertificates, certs...) + return nil +} + +func (a *Attribute) HasRootCertificates() bool { + a.lock.Lock() + defer a.lock.Unlock() + return len(a.rootCertificates) > 0 +} + +func (a *Attribute) GetRootCertPool(system bool) *x509.CertPool { + var pool *x509.CertPool + + if system { + pool, _ = x509.SystemCertPool() + } + if pool == nil { + pool = x509.NewCertPool() + } + + a.lock.Lock() + defer a.lock.Unlock() + + for _, c := range a.rootCertificates { + pool.AddCert(c) + } + return pool +} + +//////////////////////////////////////////////////////////////////////////////// + +func Get(ctx ContextProvider) *Attribute { + return ctx.AttributesContext().GetAttributes().GetOrCreateAttribute(ATTR_KEY, func(ctx datacontext.Context) interface{} { + return &Attribute{} + }).(*Attribute) +} + +func Set(ctx ContextProvider, attribute *Attribute) error { + return ctx.AttributesContext().GetAttributes().SetAttribute(ATTR_KEY, attribute) +} diff --git a/api/datacontext/attrs/rootcertsattr/attr_test.go b/api/datacontext/attrs/rootcertsattr/attr_test.go new file mode 100644 index 000000000..a9f0081e9 --- /dev/null +++ b/api/datacontext/attrs/rootcertsattr/attr_test.go @@ -0,0 +1,66 @@ +package rootcertsattr_test + +import ( + "encoding/json" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/datacontext" + me "ocm.software/ocm/api/datacontext/attrs/rootcertsattr" +) + +const NAME = "test" + +var certdata = []byte(` +-----BEGIN CERTIFICATE----- +MIIDBDCCAeygAwIBAgIQF+kRr0G+faDEAH5Y4P1J7DANBgkqhkiG9w0BAQsFADAc +MQwwCgYDVQQKEwNPQ00xDDAKBgNVBAMTA29jbTAeFw0yMzEyMjkxMDIyMzdaFw0y +NDEyMjgxMDIyMzdaMBwxDDAKBgNVBAoTA09DTTEMMAoGA1UEAxMDb2NtMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvpTQIQFNy23ygef3pshdeNjT7TME +kPEuqrqcF3KIX1cX16pHMQeU+VzXAFRj3xCy3LAM8ZzLsdHSwZDsIsGdg0nAbGjz ++USez/9TGC58ktr/84Kh0gHDE28YSVhsnNSrBJcWaBlYZz4Iy89O2Xc4jbK34Cwg +Si0ES+Ru1lxLD6FSLYLe43wCIjWRJRrMFcua6nI0P4MCpcKmTkXG2/xz80QSobI3 +z/isqOT54FKHW8DZZVlQMOxh+loeLksfEq7EYVkQoUWEV6xyR24TEpMGfxERgDre +l7lmx8nIFzRMXkot+P19XWfUBgqctVEiDF4DlRE+SvCZsNCrg7nQuC2AZQIDAQAB +o0IwQDAOBgNVHQ8BAf8EBAMCAqQwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +1iQqrWM/bCXMk+5c1bulfI5zlKcwDQYJKoZIhvcNAQELBQADggEBAAQO6lw6ePuX +E+NyhDYCulueMWHC7GRUKa1KpouFT2yM0BSQnP04VakTlwVO3w4w2KucSVVomHR3 +hTY9Ypx7iGLaqdXHmUZvx3uaTM5IXQKMMWL1LJsxAvuzucehgDlOnFBD91tKsr5o +VRvRU5ya0igBCnnGpFu7NuH3C9pgF01lrQ3EhUHuNeazxleaE3/uQWmAXfxFB4ci +gHMKSEk3HuYA1raDJFv4ihwO5pXHvlDhcW0C1oMG9lOCh8TXpVzzBDZiH1kWPWSs +gW9YBu7/p/22U4++X23RyaheGuysfRAMv9cTv+8T0J8NHaAmQz4/QHFXh+0/tQgU +EVQVGDF6KNU= +-----END CERTIFICATE----- +`) + +var _ = Describe("attribute", func() { + var cfgctx config.Context + var ctx me.Context + + BeforeEach(func() { + cfgctx = config.New(datacontext.MODE_DEFAULTED) + ctx = cfgctx.AttributesContext() + }) + + It("marshal/unmarshal", func() { + cfg := me.New() + cfg.AddRootCertificateData(certdata) + + data, err := json.Marshal(cfg) + Expect(err).To(Succeed()) + + r := &me.Config{} + Expect(json.Unmarshal(data, r)).To(Succeed()) + Expect(r).To(Equal(cfg)) + }) + + It("applies root certificate", func() { + cfg := me.New() + cfg.AddRootCertificateData(certdata) + + Expect(cfgctx.ApplyConfig(cfg, "from test")).To(Succeed()) + Expect(me.Get(ctx).HasRootCertificates()).To(BeTrue()) + }) +}) diff --git a/api/datacontext/attrs/rootcertsattr/config.go b/api/datacontext/attrs/rootcertsattr/config.go new file mode 100644 index 000000000..4a2fd9b3b --- /dev/null +++ b/api/datacontext/attrs/rootcertsattr/config.go @@ -0,0 +1,108 @@ +package rootcertsattr + +import ( + "sync" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/vfs/pkg/vfs" + + cfgcpi "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/tech/signing/signutils" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ConfigType = "rootcerts" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX + ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" +) + +func init() { + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1, usage)) +} + +// Config describes a memory based repository interface. +type Config struct { + runtime.ObjectVersionedType `json:",inline"` + RootCertificates []cfgcpi.ContentSpec `json:"rootCertificates,omitempty"` +} + +// New creates a new memory ConfigSpec. +func New() *Config { + return &Config{ + ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), + } +} + +func (a *Config) GetType() string { + return ConfigType +} + +func (a *Config) AddRootCertificateFile(name string, fss ...vfs.FileSystem) { + a.RootCertificates = append(a.RootCertificates, cfgcpi.ContentSpec{Path: name, FileSystem: utils.Optional(fss...)}) +} + +func (a *Config) AddRootCertificateData(data []byte) { + a.RootCertificates = append(a.RootCertificates, cfgcpi.ContentSpec{Data: data}) +} + +func (a *Config) AddRootCertificate(chain signutils.GenericCertificateChain) error { + certs, err := signutils.GetCertificateChain(chain, false) + if err != nil { + return err + } + a.RootCertificates = append(a.RootCertificates, cfgcpi.ContentSpec{Data: signutils.CertificateChainToPem(certs), Parsed: certs}) + return nil +} + +func (a *Config) ApplyTo(ctx cfgcpi.Context, target interface{}) error { + if t, ok := target.(Context); ok { + if t.AttributesContext().IsAttributesContext() { // apply only to root context + return errors.Wrapf(a.ApplyToAttribute(Get(t)), "applying config to certattr failed") + } + } + return cfgcpi.ErrNoContext(ConfigType) +} + +func (a *Config) ApplyToAttribute(attr *Attribute) error { + for i, k := range a.RootCertificates { + err := attr.RegisterRootCertificates(k) + if err != nil { + return errors.Wrapf(err, "invalid certificate %d", i) + } + } + return nil +} + +const usage = ` +The config type ` + ConfigType + ` can be used to define +general root certificates. A certificate value might be given by one of the fields: +- path: path of file with key data +- data: base64 encoded binary data +- stringdata: data a string parsed by key handler + +
+    rootCertificates:
+      - path: <file path>
+
+ +` + +type Appliers struct { + lock sync.Mutex + appliers []cfgcpi.ConfigApplier +} + +func (r *Appliers) Register(a ...cfgcpi.ConfigApplier) { + r.lock.Lock() + defer r.lock.Unlock() + + r.appliers = append(r.appliers, a...) +} + +var DefaultAppliers = &Appliers{} + +func RegisterApplier(a ...cfgcpi.ConfigApplier) { + DefaultAppliers.Register(a...) +} diff --git a/pkg/contexts/datacontext/attrs/rootcertsattr/suite_test.go b/api/datacontext/attrs/rootcertsattr/suite_test.go similarity index 100% rename from pkg/contexts/datacontext/attrs/rootcertsattr/suite_test.go rename to api/datacontext/attrs/rootcertsattr/suite_test.go diff --git a/api/datacontext/attrs/tmpcache/attr.go b/api/datacontext/attrs/tmpcache/attr.go new file mode 100644 index 000000000..733589ecf --- /dev/null +++ b/api/datacontext/attrs/tmpcache/attr.go @@ -0,0 +1,104 @@ +package tmpcache + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ATTR_KEY = "github.com/mandelsoft/tempblobcache" + ATTR_SHORT = "blobcache" +) + +func init() { + datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) +} + +type AttributeType struct{} + +func (a AttributeType) Name() string { + return ATTR_KEY +} + +func (a AttributeType) Description() string { + return ` +*string* Foldername for temporary blob cache +The temporary blob cache is used to accessing large blobs from remote sytems. +The are temporarily stored in the filesystem, instead of the memory, to avoid +blowing up the memory consumption. +` +} + +func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { + if a, ok := v.(*Attribute); !ok { + return nil, fmt.Errorf("temppcache attribute") + } else { + return []byte(a.Path), nil + } +} + +func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { + var s string + err := runtime.DefaultYAMLEncoding.Unmarshal(data, &s) + if err != nil { + return nil, errors.Wrapf(err, "invalid attribute value for %s", ATTR_KEY) + } + return &Attribute{ + Path: s, + }, nil +} + +//////////////////////////////////////////////////////////////////////////////// + +type Attribute struct { + Path string + 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 { + return nil, err + } + return vfs.TempFile(a.Filesystem, a.Path, pat) +} + +//////////////////////////////////////////////////////////////////////////////// + +func Get(ctx datacontext.Context) *Attribute { + 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 { + a.Filesystem = fs + } + return a + } + return &Attribute{fs.FSTempDir(), fs} +} + +func Set(ctx datacontext.Context, a *Attribute) { + ctx.GetAttributes().SetAttribute(ATTR_KEY, a) +} diff --git a/api/datacontext/attrs/vfsattr/attr.go b/api/datacontext/attrs/vfsattr/attr.go new file mode 100644 index 000000000..6a1c14b45 --- /dev/null +++ b/api/datacontext/attrs/vfsattr/attr.go @@ -0,0 +1,62 @@ +package vfsattr + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/vfs/pkg/osfs" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ATTR_KEY = "github.com/mandelsoft/vfs" + ATTR_SHORT = "vfs" +) + +func init() { + datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) +} + +type AttributeType struct{} + +func (a AttributeType) Name() string { + return ATTR_KEY +} + +func (a AttributeType) Description() string { + return ` +*intern* (not via command line) +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.CachingFileSystem required") + } + return nil, nil +} + +func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { + return nil, errors.ErrNotSupported("decode attribute", ATTR_KEY) +} + +//////////////////////////////////////////////////////////////////////////////// + +var _osfs = osfs.New() + +func Get(ctx datacontext.Context) vfs.FileSystem { + v := ctx.GetAttributes().GetAttribute(ATTR_KEY) + if v == nil { + return _osfs + } + fs, _ := v.(vfs.FileSystem) + return fs +} + +func Set(ctx datacontext.Context, fs vfs.FileSystem) { + ctx.GetAttributes().SetAttribute(ATTR_KEY, fs) +} diff --git a/api/datacontext/builder.go b/api/datacontext/builder.go new file mode 100644 index 000000000..4307c0296 --- /dev/null +++ b/api/datacontext/builder.go @@ -0,0 +1,63 @@ +package datacontext + +import ( + "context" + + "ocm.software/ocm/api/datacontext/action/api" + "ocm.software/ocm/api/datacontext/action/handlers" +) + +type Builder struct { + ctx context.Context + attributes Attributes + actions handlers.Registry +} + +func (b *Builder) getContext() context.Context { + if b.ctx == nil { + return context.Background() + } + return b.ctx +} + +func (b Builder) WithContext(ctx context.Context) Builder { + b.ctx = ctx + return b +} + +func (b Builder) WithAttributes(paranetAttr Attributes) Builder { + b.attributes = paranetAttr + return b +} + +func (b Builder) WithActionHandlers(hdlrs handlers.Registry) Builder { + b.actions = hdlrs + return b +} + +func (b Builder) Bound() (Context, context.Context) { + c := b.New() + return c, context.WithValue(b.getContext(), key, c) +} + +func (b Builder) New(m ...BuilderMode) Context { + mode := Mode(m...) + + if b.actions == nil { + switch mode { + case MODE_INITIAL: + b.actions = handlers.NewRegistry(api.NewActionTypeRegistry()) + case MODE_CONFIGURED: + b.actions = handlers.NewRegistry(api.DefaultRegistry().Copy()) + handlers.DefaultRegistry().AddTo(b.actions) + case MODE_EXTENDED: + b.actions = handlers.NewRegistry(api.DefaultRegistry(), handlers.DefaultRegistry()) + case MODE_DEFAULTED: + fallthrough + case MODE_SHARED: + b.actions = handlers.DefaultRegistry() + } + } + + return newWithActions(mode, nil, b.actions) +} diff --git a/api/datacontext/config/attrs/config_test.go b/api/datacontext/config/attrs/config_test.go new file mode 100644 index 000000000..cc43f4ba4 --- /dev/null +++ b/api/datacontext/config/attrs/config_test.go @@ -0,0 +1,80 @@ +package attrs_test + +import ( + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext" + local "ocm.software/ocm/api/datacontext/config/attrs" + "ocm.software/ocm/api/utils/runtime" +) + +const ATTR_KEY = "test" + +func init() { + datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}) +} + +type AttributeType struct{} + +func (a AttributeType) Name() string { + return ATTR_KEY +} + +func (a AttributeType) Description() string { + return ` +A Test attribute. +` +} + +type Attribute struct { + Value string `json:"value"` +} + +func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { + if _, ok := v.(*Attribute); !ok { + return nil, fmt.Errorf("boolean required") + } + return marshaller.Marshal(v) +} + +func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { + var value Attribute + err := unmarshaller.Unmarshal(data, &value) + return &value, err +} + +//////////////////////////////////////////////////////////////////////////////// + +var _ = Describe("generic attributes", func() { + attribute := &Attribute{"TEST"} + var ctx config.Context + + BeforeEach(func() { + ctx = config.WithSharedAttributes(datacontext.New(nil)).New() + }) + + Context("applies", func() { + It("applies later attribute config", func() { + sub := credentials.WithConfigs(ctx).New() + spec := local.New() + Expect(spec.AddAttribute(ATTR_KEY, attribute)).To(Succeed()) + Expect(ctx.ApplyConfig(spec, "test")).To(Succeed()) + + Expect(sub.GetAttributes().GetAttribute(ATTR_KEY, nil)).To(Equal(attribute)) + }) + + It("applies earlier attribute config", func() { + spec := local.New() + Expect(spec.AddAttribute(ATTR_KEY, attribute)).To(Succeed()) + Expect(ctx.ApplyConfig(spec, "test")).To(Succeed()) + + sub := credentials.WithConfigs(ctx).New() + Expect(sub.GetAttributes().GetAttribute(ATTR_KEY, nil)).To(Equal(attribute)) + }) + }) +}) diff --git a/pkg/contexts/datacontext/config/attrs/suite_test.go b/api/datacontext/config/attrs/suite_test.go similarity index 100% rename from pkg/contexts/datacontext/config/attrs/suite_test.go rename to api/datacontext/config/attrs/suite_test.go diff --git a/api/datacontext/config/attrs/type.go b/api/datacontext/config/attrs/type.go new file mode 100644 index 000000000..d6f8b841c --- /dev/null +++ b/api/datacontext/config/attrs/type.go @@ -0,0 +1,87 @@ +package attrs + +import ( + "encoding/json" + + "github.com/mandelsoft/goutils/errors" + + cfgcpi "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ConfigType = "attributes" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX + ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" +) + +func init() { + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1, usage)) +} + +// Config describes a memory based repository interface. +type Config struct { + runtime.ObjectVersionedType `json:",inline"` + // Attributes descibe a set of geeric attribute settings + Attributes map[string]json.RawMessage `json:"attributes,omitempty"` +} + +// New creates a new memory ConfigSpec. +func New() *Config { + return &Config{ + ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), + Attributes: map[string]json.RawMessage{}, + } +} + +func (a *Config) GetType() string { + return ConfigType +} + +func (a *Config) AddAttribute(attr string, value interface{}) error { + data, err := datacontext.DefaultAttributeScheme.Encode(attr, value, runtime.DefaultJSONEncoding) + if err == nil { + a.Attributes[attr] = data + } + return err +} + +func (a *Config) AddRawAttribute(attr string, data []byte) error { + _, err := datacontext.DefaultAttributeScheme.Decode(attr, data, runtime.DefaultJSONEncoding) + if err == nil { + a.Attributes[attr] = data + } + return err +} + +func (a *Config) ApplyTo(ctx cfgcpi.Context, target interface{}) error { + list := errors.ErrListf("applying config") + t, ok := target.(cfgcpi.Context) + if !ok { + return cfgcpi.ErrNoContext(ConfigType) + } + if a.Attributes == nil { + return nil + } + for a, e := range a.Attributes { + eff := datacontext.DefaultAttributeScheme.Shortcuts()[a] + if eff != "" { + a = eff + } + list.Add(errors.Wrapf(t.GetAttributes().SetEncodedAttribute(a, e, runtime.DefaultJSONEncoding), "attribute %q", a)) + } + return list.Result() +} + +const usage = ` +The config type ` + ConfigType + ` can be used to define a list +of arbitrary attribute specifications: + +
+    type: ` + ConfigType + `
+    attributes:
+       <name>: <yaml defining the attribute>
+       ...
+
+` diff --git a/api/datacontext/config/init.go b/api/datacontext/config/init.go new file mode 100644 index 000000000..8655415cd --- /dev/null +++ b/api/datacontext/config/init.go @@ -0,0 +1,6 @@ +package config + +import ( + _ "ocm.software/ocm/api/datacontext/config/attrs" + _ "ocm.software/ocm/api/datacontext/config/logging" +) diff --git a/api/datacontext/config/logging/config_test.go b/api/datacontext/config/logging/config_test.go new file mode 100644 index 000000000..7b8e10ffe --- /dev/null +++ b/api/datacontext/config/logging/config_test.go @@ -0,0 +1,240 @@ +package logging_test + +import ( + "bytes" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/utils/logging/testhelper" + + "github.com/mandelsoft/logging" + "github.com/tonglil/buflogr" + + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/datacontext" + logcfg "ocm.software/ocm/api/datacontext/config/logging" + log "ocm.software/ocm/api/utils/logging" +) + +var _ = Describe("logging configuration", func() { + var ctx datacontext.AttributesContext + var cfg config.Context + var buf bytes.Buffer + var orig logging.Context + + BeforeEach(func() { + orig = logging.DefaultContext().(*logging.ContextReference).Context + logging.SetDefaultContext(logging.NewDefault()) + log.SetContext(nil) + ctx = datacontext.New(nil) + cfg = config.WithSharedAttributes(ctx).New() + + buf.Reset() + def := buflogr.NewWithBuffer(&buf) + ctx.LoggingContext().SetBaseLogger(def) + }) + + AfterEach(func() { + // logging.SetDefaultContext(orig) + }) + _ = cfg + _ = orig + + It("just logs with defaults", func() { + LogTest(ctx) + + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +V[3] info realm ocm +V[2] warn realm ocm +ERROR error realm ocm +`)) + }) + + It("just logs with settings from default context", func() { + logging.DefaultContext().AddRule(logging.NewConditionRule(logging.DebugLevel)) + LogTest(ctx) + + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +V[4] debug realm ocm +V[3] info realm ocm +V[2] warn realm ocm +ERROR error realm ocm +`)) + }) + + It("just logs with settings from default context", func() { + logging.DefaultContext().AddRule(logging.NewConditionRule(logging.DebugLevel)) + LogTest(cfg) + + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +V[4] debug realm ocm +V[3] info realm ocm +V[2] warn realm ocm +ERROR error realm ocm +`)) + }) + + It("just logs with settings for root context", func() { + spec := ` +type: ` + logcfg.ConfigTypeV1 + ` +contextType: ` + datacontext.CONTEXT_TYPE + ` +settings: + rules: + - rule: + level: Debug +` + _, err := cfg.ApplyData([]byte(spec), nil, "testconfig") + Expect(err).To(Succeed()) + LogTest(ctx) + LogTest(cfg, "cfg") + + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +V[4] debug realm ocm +V[3] info realm ocm +V[2] warn realm ocm +ERROR error realm ocm +V[4] cfgdebug realm ocm +V[3] cfginfo realm ocm +V[2] cfgwarn realm ocm +ERROR cfgerror realm ocm +`)) + }) + + It("just logs with settings for root context by context provider", func() { + spec := ` +type: ` + logcfg.ConfigTypeV1 + ` +settings: + rules: + - rule: + level: Debug +` + _, err := cfg.ApplyData([]byte(spec), nil, "testconfig") + Expect(err).To(Succeed()) + + LogTest(ctx) + LogTest(cfg, "cfg") + + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +V[4] debug realm ocm +V[3] info realm ocm +V[2] warn realm ocm +ERROR error realm ocm +V[4] cfgdebug realm ocm +V[3] cfginfo realm ocm +V[2] cfgwarn realm ocm +ERROR cfgerror realm ocm +`)) + }) + + It("just logs with settings for config context", func() { + spec := ` +type: ` + logcfg.ConfigTypeV1 + ` +contextType: ` + config.CONTEXT_TYPE + ` +settings: + rules: + - rule: + level: Debug +` + _, err := cfg.ApplyData([]byte(spec), nil, "testconfig") + Expect(err).To(Succeed()) + + LogTest(ctx) + LogTest(cfg, "cfg") + + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +V[3] info realm ocm +V[2] warn realm ocm +ERROR error realm ocm +V[4] cfgdebug realm ocm +V[3] cfginfo realm ocm +V[2] cfgwarn realm ocm +ERROR cfgerror realm ocm +`)) + }) + + Context("default logging", func() { + spec1 := ` +type: ` + logcfg.ConfigTypeV1 + ` +contextType: default +settings: + rules: + - rule: + level: Debug +` + spec2 := ` +type: ` + logcfg.ConfigTypeV1 + ` +contextType: default +settings: + rules: + - rule: + level: Info +` + spec3 := ` +type: ` + logcfg.ConfigTypeV1 + ` +contextType: default +extraId: extra +settings: + rules: + - rule: + level: Debug +` + + var ctx logging.Context + + BeforeEach(func() { + log.SetContext(logging.NewDefault()) + buf.Reset() + def := buflogr.NewWithBuffer(&buf) + ctx = log.Context() + ctx.SetBaseLogger(def) + }) + + It("just logs with config", func() { + _, err := cfg.ApplyData([]byte(spec1), nil, "spec1") + Expect(err).To(Succeed()) + LogTest(ctx) + + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +V[4] debug realm ocm +V[3] info realm ocm +V[2] warn realm ocm +ERROR error realm ocm +`)) + }) + + It("applies config once", func() { + _, err := cfg.ApplyData([]byte(spec1), nil, "spec1") + Expect(err).To(Succeed()) + _, err = cfg.ApplyData([]byte(spec2), nil, "spec2") + Expect(err).To(Succeed()) + _, err = cfg.ApplyData([]byte(spec1), nil, "spec1.2") + Expect(err).To(Succeed()) + + LogTest(ctx) + + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +V[3] info realm ocm +V[2] warn realm ocm +ERROR error realm ocm +`)) + }) + It("re-applies config with extra id", func() { + _, err := cfg.ApplyData([]byte(spec1), nil, "spec1") + Expect(err).To(Succeed()) + _, err = cfg.ApplyData([]byte(spec2), nil, "spec2") + Expect(err).To(Succeed()) + _, err = cfg.ApplyData([]byte(spec3), nil, "spec3") + Expect(err).To(Succeed()) + + LogTest(ctx) + + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +V[4] debug realm ocm +V[3] info realm ocm +V[2] warn realm ocm +ERROR error realm ocm +`)) + }) + }) +}) diff --git a/pkg/contexts/datacontext/config/logging/suite_test.go b/api/datacontext/config/logging/suite_test.go similarity index 100% rename from pkg/contexts/datacontext/config/logging/suite_test.go rename to api/datacontext/config/logging/suite_test.go diff --git a/api/datacontext/config/logging/type.go b/api/datacontext/config/logging/type.go new file mode 100644 index 000000000..46514d4ab --- /dev/null +++ b/api/datacontext/config/logging/type.go @@ -0,0 +1,139 @@ +package logging + +import ( + "github.com/mandelsoft/logging" + logcfg "github.com/mandelsoft/logging/config" + + "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/datacontext" + logdata "ocm.software/ocm/api/utils/cobrautils/logopts/logging" + local "ocm.software/ocm/api/utils/logging" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ConfigType = "logging" + cpi.OCM_CONFIG_TYPE_SUFFIX + ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" +) + +func init() { + cpi.RegisterConfigType(cpi.NewConfigType[*Config](ConfigType, usage)) + cpi.RegisterConfigType(cpi.NewConfigType[*Config](ConfigTypeV1, usage)) +} + +// Config describes logging settings for a dedicated context type. +type Config struct { + runtime.ObjectVersionedType `json:",inline"` + + // ContextType described the context type to apply the setting. + // If not set, the settings will be applied to any logging context provider, + // which are not derived contexts. + ContextType string `json:"contextType,omitempty"` + Settings logcfg.Config `json:"settings"` + + // ExtraId is used for the context type "default", "ocm" or "global" to be able + // to reapply the same config again using a different + // identity given by the settings hash + the id. + ExtraId string `json:"extraId,omitempty"` +} + +// New creates a logging config specification. +func New(ctxtype string, deflvl int) *Config { + return &Config{ + ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), + ContextType: ctxtype, + Settings: logcfg.Config{ + DefaultLevel: logging.LevelName(deflvl), + }, + } +} + +// NewWithConfig creates a logging config specification from a +// logging config object. +func NewWithConfig(ctxtype string, cfg *logcfg.Config) *Config { + return &Config{ + ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), + ContextType: ctxtype, + Settings: *cfg, + } +} + +func (c *Config) AddRuleSpec(r logcfg.Rule) error { + c.Settings.Rules = append(c.Settings.Rules, r) + return nil +} + +func (c *Config) GetType() string { + return ConfigType +} + +func (c *Config) ApplyTo(ctx cpi.Context, target interface{}) error { + // first: check for forward configuration + if lc, ok := target.(*logdata.LoggingConfiguration); ok { + switch c.ContextType { + case "default", "ocm", "global", "slave": + lc.LogConfig.DefaultLevel = c.Settings.DefaultLevel + lc.LogConfig.Rules = append(lc.LogConfig.Rules, c.Settings.Rules...) + } + return nil + } + + // second: main use case is to configure vrious logging contexts + switch c.ContextType { + // configure local static logging context. + // here, config is only applied once for every + // setting hash. + case "default": + return local.Configure(&c.Settings, c.ExtraId) + + case "ocm": + return local.ConfigureOCM(&c.Settings, c.ExtraId) + + case "global": + return local.ConfigureGlobal(&c.Settings, c.ExtraId) + + case "slave": + return nil + + // configure logging context providers. + case "": + if _, ok := target.(datacontext.AttributesContext); !ok { + return cpi.ErrNoContext("attribute context") + } + + // configure dedicated context types. + default: + dc, ok := target.(datacontext.Context) + if !ok { + return cpi.ErrNoContext("data context") + } + if dc.GetType() != c.ContextType { + return cpi.ErrNoContext(c.ContextType) + } + } + lctx, ok := target.(logging.ContextProvider) + if !ok { + return cpi.ErrNoContext("logging context") + } + return logcfg.DefaultRegistry().Configure(lctx.LoggingContext(), &c.Settings) +} + +const usage = ` +The config type ` + ConfigType + ` can be used to configure the logging +aspect of a dedicated context type: + +
+    type: ` + ConfigType + `
+    contextType: ` + datacontext.CONTEXT_TYPE + `
+    settings:
+      defaultLevel: Info
+      rules:
+        - ...
+
+ +The context type ` + datacontext.CONTEXT_TYPE + ` is the root context of a +context hierarchy. + +If no context type is specified, the config will be applies to any target +acting as logging context provider, which is not a non-root context. +` diff --git a/pkg/contexts/datacontext/context-refcount-model.png b/api/datacontext/context-refcount-model.png similarity index 100% rename from pkg/contexts/datacontext/context-refcount-model.png rename to api/datacontext/context-refcount-model.png diff --git a/api/datacontext/context.go b/api/datacontext/context.go new file mode 100644 index 000000000..d883bec95 --- /dev/null +++ b/api/datacontext/context.go @@ -0,0 +1,412 @@ +package datacontext + +import ( + "context" + "fmt" + "io" + "reflect" + "sync" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/finalizer" + "github.com/mandelsoft/logging" + + "ocm.software/ocm/api/datacontext/action/handlers" + "ocm.software/ocm/api/utils" + ocmlog "ocm.software/ocm/api/utils/logging" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/refmgmt" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/api/utils/runtimefinalizer" +) + +const OCM_CONTEXT_SUFFIX = ".context" + common.OCM_TYPE_GROUP_SUFFIX + +// BuilderMode controls the handling of unset information in the +// builder configuration when calling the New method. +type BuilderMode int + +const ( + // MODE_SHARED uses the default contexts for unset nested context types. + MODE_SHARED BuilderMode = iota + // MODE_DEFAULTED uses dedicated context instances configured with the + // context type specific default registrations. + MODE_DEFAULTED + // MODE_EXTENDED uses dedicated context instances configured with + // context type registrations extending the default registrations. + MODE_EXTENDED + // MODE_CONFIGURED uses dedicated context instances configured with the + // context type registrations configured with the actual state of the + // default registrations. + MODE_CONFIGURED + // MODE_INITIAL uses completely new contexts for unset nested context types + // and initial registrations. + MODE_INITIAL +) + +const MULTI_REF = false + +func (m BuilderMode) String() string { + switch m { + case MODE_SHARED: + return "shared" + case MODE_DEFAULTED: + return "defaulted" + case MODE_EXTENDED: + return "extended" + case MODE_CONFIGURED: + return "configured" + case MODE_INITIAL: + return "initial" + default: + return fmt.Sprintf("(invalid %d)", m) + } +} + +func Mode(m ...BuilderMode) BuilderMode { + return utils.OptionalDefaulted(MODE_EXTENDED, m...) +} + +type ContextIdentity = runtimefinalizer.ObjectIdentity + +type ContextProvider interface { + // AttributesContext returns the shared attributes + AttributesContext() AttributesContext +} + +// Delegates is the interface for common +// Context features, which might be delegated +// to aggregated contexts. +type Delegates interface { + ocmlog.LogProvider + handlers.ActionsProvider +} + +type _delegates struct { + logging logging.Context + actions handlers.Registry +} + +func (d _delegates) LoggingContext() logging.Context { + return d.logging +} + +func (d _delegates) AttributionContext() logging.AttributionContext { + return d.logging.AttributionContext() +} + +func (d _delegates) Logger(messageContext ...logging.MessageContext) logging.Logger { + return d.logging.Logger(messageContext) +} + +func (d _delegates) GetActions() handlers.Registry { + return d.actions +} + +func ComposeDelegates(l logging.Context, a handlers.Registry) Delegates { + return _delegates{l, a} +} + +type ContextBinder interface { + // BindTo binds the context to a context.Context and makes it + // retrievable by a ForContext method + BindTo(ctx context.Context) context.Context +} + +// Context describes a common interface for a data context used for a dedicated +// purpose. +// Such has a type and always specific attribute store. +// Every Context can be bound to a context.Context. +type Context interface { + ContextBinder + ContextProvider + Delegates + + IsIdenticalTo(Context) bool + + // GetType returns the context type + GetType() string + GetId() ContextIdentity + + GetAttributes() Attributes + + Finalize() error + Finalizer() *finalizer.Finalizer +} + +type InternalContext interface { + Context + runtimefinalizer.RecorderProvider + GetKey() interface{} + GetAllocatable() refmgmt.Allocatable +} + +//////////////////////////////////////////////////////////////////////////////// + +// CONTEXT_TYPE is the global type for an attribute context. +const CONTEXT_TYPE = "attributes" + OCM_CONTEXT_SUFFIX + +type AttributesContext interface { + Context + + IsAttributesContext() bool + AttributesContext() AttributesContext + + BindTo(ctx context.Context) context.Context +} + +// AttributeFactory is used to atomicly create a new attribute for a context. +type AttributeFactory func(Context) interface{} + +type Attributes interface { + finalizer.Finalizable + + GetAttribute(name string, def ...interface{}) interface{} + SetAttribute(name string, value interface{}) error + SetEncodedAttribute(name string, data []byte, unmarshaller runtime.Unmarshaler) error + GetOrCreateAttribute(name string, creator AttributeFactory) interface{} +} + +// DefaultContext is the default context initialized by init functions. +var DefaultContext = NewWithActions(nil, handlers.DefaultRegistry()) + +// ForContext returns the Context to use for context.Context. +// This is either an explicit context or the default context. +func ForContext(ctx context.Context) AttributesContext { + c, _ := ForContextByKey(ctx, key, DefaultContext) + if c == nil { + return nil + } + return c.(AttributesContext) +} + +// WithContext create a new Context bound to a context.Context. +func WithContext(ctx context.Context, parentAttrs Attributes) (Context, context.Context) { + c := New(parentAttrs) + return c, c.BindTo(ctx) +} + +//////////////////////////////////////////////////////////////////////////////// + +type Updater interface { + Update() error +} + +type UpdateFunc func() error + +func (u UpdateFunc) Update() error { + return u() +} + +type delegates = Delegates + +//////////////////////////////////////////////////////////////////////////////// + +var key = reflect.TypeOf(_context{}) + +type _context struct { + *contextBase + updater Updater +} + +// gcWrapper is used as garbage collectable +// wrapper for a context implementation +// to establish a runtime finalizer. +type gcWrapper struct { + GCWrapper + *_context +} + +func newView(c *_context, ref ...bool) AttributesContext { + if utils.Optional(ref...) { + return FinalizedContext[gcWrapper](c) + } + return c +} + +func (w *gcWrapper) SetContext(c *_context) { + w._context = c +} + +var ( + _ Context = (*_context)(nil) + _ ViewCreator[AttributesContext] = (*_context)(nil) +) + +// New provides a root attribute context. +func New(parentAttrs ...Attributes) AttributesContext { + return NewWithActions(utils.Optional(parentAttrs...), handlers.NewRegistry(nil, handlers.DefaultRegistry())) +} + +func NewWithActions(parentAttrs Attributes, actions handlers.Registry) AttributesContext { + return newWithActions(MODE_DEFAULTED, parentAttrs, actions) +} + +func newWithActions(mode BuilderMode, parentAttrs Attributes, actions handlers.Registry) AttributesContext { + c := &_context{} + c.contextBase = newContextBase(c, CONTEXT_TYPE, key, parentAttrs, &c.updater, + ComposeDelegates(logging.NewWithBase(ocmlog.Context()), handlers.NewRegistry(nil, actions)), + ) + return SetupContext(mode, c.CreateView()) // see above +} + +func (c *_context) CreateView() AttributesContext { + return newView(c, true) +} + +func (c *_context) AttributesContext() AttributesContext { + if c.updater != nil { + c.updater.Update() + } + return newView(c) +} + +func (c *_context) IsAttributesContext() bool { + return true +} + +func (c *_context) Actions() handlers.Registry { + if c.updater != nil { + c.updater.Update() + } + return c.contextBase.GetActions() +} + +func (c *_context) LoggingContext() logging.Context { + if c.updater != nil { + c.updater.Update() + } + return c.contextBase.LoggingContext() +} + +func (c *_context) Logger(messageContext ...logging.MessageContext) logging.Logger { + if c.updater != nil { + c.updater.Update() + } + return c.contextBase.Logger(messageContext...) +} + +//////////////////////////////////////////////////////////////////////////////// + +var contextrange, attrsrange = runtimefinalizer.NumberRange{}, runtimefinalizer.NumberRange{} + +type _attributes struct { + sync.RWMutex + id uint64 + ctx Context + parent Attributes + updater *Updater + attributes map[string]interface{} +} + +var _ Attributes = &_attributes{} + +func NewAttributes(ctx Context, parent Attributes, updater *Updater) Attributes { + return newAttributes(ctx, parent, updater) +} + +func newAttributes(ctx Context, parent Attributes, updater *Updater) *_attributes { + return &_attributes{ + id: attrsrange.NextId(), + ctx: ctx, + parent: parent, + updater: updater, + attributes: map[string]interface{}{}, + } +} + +func (c *_attributes) Finalize() error { + list := errors.ErrListf("finalizing attributes") + for n, a := range c.attributes { + if f, ok := a.(finalizer.Finalizable); ok { + list.Addf(nil, f.Finalize(), "attribute %s", n) + } + } + return list.Result() +} + +func (c *_attributes) GetAttribute(name string, def ...interface{}) interface{} { + if *c.updater != nil { + (*c.updater).Update() + } + c.RLock() + defer c.RUnlock() + if a := c.attributes[name]; a != nil { + return a + } + if c.parent != nil { + if a := c.parent.GetAttribute(name); a != nil { + return a + } + } + return utils.Optional(def...) +} + +func (c *_attributes) SetEncodedAttribute(name string, data []byte, unmarshaller runtime.Unmarshaler) error { + s := DefaultAttributeScheme.Shortcuts()[name] + if s != "" { + name = s + } + v, err := DefaultAttributeScheme.Decode(name, data, unmarshaller) + if err != nil { + return err + } + c.SetAttribute(name, v) + return nil +} + +func (c *_attributes) setAttribute(name string, value interface{}) error { + c.Lock() + defer c.Unlock() + + _, err := DefaultAttributeScheme.Encode(name, value, nil) + if err != nil && !errors.IsErrUnknownKind(err, "attribute") { + return err + } + old := c.attributes[name] + if old != nil && old != value { + if c, ok := old.(io.Closer); ok { + c.Close() + } + } + value, err = DefaultAttributeScheme.Convert(name, value) + if err != nil && !errors.IsErrUnknownKind(err, "attribute") { + return err + } + c.attributes[name] = value + return nil +} + +func (c *_attributes) SetAttribute(name string, value interface{}) error { + err := c.setAttribute(name, value) + if err == nil { + if *c.updater != nil { + (*c.updater).Update() + } + } + return err +} + +func (c *_attributes) getOrCreateAttribute(name string, creator AttributeFactory) interface{} { + c.Lock() + defer c.Unlock() + if v := c.attributes[name]; v != nil { + return v + } + if c.parent != nil { + if v := c.parent.GetAttribute(name); v != nil { + return v + } + } + v := creator(c.ctx) + c.attributes[name] = v + return v +} + +func (c *_attributes) GetOrCreateAttribute(name string, creator AttributeFactory) interface{} { + r := c.getOrCreateAttribute(name, creator) + if *c.updater != nil { + (*c.updater).Update() + } + return r +} diff --git a/api/datacontext/context_test.go b/api/datacontext/context_test.go new file mode 100644 index 000000000..26c1be014 --- /dev/null +++ b/api/datacontext/context_test.go @@ -0,0 +1,23 @@ +package datacontext_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + me "ocm.software/ocm/api/datacontext" +) + +var _ = Describe("area test", func() { + It("can be garbage collected", func() { + // ocmlog.Context().AddRule(logging.NewConditionRule(logging.DebugLevel, me.Realm)) + + ctx := me.New() + Expect(ctx.IsIdenticalTo(ctx)).To(BeTrue()) + + ctx2 := ctx.AttributesContext() + Expect(ctx.IsIdenticalTo(ctx2)).To(BeTrue()) + + ctx3 := me.New() + Expect(ctx.IsIdenticalTo(ctx3)).To(BeFalse()) + }) +}) diff --git a/pkg/contexts/datacontext/cpi.go b/api/datacontext/cpi.go similarity index 96% rename from pkg/contexts/datacontext/cpi.go rename to api/datacontext/cpi.go index 4598e0781..42fe2f320 100644 --- a/pkg/contexts/datacontext/cpi.go +++ b/api/datacontext/cpi.go @@ -9,10 +9,10 @@ import ( "github.com/mandelsoft/logging" "github.com/modern-go/reflect2" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/action/handlers" - "github.com/open-component-model/ocm/pkg/refmgmt" - "github.com/open-component-model/ocm/pkg/refmgmt/finalized" - "github.com/open-component-model/ocm/pkg/runtimefinalizer" + "ocm.software/ocm/api/datacontext/action/handlers" + "ocm.software/ocm/api/utils/refmgmt" + "ocm.software/ocm/api/utils/refmgmt/finalized" + "ocm.software/ocm/api/utils/runtimefinalizer" ) // NewContextBase creates a context base implementation supporting diff --git a/api/datacontext/gc_test.go b/api/datacontext/gc_test.go new file mode 100644 index 000000000..56bb08144 --- /dev/null +++ b/api/datacontext/gc_test.go @@ -0,0 +1,92 @@ +package datacontext_test + +import ( + "runtime" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/mandelsoft/goutils/general" + + me "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/utils/runtimefinalizer" +) + +var _ = Describe("area test", func() { + It("can be garbage collected", func() { + ctx := me.New() + r := runtimefinalizer.GetRuntimeFinalizationRecorder(ctx) + id := ctx.GetId() + Expect(me.GetContextRefCount(ctx)).To(Equal(1)) + ctx = nil + runtime.GC() + time.Sleep(time.Second) + Expect(r.Get()).To(ConsistOf(id)) + }) + + It("provides second reference", func() { + // ocmlog.Context().AddRule(logging.NewConditionRule(logging.DebugLevel, me.Realm)) + multiRefs := general.Conditional(me.MULTI_REF, 2, 1) + + ctx := me.New() + Expect(me.GetContextRefCount(ctx)).To(Equal(1)) + + actx := ctx.AttributesContext() + Expect(me.GetContextRefCount(ctx)).To(Equal(multiRefs)) + + r := runtimefinalizer.GetRuntimeFinalizationRecorder(ctx) + Expect(r).NotTo(BeNil()) + + runtime.GC() + time.Sleep(time.Second) + ctx.GetType() + Expect(r.Get()).To(BeNil()) + + actx.GetType() + actx = nil + runtime.GC() + time.Sleep(time.Second) + ctx.GetType() + Expect(r.Get()).To(BeNil()) + Expect(me.GetContextRefCount(ctx)).To(Equal(1)) + + ctx = nil + for i := 0; i < 100; i++ { + runtime.GC() + time.Sleep(time.Millisecond) + } + + Expect(r.Get()).To(ContainElement(ContainSubstring(me.CONTEXT_TYPE))) + }) + + It("creates views", func() { + ctx := me.New() + r := runtimefinalizer.GetRuntimeFinalizationRecorder(ctx) + + Expect(me.GetContextRefCount(ctx)).To(Equal(1)) + Expect(me.IsPersistentContextRef(ctx)).To(BeTrue()) + + view := me.PersistentContextRef(ctx) + Expect(me.GetContextRefCount(view)).To(Equal(1)) // reuse persistent ref + Expect(me.IsPersistentContextRef(view)).To(BeTrue()) + + non := view.AttributesContext() + Expect(me.IsPersistentContextRef(non)).To(BeFalse()) + + view2 := me.PersistentContextRef(non) + Expect(me.GetContextRefCount(view2)).To(Equal(2)) // create new view + Expect(me.IsPersistentContextRef(view2)).To(BeTrue()) + + Expect(ctx.IsIdenticalTo(view)).To(BeTrue()) + Expect(ctx.IsIdenticalTo(view2)).To(BeTrue()) + + ctx = nil + view = nil + view2 = nil + + runtime.GC() + time.Sleep(time.Second) + Expect(len(r.Get())).To(Equal(1)) // ref non is not persistent + }) +}) diff --git a/api/datacontext/logging.go b/api/datacontext/logging.go new file mode 100644 index 000000000..6c0cac83c --- /dev/null +++ b/api/datacontext/logging.go @@ -0,0 +1,13 @@ +package datacontext + +import ( + ocmlog "ocm.software/ocm/api/utils/logging" +) + +var Realm = ocmlog.DefineSubRealm("context lifecycle", "context") + +var Logger = ocmlog.DynamicLogger(Realm) + +func Debug(c Context, msg string, keypairs ...interface{}) { + c.LoggingContext().Logger(Realm).Debug(msg, append(keypairs, "id", c.GetId())...) +} diff --git a/api/datacontext/session.go b/api/datacontext/session.go new file mode 100644 index 000000000..c6e790dbe --- /dev/null +++ b/api/datacontext/session.go @@ -0,0 +1,149 @@ +package datacontext + +import ( + "io" + "sync" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/utils/accessio" +) + +// Session is a context keeping track of objects requiring a close +// after final use. When closing a session all subsequent objects +// will be closed in the opposite order they are added. +// Added closers may be closed prio to the session without causing +// errors. +type Session interface { + // Closer adds a closer returned by a function call providing a closer and an error + // to the session if not error is returned. The results of the call are forwarded to + // the own result. Unfortunately, Go does not support type parameters for methods, + // therefore only an io.Closer can be returned a function result. + Closer(closer io.Closer, extra ...interface{}) (io.Closer, error) + GetOrCreate(key interface{}, creator func(SessionBase) Session) Session + AddCloser(closer io.Closer, callbacks ...accessio.CloserCallback) io.Closer + Close() error + IsClosed() bool +} + +type SessionBase interface { + Lock() + Unlock() + RLock() + RUnlock() + + Session() Session + IsClosed() bool + AddCloser(closer io.Closer, callbacks ...accessio.CloserCallback) io.Closer +} + +type ObjectKey struct { + Object interface{} + Name string +} + +type session struct { + base sessionBase +} + +type sessionBase struct { + sync.RWMutex + session Session + closed bool + closer []io.Closer + sessions map[interface{}]Session +} + +func NewSession() Session { + s := &session{ + sessionBase{ + sessions: map[interface{}]Session{}, + }, + } + s.base.session = s + return s +} + +func GetOrCreateSubSession(s Session, key interface{}, creator func(SessionBase) Session) Session { + if s == nil { + s = NewSession() + } + return s.GetOrCreate(key, creator) +} + +func (s *session) IsClosed() bool { + s.base.RLock() + defer s.base.RUnlock() + return s.base.closed +} + +func (s *session) Close() error { + s.base.Lock() + defer s.base.Unlock() + return s.base.Close() +} + +func (s *session) Closer(closer io.Closer, extra ...interface{}) (io.Closer, error) { + for _, e := range extra { + if err, ok := e.(error); ok && err != nil { + return nil, err + } + } + if closer == nil { + return nil, nil + } + s.base.Lock() + defer s.base.Unlock() + s.base.AddCloser(closer) + + return closer, nil +} + +func (s *session) AddCloser(closer io.Closer, callbacks ...accessio.CloserCallback) io.Closer { + if closer == nil { + return nil + } + s.base.Lock() + defer s.base.Unlock() + return s.base.AddCloser(closer, callbacks...) +} + +func (s *session) GetOrCreate(key interface{}, creator func(SessionBase) Session) Session { + s.base.Lock() + defer s.base.Unlock() + return s.base.GetOrCreate(key, creator) +} + +func (s *sessionBase) Session() Session { + return s.session +} + +func (s *sessionBase) IsClosed() bool { + return s.closed +} + +func (s *sessionBase) Close() error { + if s.closed { + return nil + } + s.closed = true + list := errors.ErrListf("closing session") + for i := len(s.closer) - 1; i >= 0; i-- { + list.Add(s.closer[i].Close()) + } + return list.Result() +} + +func (s *sessionBase) AddCloser(closer io.Closer, callbacks ...accessio.CloserCallback) io.Closer { + s.closer = append(s.closer, accessio.OnceCloser(closer, callbacks...)) + return closer +} + +func (s *sessionBase) GetOrCreate(key interface{}, creator func(SessionBase) Session) Session { + cur := s.sessions[key] + if cur == nil { + cur = creator(s) + s.sessions[key] = cur + } + return cur +} diff --git a/pkg/contexts/datacontext/setup.go b/api/datacontext/setup.go similarity index 100% rename from pkg/contexts/datacontext/setup.go rename to api/datacontext/setup.go diff --git a/pkg/contexts/datacontext/suite_test.go b/api/datacontext/suite_test.go similarity index 100% rename from pkg/contexts/datacontext/suite_test.go rename to api/datacontext/suite_test.go diff --git a/pkg/contexts/datacontext/util.go b/api/datacontext/util.go similarity index 100% rename from pkg/contexts/datacontext/util.go rename to api/datacontext/util.go diff --git a/pkg/env/builder/blob.go b/api/helper/builder/blob.go similarity index 87% rename from pkg/env/builder/blob.go rename to api/helper/builder/blob.go index 894e7c8a2..c35252c56 100644 --- a/pkg/env/builder/blob.go +++ b/api/helper/builder/blob.go @@ -1,9 +1,9 @@ package builder import ( - "github.com/open-component-model/ocm/pkg/blobaccess" - "github.com/open-component-model/ocm/pkg/blobaccess/dirtree" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/dirtree" ) const T_BLOBACCESS = "blob access" diff --git a/api/helper/builder/builder.go b/api/helper/builder/builder.go new file mode 100644 index 000000000..afaf600ad --- /dev/null +++ b/api/helper/builder/builder.go @@ -0,0 +1,210 @@ +package builder + +import ( + "github.com/mandelsoft/goutils/exception" + "github.com/mandelsoft/vfs/pkg/osfs" + "github.com/modern-go/reflect2" + "github.com/onsi/ginkgo/v2" + + "ocm.software/ocm/api/helper/env" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +type element interface { + SetBuilder(b *Builder) + Type() string + Close() error + Set() + + Result() interface{} +} + +type State struct{} + +type base struct { + *Builder + result interface{} +} + +func (e *base) SetBuilder(b *Builder) { + e.Builder = b +} + +func (e *base) Result() interface{} { + return e.result +} + +type static struct { + def_modopts ocm.ModificationOptions +} + +type state struct { + *static + ocm_repo ocm.Repository + ocm_comp ocm.ComponentAccess + ocm_vers ocm.ComponentVersionAccess + ocm_rsc *compdesc.ResourceMeta + ocm_src *compdesc.SourceMeta + ocm_meta *compdesc.ElementMeta + ocm_labels *metav1.Labels + ocm_acc *compdesc.AccessSpec + ocm_modopts *ocm.ModificationOptions + + blob *blobaccess.BlobAccess + hint *string + + oci_repo oci.Repository + oci_nsacc oci.NamespaceAccess + oci_artacc oci.ArtifactAccess + oci_cleanuplayers bool + oci_tags *[]string + oci_artfunc func(oci.ArtifactAccess) error + oci_annofunc func(name, value string) + oci_platform *artdesc.Platform +} + +type Builder struct { + *env.Environment + stack []element + state +} + +// New creates a new composition environment +// including an own OCM context and a private +// filesystem, which can be used to compose +// OCM/OCI repositories and their content. +// It can be configured to work with dedicated +// settings, also. +func New(opts ...env.Option) *Builder { + return &Builder{Environment: env.NewEnvironment(append([]env.Option{env.FileSystem(osfs.OsFs, "/"), env.FailHandler(env.ExceptionFailHandler)}, opts...)...), state: state{static: &static{}}} +} + +// NewBuilder creates a new composition environment +// including an own OCM context and a private +// filesystem, which can be used to compose +// OCM/OCI repositories and their content. +// By default, a private environment is created based on +// a ginko fail handling intended to be used for test cases. +// But it can be configured to work as library with dedicated +// settings, also. +func NewBuilder(opts ...env.Option) *Builder { + return &Builder{Environment: env.NewEnvironment(append([]env.Option{env.FailHandler(ginkgo.Fail)}, opts...)...), state: state{static: &static{}}} +} + +var _ accessio.Option = (*Builder)(nil) + +// Build executes the given functions and returns a potential configuration +// error, instead of using the builder's env.FailHandler. +// Additionally, a build can always throw an exception using +// the exception.Throw function. +func (b *Builder) Build(funcs ...func(*Builder)) (err error) { + old := b.GetFailHandler() + defer func() { + b.SetFailHandler(old) + }() + b.SetFailHandler(env.ExceptionFailHandler) + + defer exception.PropagateException(&err) + for _, f := range funcs { + f(b) + } + return nil +} + +func (b *Builder) SetFailhandler(h ...env.FailHandler) *Builder { + b.Environment.SetFailHandler(h...) + return b +} + +// PropagateError can be used in defer to convert an composition error +// into an error return. +func (b *Builder) PropagateError(errp *error, matchers ...exception.Matcher) { + if r := recover(); r != nil { + *errp = exception.FilterException(r, matchers...) + } +} + +func (b *Builder) set() { + b.state = state{static: b.state.static} + + if len(b.stack) > 0 { + b.peek().Set() + } +} + +func (b *Builder) expect(p interface{}, msg string, tests ...func() bool) { + if reflect2.IsNil(p) { + b.fail(msg+" required", 1) + } + for _, f := range tests { + if !f() { + b.fail(msg+" required", 1) + } + } +} + +func (b *Builder) fail(msg string, callerSkip ...int) { + b.Fail(msg, utils.Optional(callerSkip...)+2) +} + +func (b *Builder) failOn(err error, callerSkip ...int) { + b.FailOnErr(err, "", utils.Optional(callerSkip...)+2) +} + +func (b *Builder) peek() element { + if len(b.stack) == 0 { + b.fail("no open frame", 2) + } + return b.stack[len(b.stack)-1] +} + +func (b *Builder) pop() element { + if len(b.stack) == 0 { + b.fail("no open frame", 2) + } + e := b.stack[len(b.stack)-1] + b.stack = b.stack[:len(b.stack)-1] + b.set() + return e +} + +func (b *Builder) push(e element) { + b.stack = append(b.stack, e) + b.set() +} + +func (b *Builder) configure(e element, funcs []func(), skip ...int) interface{} { + e.SetBuilder(b) + b.push(e) + b.Configure(funcs...) + err := b.pop().Close() + if err != nil { + b.fail(err.Error(), utils.Optional(skip...)+1) + } + return e.Result() +} + +func (b *Builder) Configure(funcs ...func()) { + for _, f := range funcs { + if f != nil { + f() + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +func (b *Builder) Hint(hint string) { + b.expect(b.hint, T_OCMACCESS) + if b.ocm_acc != nil && *b.ocm_acc != nil { + b.fail("access already set") + } + *(b.hint) = hint +} diff --git a/pkg/env/builder/builder_test.go b/api/helper/builder/builder_test.go similarity index 100% rename from pkg/env/builder/builder_test.go rename to api/helper/builder/builder_test.go diff --git a/pkg/env/builder/oci_anno.go b/api/helper/builder/oci_anno.go similarity index 100% rename from pkg/env/builder/oci_anno.go rename to api/helper/builder/oci_anno.go diff --git a/pkg/env/builder/oci_artifact.go b/api/helper/builder/oci_artifact.go similarity index 95% rename from pkg/env/builder/oci_artifact.go rename to api/helper/builder/oci_artifact.go index 7f03e7f84..e9794708f 100644 --- a/pkg/env/builder/oci_artifact.go +++ b/api/helper/builder/oci_artifact.go @@ -3,9 +3,9 @@ package builder import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/cpi" ) const ( diff --git a/api/helper/builder/oci_artifactset.go b/api/helper/builder/oci_artifactset.go new file mode 100644 index 000000000..55a77fe93 --- /dev/null +++ b/api/helper/builder/oci_artifactset.go @@ -0,0 +1,20 @@ +package builder + +import ( + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" +) + +const T_OCIARTIFACTSET = "artifact set" + +//////////////////////////////////////////////////////////////////////////////// + +func (b *Builder) ArtifactSet(path string, fmt accessio.FileFormat, f ...func()) { + r, err := artifactset.Open(accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, path, 0o777, fmt, accessio.PathFileSystem(b.FileSystem())) + b.failOn(err) + + b.configure(&ociNamespace{NamespaceAccess: r, kind: T_OCIARTIFACTSET, annofunc: func(name, value string) { + r.Annotate(name, value) + }}, f) +} diff --git a/pkg/env/builder/oci_config.go b/api/helper/builder/oci_config.go similarity index 87% rename from pkg/env/builder/oci_config.go rename to api/helper/builder/oci_config.go index 875787d28..287d1528e 100644 --- a/pkg/env/builder/oci_config.go +++ b/api/helper/builder/oci_config.go @@ -3,8 +3,8 @@ package builder import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" ) const T_OCICONFIG = "oci config" diff --git a/api/helper/builder/oci_ctf.go b/api/helper/builder/oci_ctf.go new file mode 100644 index 000000000..9832a58c7 --- /dev/null +++ b/api/helper/builder/oci_ctf.go @@ -0,0 +1,15 @@ +package builder + +import ( + "ocm.software/ocm/api/oci/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" +) + +const T_OCI_CTF = "oci common transport format" + +func (b *Builder) OCICommonTransport(path string, fmt accessio.FileFormat, f ...func()) { + r, err := ctf.Open(b.OCMContext().OCIContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, path, 0o777, accessio.PathFileSystem(b.FileSystem())) + b.failOn(err) + b.configure(&ociRepository{Repository: r, kind: T_OCI_CTF}, f) +} diff --git a/pkg/env/builder/oci_layer.go b/api/helper/builder/oci_layer.go similarity index 87% rename from pkg/env/builder/oci_layer.go rename to api/helper/builder/oci_layer.go index 694bd7247..64b90e503 100644 --- a/pkg/env/builder/oci_layer.go +++ b/api/helper/builder/oci_layer.go @@ -3,8 +3,8 @@ package builder import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" ) const T_OCILAYER = "oci layer" diff --git a/pkg/env/builder/oci_namespace.go b/api/helper/builder/oci_namespace.go similarity index 92% rename from pkg/env/builder/oci_namespace.go rename to api/helper/builder/oci_namespace.go index e2d7eaf7c..d868a9c33 100644 --- a/pkg/env/builder/oci_namespace.go +++ b/api/helper/builder/oci_namespace.go @@ -1,7 +1,7 @@ package builder import ( - "github.com/open-component-model/ocm/pkg/contexts/oci" + "ocm.software/ocm/api/oci" ) const T_OCINAMESPACE = "oci namespace" diff --git a/pkg/env/builder/oci_platform.go b/api/helper/builder/oci_platform.go similarity index 100% rename from pkg/env/builder/oci_platform.go rename to api/helper/builder/oci_platform.go diff --git a/pkg/env/builder/oci_repo.go b/api/helper/builder/oci_repo.go similarity index 78% rename from pkg/env/builder/oci_repo.go rename to api/helper/builder/oci_repo.go index 3fab3024d..e1d502620 100644 --- a/pkg/env/builder/oci_repo.go +++ b/api/helper/builder/oci_repo.go @@ -1,9 +1,9 @@ package builder import ( - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ocireg" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/oci/extensions/repositories/ocireg" ) const T_OCIREPOSITORY = "oci repository" diff --git a/pkg/env/builder/oci_tags.go b/api/helper/builder/oci_tags.go similarity index 100% rename from pkg/env/builder/oci_tags.go rename to api/helper/builder/oci_tags.go diff --git a/pkg/env/builder/ocm_access.go b/api/helper/builder/ocm_access.go similarity index 85% rename from pkg/env/builder/ocm_access.go rename to api/helper/builder/ocm_access.go index c08a956e3..17e8f916a 100644 --- a/pkg/env/builder/ocm_access.go +++ b/api/helper/builder/ocm_access.go @@ -1,7 +1,7 @@ package builder import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" + "ocm.software/ocm/api/ocm/compdesc" ) const T_OCMACCESS = "access" diff --git a/api/helper/builder/ocm_comparch.go b/api/helper/builder/ocm_comparch.go new file mode 100644 index 000000000..45b14789c --- /dev/null +++ b/api/helper/builder/ocm_comparch.go @@ -0,0 +1,20 @@ +package builder + +import ( + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/repositories/comparch" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" +) + +const T_COMPARCH = "component archive" + +func (b *Builder) ComponentArchive(path string, fmt accessio.FileFormat, name, vers string, f ...func()) { + r, err := comparch.Open(b.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, path, 0o777, accessio.PathFileSystem(b.FileSystem())) + b.failOn(err) + r.SetName(name) + r.SetVersion(vers) + r.GetDescriptor().Provider.Name = metav1.ProviderName("ACME") + + b.configure(&ocmVersion{ComponentVersionAccess: r, kind: T_COMPARCH}, f) +} diff --git a/pkg/env/builder/ocm_component.go b/api/helper/builder/ocm_component.go similarity index 90% rename from pkg/env/builder/ocm_component.go rename to api/helper/builder/ocm_component.go index d2565e611..0782f1205 100644 --- a/pkg/env/builder/ocm_component.go +++ b/api/helper/builder/ocm_component.go @@ -1,7 +1,7 @@ package builder import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" + "ocm.software/ocm/api/ocm/cpi" ) const T_OCMCOMPONENT = "component" diff --git a/pkg/env/builder/ocm_composition.go b/api/helper/builder/ocm_composition.go similarity index 77% rename from pkg/env/builder/ocm_composition.go rename to api/helper/builder/ocm_composition.go index db4571cda..a63656232 100644 --- a/pkg/env/builder/ocm_composition.go +++ b/api/helper/builder/ocm_composition.go @@ -1,7 +1,7 @@ package builder import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/composition" + "ocm.software/ocm/api/ocm/extensions/repositories/composition" ) const T_OCM_COMPOSITION = "ocm composition repositoryt" diff --git a/api/helper/builder/ocm_ctf.go b/api/helper/builder/ocm_ctf.go new file mode 100644 index 000000000..6bcf62473 --- /dev/null +++ b/api/helper/builder/ocm_ctf.go @@ -0,0 +1,15 @@ +package builder + +import ( + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" +) + +const T_OCM_CTF = "ocm common transport format" + +func (b *Builder) OCMCommonTransport(path string, fmt accessio.FileFormat, f ...func()) { + r, err := ctf.Open(b.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, path, 0o777, fmt, accessio.PathFileSystem(b.FileSystem())) + b.failOn(err) + b.configure(&ocmRepository{Repository: r, kind: T_OCM_CTF}, f) +} diff --git a/pkg/env/builder/ocm_identity.go b/api/helper/builder/ocm_identity.go similarity index 100% rename from pkg/env/builder/ocm_identity.go rename to api/helper/builder/ocm_identity.go diff --git a/pkg/env/builder/ocm_label.go b/api/helper/builder/ocm_label.go similarity index 89% rename from pkg/env/builder/ocm_label.go rename to api/helper/builder/ocm_label.go index ff9982b8b..95feed032 100644 --- a/pkg/env/builder/ocm_label.go +++ b/api/helper/builder/ocm_label.go @@ -1,7 +1,7 @@ package builder import ( - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" ) const T_OCMLABELS = "element with labels" diff --git a/pkg/env/builder/ocm_provider.go b/api/helper/builder/ocm_provider.go similarity index 81% rename from pkg/env/builder/ocm_provider.go rename to api/helper/builder/ocm_provider.go index 190052c5c..8a6f2a32a 100644 --- a/pkg/env/builder/ocm_provider.go +++ b/api/helper/builder/ocm_provider.go @@ -1,8 +1,8 @@ package builder import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" ) type ocmProvider struct { diff --git a/pkg/env/builder/ocm_reference.go b/api/helper/builder/ocm_reference.go similarity index 91% rename from pkg/env/builder/ocm_reference.go rename to api/helper/builder/ocm_reference.go index b9b84fa6a..c6a43ad83 100644 --- a/pkg/env/builder/ocm_reference.go +++ b/api/helper/builder/ocm_reference.go @@ -1,7 +1,7 @@ package builder import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" + "ocm.software/ocm/api/ocm/compdesc" ) type ocmReference struct { diff --git a/api/helper/builder/ocm_repo.go b/api/helper/builder/ocm_repo.go new file mode 100644 index 000000000..35900e2e7 --- /dev/null +++ b/api/helper/builder/ocm_repo.go @@ -0,0 +1,41 @@ +package builder + +import ( + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/repositories/genericocireg" + "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" + ocm "ocm.software/ocm/api/ocm/types" +) + +const T_OCMREPOSITORY = "ocm repository" + +type ocmRepository struct { + base + kind string + cpi.Repository +} + +func (r *ocmRepository) Type() string { + if r.kind != "" { + return r.kind + } + return T_OCMREPOSITORY +} + +func (r *ocmRepository) Set() { + r.Builder.ocm_repo = r.Repository + r.Builder.oci_repo = genericocireg.GetOCIRepository(r.Repository) +} + +func (b *Builder) OCMRepository(spec ocm.RepositorySpec, f ...func()) { + repo, err := b.OCMContext().RepositoryForSpec(spec) + b.failOn(err) + b.configure(&ocmRepository{Repository: repo, kind: T_OCMREPOSITORY}, f) +} + +func (b *Builder) OCIBasedOCMRepository(url string, path string, f ...func()) { + spec := ocireg.NewRepositorySpec(url, &ocireg.ComponentRepositoryMeta{ + SubPath: path, + }) + b.OCMRepository(spec, f...) +} diff --git a/pkg/env/builder/ocm_resource.go b/api/helper/builder/ocm_resource.go similarity index 89% rename from pkg/env/builder/ocm_resource.go rename to api/helper/builder/ocm_resource.go index 44d352857..9b3ffe30d 100644 --- a/pkg/env/builder/ocm_resource.go +++ b/api/helper/builder/ocm_resource.go @@ -3,10 +3,10 @@ package builder import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" ) type ocmResource struct { diff --git a/pkg/env/builder/ocm_source.go b/api/helper/builder/ocm_source.go similarity index 88% rename from pkg/env/builder/ocm_source.go rename to api/helper/builder/ocm_source.go index d23f41f28..da39ec580 100644 --- a/pkg/env/builder/ocm_source.go +++ b/api/helper/builder/ocm_source.go @@ -3,8 +3,8 @@ package builder import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" ) type ocmSource struct { diff --git a/pkg/env/builder/ocm_version.go b/api/helper/builder/ocm_version.go similarity index 90% rename from pkg/env/builder/ocm_version.go rename to api/helper/builder/ocm_version.go index 204d202da..6c0939e56 100644 --- a/pkg/env/builder/ocm_version.go +++ b/api/helper/builder/ocm_version.go @@ -3,8 +3,8 @@ package builder import ( "github.com/mandelsoft/goutils/errors" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/cpi" ) const T_OCMVERSION = "component version" diff --git a/api/helper/builder/ocm_version_test.go b/api/helper/builder/ocm_version_test.go new file mode 100644 index 000000000..e1841cceb --- /dev/null +++ b/api/helper/builder/ocm_version_test.go @@ -0,0 +1,60 @@ +package builder_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + . "ocm.software/ocm/api/ocm/testhelper" + + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/attrs/compositionmodeattr" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + ocmutils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" +) + +const ( + ARCH = "/tmp/ctf" + ARCH2 = "/tmp/ctf2" + PROVIDER = "open-component-model" + VERSION = "v1" + COMPONENT = "github.com/open-component-model/test" + OUT = "/tmp/res" +) + +var _ = Describe("Transfer handler", func() { + var env *Builder + + BeforeEach(func() { + env = NewBuilder() + compositionmodeattr.Set(env.OCMContext(), true) + env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { + env.Component(COMPONENT, func() { + env.Version(VERSION, func() { + env.Provider(PROVIDER) + TestDataResource(env) + }) + }) + }) + }) + + AfterEach(func() { + env.Cleanup() + }) + + It("add ocm resource", func() { + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) + cv := Must(src.LookupComponentVersion(COMPONENT, VERSION)) + + Expect(len(cv.GetDescriptor().Resources)).To(Equal(1)) + + r := Must(cv.GetResourceByIndex(0)) + a := Must(r.Access()) + Expect(a.GetType()).To(Equal(localblob.Type)) + + data := Must(ocmutils.GetResourceData(r)) + Expect(string(data)).To(Equal(S_TESTDATA)) + }) +}) diff --git a/pkg/env/builder/rsa_keypair.go b/api/helper/builder/rsa_keypair.go similarity index 82% rename from pkg/env/builder/rsa_keypair.go rename to api/helper/builder/rsa_keypair.go index 10b4452ed..8daf44956 100644 --- a/pkg/env/builder/rsa_keypair.go +++ b/api/helper/builder/rsa_keypair.go @@ -3,10 +3,10 @@ package builder import ( "github.com/mandelsoft/filepath/pkg/filepath" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" - "github.com/open-component-model/ocm/pkg/signing/signutils" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/api/tech/signing/signutils" + "ocm.software/ocm/api/utils" ) // TODO: switch to context local setting. diff --git a/pkg/env/builder/suite_test.go b/api/helper/builder/suite_test.go similarity index 100% rename from pkg/env/builder/suite_test.go rename to api/helper/builder/suite_test.go diff --git a/pkg/env/env.go b/api/helper/env/env.go similarity index 96% rename from pkg/env/env.go rename to api/helper/env/env.go index ee9d08340..b3c51b7b3 100644 --- a/pkg/env/env.go +++ b/api/helper/env/env.go @@ -20,13 +20,13 @@ import ( "github.com/mandelsoft/vfs/pkg/readonlyfs" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/config" - "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" - ocm "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/oci" + ocm "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/accessio" ) //////////////////////////////////////////////////////////////////////////////// diff --git a/pkg/env/env_test.go b/api/helper/env/env_test.go similarity index 91% rename from pkg/env/env_test.go rename to api/helper/env/env_test.go index ab7ae3b64..f225832e3 100644 --- a/pkg/env/env_test.go +++ b/api/helper/env/env_test.go @@ -7,7 +7,7 @@ import ( "github.com/mandelsoft/vfs/pkg/osfs" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/contexts/ocm" + "ocm.software/ocm/api/ocm" ) var _ = Describe("Environment", func() { diff --git a/pkg/env/keypair.go b/api/helper/env/keypair.go similarity index 81% rename from pkg/env/keypair.go rename to api/helper/env/keypair.go index f1b286b7b..a9139b95b 100644 --- a/pkg/env/keypair.go +++ b/api/helper/env/keypair.go @@ -3,10 +3,10 @@ package env import ( "github.com/mandelsoft/filepath/pkg/filepath" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" - "github.com/open-component-model/ocm/pkg/signing/signutils" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/api/tech/signing/signutils" + "ocm.software/ocm/api/utils" ) func (e *Environment) RSAKeyPair(name ...string) { diff --git a/pkg/env/suite_test.go b/api/helper/env/suite_test.go similarity index 100% rename from pkg/env/suite_test.go rename to api/helper/env/suite_test.go diff --git a/pkg/env/testdata/testfile.txt b/api/helper/env/testdata/testfile.txt similarity index 100% rename from pkg/env/testdata/testfile.txt rename to api/helper/env/testdata/testfile.txt diff --git a/api/oci/action_test.go b/api/oci/action_test.go new file mode 100644 index 000000000..03d5c1407 --- /dev/null +++ b/api/oci/action_test.go @@ -0,0 +1,18 @@ +package oci_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/oci" + oci_repository_prepare "ocm.software/ocm/api/oci/extensions/actions/oci-repository-prepare" +) + +var _ = Describe("action registration", func() { + It("registers oci prepare", func() { + a := oci.DefaultContext().GetActions().GetActionTypes().GetAction(oci_repository_prepare.Type) + Expect(a).NotTo(BeNil()) + v := a.GetVersion("v1") + Expect(v).NotTo(BeNil()) + }) +}) diff --git a/pkg/contexts/oci/annotations/annotations.go b/api/oci/annotations/annotations.go similarity index 100% rename from pkg/contexts/oci/annotations/annotations.go rename to api/oci/annotations/annotations.go diff --git a/api/oci/art_test.go b/api/oci/art_test.go new file mode 100644 index 000000000..d118fb22a --- /dev/null +++ b/api/oci/art_test.go @@ -0,0 +1,38 @@ +package oci_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/oci" +) + +func CheckArt(ref string, exp *oci.ArtSpec) { + spec, err := oci.ParseArt(ref) + if exp == nil { + Expect(err).To(HaveOccurred()) + } else { + Expect(err).To(Succeed()) + Expect(spec).To(Equal(*exp)) + } +} + +var _ = Describe("art parsing", func() { + digest := digest.Digest("sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a") + tag := "v1" + + It("succeeds", func() { + CheckArt("ubuntu", &oci.ArtSpec{Repository: "ubuntu"}) + CheckArt("ubuntu/test", &oci.ArtSpec{Repository: "ubuntu/test"}) + CheckArt("ubuntu/test@"+digest.String(), &oci.ArtSpec{Repository: "ubuntu/test", Digest: &digest}) + CheckArt("ubuntu/test:"+tag, &oci.ArtSpec{Repository: "ubuntu/test", Tag: &tag}) + CheckArt("ubuntu/test:"+tag+"@"+digest.String(), &oci.ArtSpec{Repository: "ubuntu/test", Digest: &digest, Tag: &tag}) + }) + + It("fails", func() { + CheckArt("ubu@ntu", nil) + CheckArt("ubu@sha256:123", nil) + }) +}) diff --git a/api/oci/artdesc/artifact.go b/api/oci/artdesc/artifact.go new file mode 100644 index 000000000..f626c93d1 --- /dev/null +++ b/api/oci/artdesc/artifact.go @@ -0,0 +1,298 @@ +package artdesc + +import ( + "encoding/json" + out "fmt" + + "github.com/containerd/containerd/images" + "github.com/mandelsoft/goutils/errors" + "github.com/opencontainers/go-digest" + ociv1 "github.com/opencontainers/image-spec/specs-go/v1" + + "ocm.software/ocm/api/oci/artdesc/helper" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +const SchemeVersion = helper.SchemeVersion + +const ( + MediaTypeImageManifest = ociv1.MediaTypeImageManifest + MediaTypeImageIndex = ociv1.MediaTypeImageIndex + MediaTypeImageLayer = ociv1.MediaTypeImageLayer + MediaTypeImageLayerGzip = ociv1.MediaTypeImageLayerGzip + + MediaTypeDockerSchema2Manifest = images.MediaTypeDockerSchema2Manifest + MediaTypeDockerSchema2ManifestList = images.MediaTypeDockerSchema2ManifestList + + MediaTypeImageConfig = ociv1.MediaTypeImageConfig +) + +var legacy = false + +type ( + Descriptor = ociv1.Descriptor + Platform = ociv1.Platform +) + +type ArtifactDescriptor interface { + IsManifest() bool + IsIndex() bool + IsValid() bool + + Digest() digest.Digest + Blob() (blobaccess.BlobAccess, error) + Artifact() *Artifact + Manifest() (*Manifest, error) + Index() (*Index, error) +} + +type BlobDescriptorSource interface { + GetBlobDescriptor(digest.Digest) *Descriptor + MimeType() string + IsValid() bool +} + +// Artifact is the unified representation of an OCI artifact +// according to https://github.com/opencontainers/image-spec/blob/main/manifest.md +// It is either an image manifest or an image index manifest (fat image). +type Artifact struct { + manifest *Manifest + index *Index +} + +var ( + _ ArtifactDescriptor = (*Artifact)(nil) + _ BlobDescriptorSource = (*Artifact)(nil) + _ json.Marshaler = (*Artifact)(nil) + _ json.Unmarshaler = (*Artifact)(nil) +) + +func New() *Artifact { + return &Artifact{} +} + +func NewManifestArtifact() *Artifact { + a := New() + a.SetManifest(NewManifest()) + return a +} + +func NewIndexArtifact() *Artifact { + a := New() + a.SetIndex(NewIndex()) + return a +} + +func (d *Artifact) Digest() digest.Digest { + var blob blobaccess.BlobAccess + if d.manifest != nil { + blob, _ = d.manifest.Blob() + } + if d.index != nil { + blob, _ = d.index.Blob() + } + if blob != nil { + return blob.Digest() + } + return "" +} + +func (d *Artifact) Blob() (blobaccess.BlobAccess, error) { + if d.manifest != nil { + return d.manifest.Blob() + } + if d.index != nil { + return d.index.Blob() + } + return nil, errors.ErrInvalid("oci artifact") +} + +func (d *Artifact) Artifact() *Artifact { + return d +} + +func (d *Artifact) MimeType() string { + if d.IsIndex() { + return d.index.MimeType() + } + if d.IsManifest() { + return d.manifest.MimeType() + } + return "" +} + +func (d *Artifact) SetManifest(m *Manifest) error { + if d.IsIndex() || d.IsManifest() { + return errors.Newf("artifact descriptor already instantiated") + } + d.manifest = m + return nil +} + +func (d *Artifact) SetIndex(i *Index) error { + if d.IsIndex() || d.IsManifest() { + return errors.Newf("artifact descriptor already instantiated") + } + d.index = i + return nil +} + +func (d *Artifact) IsValid() bool { + return d.manifest != nil || d.index != nil +} + +func (d *Artifact) IsManifest() bool { + return d.manifest != nil +} + +func (d *Artifact) IsIndex() bool { + return d.index != nil +} + +func (d *Artifact) Index() (*Index, error) { + if d.index != nil { + return d.index, nil + } + return nil, errors.ErrInvalid() +} + +func (d *Artifact) Manifest() (*Manifest, error) { + if d.manifest != nil { + return d.manifest, nil + } + return nil, errors.ErrInvalid() +} + +func (d *Artifact) SetAnnotation(name, value string) error { + return d.modifyAnnotation(func(annos *map[string]string) { + if *annos == nil { + *annos = map[string]string{} + } + (*annos)[name] = value + }) +} + +func (d *Artifact) GetAnnotation(name string) string { + var annos map[string]string + switch { + case d.manifest != nil: + annos = d.manifest.Annotations + case d.index != nil: + annos = d.index.Annotations + default: + return "" + } + if len(annos) == 0 { + return "" + } + return annos[name] +} + +func (d *Artifact) DeleteAnnotation(name string) error { + return d.modifyAnnotation(func(annos *map[string]string) { + if *annos == nil { + return + } + delete(*annos, name) + if len(*annos) == 0 { + *annos = nil + } + }) +} + +func (d *Artifact) modifyAnnotation(mod func(annos *map[string]string)) error { + var annos map[string]string + + switch { + case d.manifest != nil: + annos = d.manifest.Annotations + case d.index != nil: + annos = d.index.Annotations + default: + return errors.Newf("void artifact access") + } + mod(&annos) + if d.manifest != nil { + d.manifest.Annotations = annos + } else { + d.index.Annotations = annos + } + return nil +} + +func (d *Artifact) ToBlobAccess() (blobaccess.BlobAccess, error) { + if d.IsManifest() { + return d.manifest.Blob() + } + if d.IsIndex() { + return d.index.Blob() + } + return nil, errors.ErrInvalid("artifact descriptor") +} + +func (d *Artifact) GetBlobDescriptor(digest digest.Digest) *Descriptor { + if d.IsManifest() { + m, err := d.Manifest() + if err != nil { + out.Printf("manifest was empty for artifact digest %s", digest) + + return nil + } + return m.GetBlobDescriptor(digest) + } + if d.IsIndex() { + i, _ := d.Index() + return i.GetBlobDescriptor(digest) + } + return nil +} + +func (d Artifact) MarshalJSON() ([]byte, error) { + if d.manifest != nil { + d.manifest.MediaType = ArtifactMimeType(d.manifest.MediaType, ociv1.MediaTypeImageManifest, legacy) + return json.Marshal(d.manifest) + } + if d.index != nil { + d.index.MediaType = ArtifactMimeType(d.index.MediaType, ociv1.MediaTypeImageIndex, legacy) + return json.Marshal(d.index) + } + return []byte("null"), nil +} + +func (d *Artifact) UnmarshalJSON(data []byte) error { + if string(data) == "null" { + return nil + } + var m helper.GenericDescriptor + + err := json.Unmarshal(data, &m) + if err != nil { + return err + } + + err = m.Validate() + if err != nil { + return err + } + if m.IsManifest() { + d.manifest = (*Manifest)(m.AsManifest()) + d.index = nil + } else { + d.index = (*Index)(m.AsIndex()) + d.manifest = nil + } + return nil +} + +func Decode(data []byte) (*Artifact, error) { + var d Artifact + + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil +} + +func Encode(d *Artifact) ([]byte, error) { + return json.Marshal(d) +} diff --git a/api/oci/artdesc/config.go b/api/oci/artdesc/config.go new file mode 100644 index 000000000..ccfc5df1b --- /dev/null +++ b/api/oci/artdesc/config.go @@ -0,0 +1,25 @@ +package artdesc + +import ( + "encoding/json" + + ociv1 "github.com/opencontainers/image-spec/specs-go/v1" + + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +type ImageConfig = ociv1.Image + +func ParseImageConfig(blob blobaccess.BlobAccess) (*ImageConfig, error) { + var cfg ImageConfig + + data, err := blob.Get() + if err != nil { + return nil, err + } + err = json.Unmarshal(data, &cfg) + if err != nil { + return nil, err + } + return &cfg, nil +} diff --git a/pkg/contexts/oci/artdesc/helper/generic.go b/api/oci/artdesc/helper/generic.go similarity index 100% rename from pkg/contexts/oci/artdesc/helper/generic.go rename to api/oci/artdesc/helper/generic.go diff --git a/api/oci/artdesc/index.go b/api/oci/artdesc/index.go new file mode 100644 index 000000000..fe741236a --- /dev/null +++ b/api/oci/artdesc/index.go @@ -0,0 +1,117 @@ +package artdesc + +import ( + "encoding/json" + + "github.com/mandelsoft/goutils/errors" + "github.com/opencontainers/go-digest" + "github.com/opencontainers/image-spec/specs-go" + ociv1 "github.com/opencontainers/image-spec/specs-go/v1" + + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +type Index ociv1.Index + +var _ BlobDescriptorSource = (*Index)(nil) + +func NewIndex() *Index { + return &Index{ + Versioned: specs.Versioned{SchemeVersion}, + MediaType: MediaTypeImageIndex, + Manifests: nil, + Annotations: nil, + } +} + +var _ ArtifactDescriptor = (*Index)(nil) + +func (i *Index) IsManifest() bool { + return false +} + +func (i *Index) IsIndex() bool { + return true +} + +func (i *Index) Digest() digest.Digest { + blob, _ := i.Blob() + if blob != nil { + return blob.Digest() + } + return "" +} + +func (i *Index) Artifact() *Artifact { + return &Artifact{index: i} +} + +func (i *Index) Manifest() (*Manifest, error) { + return nil, errors.ErrInvalid() +} + +func (i *Index) Index() (*Index, error) { + return i, nil +} + +func (i *Index) IsValid() bool { + return true +} + +func (i *Index) GetBlobDescriptor(digest digest.Digest) *Descriptor { + for _, m := range i.Manifests { + if m.Digest == digest { + return &m + } + } + return nil +} + +func (i *Index) MimeType() string { + return ArtifactMimeType(i.MediaType, MediaTypeImageIndex, legacy) +} + +func (i *Index) SetAnnotation(name, value string) { + if i.Annotations == nil { + i.Annotations = map[string]string{} + } + i.Annotations[name] = value +} + +func (i *Index) DeleteAnnotation(name string) { + if i.Annotations == nil { + return + } + delete(i.Annotations, name) + if len(i.Annotations) == 0 { + i.Annotations = nil + } +} + +func (i *Index) Blob() (blobaccess.BlobAccess, error) { + i.MediaType = i.MimeType() + data, err := json.Marshal(i) + if err != nil { + return nil, err + } + return blobaccess.ForData(i.MediaType, data), nil +} + +func (i *Index) AddManifest(d *Descriptor) { + i.Manifests = append(i.Manifests, *d) +} + +//////////////////////////////////////////////////////////////////////////////// + +func DecodeIndex(data []byte) (*Index, error) { + var d Index + + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil +} + +func EncodeIndex(d *Index) ([]byte, error) { + return json.Marshal(d) +} diff --git a/pkg/contexts/oci/artdesc/manifest.go b/api/oci/artdesc/manifest.go similarity index 97% rename from pkg/contexts/oci/artdesc/manifest.go rename to api/oci/artdesc/manifest.go index ed8ae2247..c0f990903 100644 --- a/pkg/contexts/oci/artdesc/manifest.go +++ b/api/oci/artdesc/manifest.go @@ -8,7 +8,7 @@ import ( "github.com/opencontainers/image-spec/specs-go" ociv1 "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" ) type Manifest ociv1.Manifest diff --git a/pkg/contexts/oci/artdesc/suite_test.go b/api/oci/artdesc/suite_test.go similarity index 100% rename from pkg/contexts/oci/artdesc/suite_test.go rename to api/oci/artdesc/suite_test.go diff --git a/api/oci/artdesc/utils.go b/api/oci/artdesc/utils.go new file mode 100644 index 000000000..1ac3126e2 --- /dev/null +++ b/api/oci/artdesc/utils.go @@ -0,0 +1,132 @@ +package artdesc + +import ( + "strings" + + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +func DefaultBlobDescriptor(blob blobaccess.BlobAccess) *Descriptor { + return &Descriptor{ + MediaType: blob.MimeType(), + Digest: blob.Digest(), + Size: blob.Size(), + URLs: nil, + Annotations: nil, + Platform: nil, + } +} + +func IsDigest(version string) (bool, digest.Digest) { + if strings.HasPrefix(version, "@") { + return true, digest.Digest(version[1:]) + } + if strings.Contains(version, ":") { + return true, digest.Digest(version) + } + return false, "" +} + +func ToContentMediaType(media string) string { +loop: + for { + last := strings.LastIndex(media, "+") + if last < 0 { + break + } + switch media[last+1:] { + case "tar": + fallthrough + case "gzip": + fallthrough + case "yaml": + fallthrough + case "json": + media = media[:last] + default: + break loop + } + } + return media +} + +func ToDescriptorMediaType(media string) string { + return ToContentMediaType(media) + "+json" +} + +func ToArchiveMediaTypes(media string) []string { + base := ToContentMediaType(media) + return []string{base + "+tar", base + "+tar+gzip"} +} + +func IsOCIMediaType(media string) bool { + c := ToContentMediaType(media) + for _, t := range ContentTypes() { + if t == c { + return true + } + } + return false +} + +func ContentTypes() []string { + r := []string{} + for _, t := range DescriptorTypes() { + r = append(r, ToContentMediaType(t)) + } + return r +} + +func DescriptorTypes() []string { + return []string{ + MediaTypeImageManifest, + MediaTypeImageIndex, + MediaTypeDockerSchema2Manifest, + MediaTypeDockerSchema2ManifestList, + } +} + +func ArchiveBlobTypes() []string { + r := []string{} + for _, t := range ContentTypes() { + r = append(r, ToArchiveMediaTypes(t)...) + } + return r +} + +func ArtifactMimeType(cur, def string, legacy bool) string { + if cur != "" { + return cur + } + return MapArtifactMimeType(def, legacy) +} + +func MapArtifactMimeType(mime string, legacy bool) string { + if legacy { + switch mime { + case MediaTypeImageManifest: + return MediaTypeDockerSchema2Manifest + case MediaTypeImageIndex: + return MediaTypeDockerSchema2ManifestList + } + } else { + switch mime { + case MediaTypeDockerSchema2Manifest: + // return MediaTypeImageManifest + case MediaTypeDockerSchema2ManifestList: + // return MediaTypeImageIndex + } + } + return mime +} + +func MapArtifactBlobMimeType(blob blobaccess.BlobAccess, legacy bool) blobaccess.BlobAccess { + mime := blob.MimeType() + mapped := MapArtifactMimeType(mime, legacy) + if mapped != mime { + return blobaccess.WithMimeType(mapped, blob) + } + return blob +} diff --git a/api/oci/artdesc/utils_test.go b/api/oci/artdesc/utils_test.go new file mode 100644 index 000000000..9683784aa --- /dev/null +++ b/api/oci/artdesc/utils_test.go @@ -0,0 +1,20 @@ +package artdesc_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/oci/artdesc" +) + +var _ = Describe("utils", func() { + It("strips media type", func() { + Expect(artdesc.ToContentMediaType(artdesc.MediaTypeImageManifest)).To(Equal("application/vnd.oci.image.manifest.v1")) + Expect(artdesc.ToContentMediaType(artdesc.MediaTypeImageIndex)).To(Equal("application/vnd.oci.image.index.v1")) + }) + + It("maps to descriptor media typ", func() { + Expect(artdesc.ToDescriptorMediaType(artdesc.ToContentMediaType(artdesc.MediaTypeImageManifest) + "+tar+gzip")).To(Equal(artdesc.MediaTypeImageManifest)) + Expect(artdesc.ToDescriptorMediaType(artdesc.ToContentMediaType(artdesc.MediaTypeImageIndex) + "+tar+gzip")).To(Equal(artdesc.MediaTypeImageIndex)) + }) +}) diff --git a/api/oci/builder.go b/api/oci/builder.go new file mode 100644 index 000000000..fea508f64 --- /dev/null +++ b/api/oci/builder.go @@ -0,0 +1,29 @@ +package oci + +import ( + "context" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/oci/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 WithRepositoyTypeScheme(scheme RepositoryTypeScheme) internal.Builder { + return internal.Builder{}.WithRepositoyTypeScheme(scheme) +} + +func WithRepositorySpecHandlers(reg RepositorySpecHandlers) internal.Builder { + return internal.Builder{}.WithRepositorySpecHandlers(reg) +} + +func New(mode ...datacontext.BuilderMode) Context { + return internal.Builder{}.New(mode...) +} diff --git a/api/oci/config/config_test.go b/api/oci/config/config_test.go new file mode 100644 index 000000000..f95f29320 --- /dev/null +++ b/api/oci/config/config_test.go @@ -0,0 +1,79 @@ +package config_test + +import ( + "encoding/json" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/oci/config" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/oci/extensions/repositories/ocireg" +) + +func normalize(i interface{}) ([]byte, error) { + data, err := json.Marshal(i) + if err != nil { + return nil, err + } + var generic map[string]interface{} + err = json.Unmarshal(data, &generic) + if err != nil { + return nil, err + } + return json.Marshal(generic) +} + +var _ = Describe("oci config", func() { + spec := ocireg.NewRepositorySpec("ghcr.io") + data, err := normalize(spec) + Expect(err).To(Succeed()) + + specdata := "{\"aliases\":{\"alias\":" + string(data) + "},\"type\":\"" + config.ConfigType + "\"}" + + Context("serialize", func() { + It("serializes config", func() { + cfg := config.New() + err := cfg.SetAlias("alias", spec) + Expect(err).To(Succeed()) + + data, err := normalize(cfg) + + Expect(err).To(Succeed()) + Expect(data).To(Equal([]byte(specdata))) + + cfg2 := config.New() + err = json.Unmarshal(data, cfg2) + Expect(err).To(Succeed()) + Expect(cfg2).To(Equal(cfg)) + }) + }) + + Context("apply", func() { + It("applies directly", func() { + ctx := cpi.New() + + cfg := config.New() + err := cfg.SetAlias("alias", spec) + Expect(err).To(Succeed()) + + Expect(cfg.ApplyTo(ctx.ConfigContext(), ctx)).To(Succeed()) + + found := ctx.GetAlias("alias") + Expect(found).To(Equal(cfg.Aliases["alias"])) + }) + + It("applies via config context", func() { + ctx := cpi.New() + + cfg := config.New() + err := cfg.SetAlias("alias", spec) + Expect(err).To(Succeed()) + + Expect(ctx.ConfigContext().ApplyConfig(cfg, "programmatic")).To(Succeed()) + + found := ctx.GetAlias("alias") + Expect(found).To(Equal(cfg.Aliases["alias"])) + }) + }) +}) diff --git a/pkg/contexts/oci/config/suite_test.go b/api/oci/config/suite_test.go similarity index 100% rename from pkg/contexts/oci/config/suite_test.go rename to api/oci/config/suite_test.go diff --git a/api/oci/config/type.go b/api/oci/config/type.go new file mode 100644 index 000000000..2d532af63 --- /dev/null +++ b/api/oci/config/type.go @@ -0,0 +1,70 @@ +package config + +import ( + "ocm.software/ocm/api/config" + cfgcpi "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ConfigType = "oci" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX + ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" +) + +func init() { + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1, usage)) +} + +// Config describes a memory based config interface. +type Config struct { + runtime.ObjectVersionedType `json:",inline"` + Aliases map[string]*cpi.GenericRepositorySpec `json:"aliases,omitempty"` +} + +// New creates a new memory ConfigSpec. +func New() *Config { + return &Config{ + ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), + } +} + +func (a *Config) GetType() string { + return ConfigType +} + +func (a *Config) SetAlias(name string, spec cpi.RepositorySpec) error { + g, err := cpi.ToGenericRepositorySpec(spec) + if err != nil { + return err + } + if a.Aliases == nil { + a.Aliases = map[string]*cpi.GenericRepositorySpec{} + } + a.Aliases[name] = g + return nil +} + +func (a *Config) ApplyTo(ctx config.Context, target interface{}) error { + t, ok := target.(cpi.Context) + if !ok { + return config.ErrNoContext(ConfigType) + } + for n, s := range a.Aliases { + t.SetAlias(n, s) + } + return nil +} + +const usage = ` +The config type ` + ConfigType + ` can be used to define +OCI registry aliases: + +
+    type: ` + ConfigType + `
+    aliases:
+       <name>: <OCI registry specification>
+       ...
+
+` diff --git a/pkg/contexts/oci/cpi/README.md b/api/oci/cpi/README.md similarity index 100% rename from pkg/contexts/oci/cpi/README.md rename to api/oci/cpi/README.md diff --git a/api/oci/cpi/interface.go b/api/oci/cpi/interface.go new file mode 100644 index 000000000..f74921792 --- /dev/null +++ b/api/oci/cpi/interface.go @@ -0,0 +1,95 @@ +package cpi + +// This is the Context Provider Interface for credential providers + +import ( + "github.com/opencontainers/go-digest" + ociv1 "github.com/opencontainers/image-spec/specs-go/v1" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/oci/internal" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +const CONTEXT_TYPE = internal.CONTEXT_TYPE + +const CommonTransportFormat = internal.CommonTransportFormat + +type ( + Context = internal.Context + ContextProvider = internal.ContextProvider + Repository = internal.Repository + RepositorySpecHandlers = internal.RepositorySpecHandlers + RepositorySpecHandler = internal.RepositorySpecHandler + UniformRepositorySpec = internal.UniformRepositorySpec + RepositoryType = internal.RepositoryType + RepositoryTypeProvider = internal.RepositoryTypeProvider + RepositoryTypeScheme = internal.RepositoryTypeScheme + RepositorySpec = internal.RepositorySpec + IntermediateRepositorySpecAspect = internal.IntermediateRepositorySpecAspect + GenericRepositorySpec = internal.GenericRepositorySpec + ArtifactAccess = internal.ArtifactAccess + Artifact = internal.Artifact + ArtifactSource = internal.ArtifactSource + ArtifactSink = internal.ArtifactSink + BlobSource = internal.BlobSource + BlobSink = internal.BlobSink + NamespaceLister = internal.NamespaceLister + NamespaceAccess = internal.NamespaceAccess + ManifestAccess = internal.ManifestAccess + IndexAccess = internal.IndexAccess + BlobAccess = internal.BlobAccess + DataAccess = internal.DataAccess + RepositorySource = internal.RepositorySource + ConsumerIdentityProvider = internal.ConsumerIdentityProvider +) + +type Descriptor = ociv1.Descriptor + +func DefaultContext() Context { + return internal.DefaultContext +} + +func New(m ...datacontext.BuilderMode) Context { + return internal.Builder{}.New(m...) +} + +func FromProvider(p ContextProvider) Context { + return internal.FromProvider(p) +} + +func RegisterRepositorySpecHandler(handler RepositorySpecHandler, types ...string) { + internal.RegisterRepositorySpecHandler(handler, types...) +} + +func ToGenericRepositorySpec(spec RepositorySpec) (*GenericRepositorySpec, error) { + return internal.ToGenericRepositorySpec(spec) +} + +func UniformRepositorySpecForHostURL(typ string, host string) *UniformRepositorySpec { + return internal.UniformRepositorySpecForHostURL(typ, host) +} + +const ( + KIND_OCIARTIFACT = internal.KIND_OCIARTIFACT + KIND_MEDIATYPE = blobaccess.KIND_MEDIATYPE + KIND_BLOB = blobaccess.KIND_BLOB +) + +func ErrUnknownArtifact(name, version string) error { + return internal.ErrUnknownArtifact(name, version) +} + +func ErrBlobNotFound(digest digest.Digest) error { + return blobaccess.ErrBlobNotFound(digest) +} + +func IsErrBlobNotFound(err error) bool { + return blobaccess.IsErrBlobNotFound(err) +} + +// provide context interface for other files to avoid diffs in imports. +var ( + newStrictRepositoryTypeScheme = internal.NewStrictRepositoryTypeScheme + defaultRepositoryTypeScheme = internal.DefaultRepositoryTypeScheme +) diff --git a/pkg/contexts/oci/cpi/mod.go b/api/oci/cpi/mod.go similarity index 89% rename from pkg/contexts/oci/cpi/mod.go rename to api/oci/cpi/mod.go index 7e4278156..21da6bb77 100644 --- a/pkg/contexts/oci/cpi/mod.go +++ b/api/oci/cpi/mod.go @@ -3,8 +3,8 @@ package cpi import ( "github.com/opencontainers/go-digest" - "github.com/open-component-model/ocm/pkg/common/accessobj" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/utils/accessobj" ) type _Artifact = artdesc.Artifact diff --git a/api/oci/cpi/repotypes.go b/api/oci/cpi/repotypes.go new file mode 100644 index 000000000..f39e32b5f --- /dev/null +++ b/api/oci/cpi/repotypes.go @@ -0,0 +1,36 @@ +package cpi + +// this file is identical for contexts oci and credentials and similar for +// ocm. + +import ( + "ocm.software/ocm/api/utils/runtime" +) + +type RepositoryTypeVersionScheme = runtime.TypeVersionScheme[RepositorySpec, RepositoryType] + +func NewRepositoryTypeVersionScheme(kind string) RepositoryTypeVersionScheme { + return runtime.NewTypeVersionScheme[RepositorySpec, RepositoryType](kind, newStrictRepositoryTypeScheme()) +} + +func RegisterRepositoryType(rtype RepositoryType) { + defaultRepositoryTypeScheme.Register(rtype) +} + +func RegisterRepositoryTypeVersions(s RepositoryTypeVersionScheme) { + defaultRepositoryTypeScheme.AddKnownTypes(s) +} + +//////////////////////////////////////////////////////////////////////////////// + +func NewRepositoryType[I RepositorySpec](name string) RepositoryType { + return runtime.NewVersionedTypedObjectType[RepositorySpec, I](name) +} + +func NewRepositoryTypeByConverter[I RepositorySpec, V runtime.TypedObject](name string, converter runtime.Converter[I, V]) RepositoryType { + return runtime.NewVersionedTypedObjectTypeByConverter[RepositorySpec, I](name, converter) +} + +func NewRepositoryTypeByFormatVersion(name string, fmt runtime.FormatVersion[RepositorySpec]) RepositoryType { + return runtime.NewVersionedTypedObjectTypeByFormatVersion[RepositorySpec](name, fmt) +} diff --git a/api/oci/cpi/state.go b/api/oci/cpi/state.go new file mode 100644 index 000000000..53abaf18a --- /dev/null +++ b/api/oci/cpi/state.go @@ -0,0 +1,84 @@ +package cpi + +import ( + "reflect" + + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/utils/accessobj" +) + +type ManifestStateHandler struct{} + +var _ accessobj.StateHandler = &ManifestStateHandler{} + +func NewManifestStateHandler() accessobj.StateHandler { + return &ManifestStateHandler{} +} + +func (i ManifestStateHandler) Initial() interface{} { + return artdesc.NewManifest() +} + +func (i ManifestStateHandler) Encode(d interface{}) ([]byte, error) { + return artdesc.EncodeManifest(d.(*artdesc.Manifest)) +} + +func (i ManifestStateHandler) Decode(data []byte) (interface{}, error) { + return artdesc.DecodeManifest(data) +} + +func (i ManifestStateHandler) Equivalent(a, b interface{}) bool { + return reflect.DeepEqual(a, b) +} + +//////////////////////////////////////////////////////////////////////////////// + +type IndexStateHandler struct{} + +var _ accessobj.StateHandler = &IndexStateHandler{} + +func NewIndexStateHandler() accessobj.StateHandler { + return &IndexStateHandler{} +} + +func (i IndexStateHandler) Initial() interface{} { + return artdesc.NewIndex() +} + +func (i IndexStateHandler) Encode(d interface{}) ([]byte, error) { + return artdesc.EncodeIndex(d.(*artdesc.Index)) +} + +func (i IndexStateHandler) Decode(data []byte) (interface{}, error) { + return artdesc.DecodeIndex(data) +} + +func (i IndexStateHandler) Equivalent(a, b interface{}) bool { + return reflect.DeepEqual(a, b) +} + +//////////////////////////////////////////////////////////////////////////////// + +type ArtifactStateHandler struct{} + +var _ accessobj.StateHandler = &ArtifactStateHandler{} + +func NewArtifactStateHandler() accessobj.StateHandler { + return &ArtifactStateHandler{} +} + +func (i ArtifactStateHandler) Initial() interface{} { + return artdesc.New() +} + +func (i ArtifactStateHandler) Encode(d interface{}) ([]byte, error) { + return artdesc.Encode(d.(*artdesc.Artifact)) +} + +func (i ArtifactStateHandler) Decode(data []byte) (interface{}, error) { + return artdesc.Decode(data) +} + +func (i ArtifactStateHandler) Equivalent(a, b interface{}) bool { + return reflect.DeepEqual(a, b) +} diff --git a/pkg/contexts/oci/cpi/suite_test.go b/api/oci/cpi/suite_test.go similarity index 100% rename from pkg/contexts/oci/cpi/suite_test.go rename to api/oci/cpi/suite_test.go diff --git a/api/oci/cpi/support/artifact.go b/api/oci/cpi/support/artifact.go new file mode 100644 index 000000000..253348936 --- /dev/null +++ b/api/oci/cpi/support/artifact.go @@ -0,0 +1,271 @@ +package support + +import ( + "compress/gzip" + "fmt" + "io" + + "github.com/mandelsoft/goutils/errors" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/oci/internal" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +var ErrNoIndex = errors.New("manifest does not support access to subsequent artifacts") + +type ArtifactAccessImpl struct { + cpi.ArtifactAccessImplBase + artifactBase +} + +var _ cpi.ArtifactAccessImpl = (*ArtifactAccessImpl)(nil) + +func NewArtifactForBlob(container NamespaceAccessImpl, blob blobaccess.BlobAccess, closer ...io.Closer) (cpi.ArtifactAccess, error) { + mode := accessobj.ACC_WRITABLE + if container.IsReadOnly() { + mode = accessobj.ACC_READONLY + } + state, err := accessobj.NewBlobStateForBlob(mode, blob, cpi.NewArtifactStateHandler()) + if err != nil { + return nil, err + } + return newArtifact(container, state, closer...) +} + +func NewArtifact(container NamespaceAccessImpl, defs ...cpi.Artifact) (cpi.ArtifactAccess, error) { + var def cpi.Artifact + if len(defs) != 0 && defs[0] != nil && defs[0].IsValid() { + def = defs[0].Artifact() + } + mode := accessobj.ACC_WRITABLE + if container.IsReadOnly() { + mode = accessobj.ACC_READONLY + } + state, err := accessobj.NewBlobStateForObject(mode, def, cpi.NewArtifactStateHandler()) + if err != nil { + return nil, fmt.Errorf("failed to fetch new blob state: %w", err) + } + return newArtifact(container, state) +} + +func newArtifact(container NamespaceAccessImpl, state accessobj.State, closer ...io.Closer) (cpi.ArtifactAccess, error) { + base, err := cpi.NewArtifactAccessImplBase(container, closer...) + if err != nil { + return nil, err + } + impl := &ArtifactAccessImpl{ + ArtifactAccessImplBase: *base, + artifactBase: newArtifactBase(container, state), + } + return cpi.NewArtifactAccess(impl), nil +} + +func (a *ArtifactAccessImpl) AddBlob(access cpi.BlobAccess) error { + return a.container.AddBlob(access) +} + +func (a *ArtifactAccessImpl) NewArtifact(art ...cpi.Artifact) (cpi.ArtifactAccess, error) { + if !a.IsIndex() { + return nil, ErrNoIndex + } + if a.IsReadOnly() { + return nil, accessio.ErrReadOnly + } + return NewArtifact(a.container, art...) +} + +//////////////////////////////////////////////////////////////////////////////// + +func (a *ArtifactAccessImpl) Artifact() *artdesc.Artifact { + return a.GetDescriptor() +} + +func (a *ArtifactAccessImpl) GetDescriptor() *artdesc.Artifact { + d := a.state.GetState().(*artdesc.Artifact) + if d.IsValid() { + return d + } + return nil +} + +//////////////////////////////////////////////////////////////////////////////// +// from artdesc.Artifact + +func (a *ArtifactAccessImpl) GetBlobDescriptor(digest digest.Digest) *cpi.Descriptor { + d := a.GetDescriptor().GetBlobDescriptor(digest) + /* + if d == nil { + d = a.container.GetBlobDescriptor(digest) + } + */ + return d +} + +func (a *ArtifactAccessImpl) Index() (*artdesc.Index, error) { + a.lock.Lock() + defer a.lock.Unlock() + d, ok := a.state.GetState().(*artdesc.Artifact) + if !ok { + return nil, fmt.Errorf("failed to assert type %T to *artdesc.Artifact", a.state.GetState()) + } + if !d.IsValid() { + idx := artdesc.NewIndex() + if err := d.SetIndex(idx); err != nil { + return nil, errors.Newf("artifact is manifest") + } + } + return d.Index() +} + +func (a *ArtifactAccessImpl) Manifest() (*artdesc.Manifest, error) { + a.lock.Lock() + defer a.lock.Unlock() + d := a.state.GetState().(*artdesc.Artifact) + if !d.IsValid() { + m := artdesc.NewManifest() + if err := d.SetManifest(m); err != nil { + return nil, errors.Newf("artifact is index") + } + } + return d.Manifest() +} + +func (a *ArtifactAccessImpl) ManifestAccess(v cpi.ArtifactAccess) internal.ManifestAccess { + a.lock.Lock() + defer a.lock.Unlock() + d := a.state.GetState().(*artdesc.Artifact) + if !d.IsManifest() { + m := artdesc.NewManifest() + if err := d.SetManifest(m); err != nil { + return nil + } + } + return NewManifestForArtifact(v, a) +} + +func (a *ArtifactAccessImpl) IndexAccess(v cpi.ArtifactAccess) internal.IndexAccess { + a.lock.Lock() + defer a.lock.Unlock() + d := a.state.GetState().(*artdesc.Artifact) + if !d.IsIndex() { + i := artdesc.NewIndex() + if err := d.SetIndex(i); err != nil { + return nil + } + } + return NewIndexForArtifact(v, a) +} + +func (a *ArtifactAccessImpl) GetArtifact(digest digest.Digest) (cpi.ArtifactAccess, error) { + if !a.IsIndex() { + return nil, ErrNoIndex + } + return a.container.GetArtifact("@" + digest.String()) +} + +func (a *ArtifactAccessImpl) GetBlobData(digest digest.Digest) (int64, cpi.DataAccess, error) { + return a.container.GetBlobData(digest) +} + +func (a *ArtifactAccessImpl) GetBlob(digest digest.Digest) (cpi.BlobAccess, error) { + d := a.GetBlobDescriptor(digest) + if d != nil { + size, data, err := a.container.GetBlobData(digest) + if err != nil { + return nil, err + } + err = AdjustSize(d, size) + if err != nil { + return nil, err + } + return blobaccess.ForDataAccess(d.Digest, d.Size, d.MediaType, data), nil + } + return nil, cpi.ErrBlobNotFound(digest) +} + +func (a *ArtifactAccessImpl) AddArtifact(art cpi.Artifact, platform *artdesc.Platform) (cpi.BlobAccess, error) { + if a.IsReadOnly() { + return nil, accessio.ErrReadOnly + } + d, err := a.Index() + if err != nil { + return nil, err + } + + a.lock.Lock() + defer a.lock.Unlock() + + blob, err := a.container.AddArtifact(art) + if err != nil { + return nil, err + } + d.Manifests = append(d.Manifests, cpi.Descriptor{ + MediaType: blob.MimeType(), + Digest: blob.Digest(), + Size: blob.Size(), + URLs: nil, + Annotations: nil, + Platform: platform, + }) + return blob, nil +} + +func (a *ArtifactAccessImpl) AddLayer(blob cpi.BlobAccess, d *cpi.Descriptor) (int, error) { + if a.IsReadOnly() { + return -1, accessio.ErrReadOnly + } + m, err := a.Manifest() + if err != nil { + return -1, err + } + + a.lock.Lock() + defer a.lock.Unlock() + if d == nil { + d = &artdesc.Descriptor{} + } + d.Digest = blob.Digest() + d.Size = blob.Size() + if d.MediaType == "" { + d.MediaType = blob.MimeType() + if d.MediaType == "" { + d.MediaType = artdesc.MediaTypeImageLayer + r, err := blob.Reader() + if err != nil { + return -1, err + } + defer r.Close() + zr, err := gzip.NewReader(r) + if err == nil { + err = zr.Close() + if err == nil { + d.MediaType = artdesc.MediaTypeImageLayerGzip + } + } + } + } + + err = a.container.AddBlob(blob) + if err != nil { + return -1, err + } + + m.Layers = append(m.Layers, *d) + return len(m.Layers) - 1, nil +} + +func AdjustSize(d *artdesc.Descriptor, size int64) error { + if size != blobaccess.BLOB_UNKNOWN_SIZE { + if d.Size == blobaccess.BLOB_UNKNOWN_SIZE { + d.Size = size + } else if d.Size != size { + return errors.Newf("blob size mismatch %d != %d", size, d.Size) + } + } + return nil +} diff --git a/pkg/contexts/oci/cpi/support/artifactsetblobaccess.go b/api/oci/cpi/support/artifactsetblobaccess.go similarity index 91% rename from pkg/contexts/oci/cpi/support/artifactsetblobaccess.go rename to api/oci/cpi/support/artifactsetblobaccess.go index 425c2d8cc..cf252258d 100644 --- a/pkg/contexts/oci/cpi/support/artifactsetblobaccess.go +++ b/api/oci/cpi/support/artifactsetblobaccess.go @@ -5,9 +5,9 @@ import ( "github.com/opencontainers/go-digest" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" ) //////////////////////////////////////////////////////////////////////////////// diff --git a/pkg/contexts/oci/cpi/support/base.go b/api/oci/cpi/support/base.go similarity index 86% rename from pkg/contexts/oci/cpi/support/base.go rename to api/oci/cpi/support/base.go index c4a32c630..60cbf0a79 100644 --- a/pkg/contexts/oci/cpi/support/base.go +++ b/api/oci/cpi/support/base.go @@ -7,10 +7,10 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/opencontainers/go-digest" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common/accessobj" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" ) type artifactBase struct { diff --git a/pkg/contexts/oci/cpi/support/flavor_index.go b/api/oci/cpi/support/flavor_index.go similarity index 88% rename from pkg/contexts/oci/cpi/support/flavor_index.go rename to api/oci/cpi/support/flavor_index.go index 450fce7ca..971a487dc 100644 --- a/pkg/contexts/oci/cpi/support/flavor_index.go +++ b/api/oci/cpi/support/flavor_index.go @@ -4,11 +4,11 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/opencontainers/go-digest" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common/accessobj" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/contexts/oci/internal" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/oci/internal" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" ) type IndexAccess struct { diff --git a/pkg/contexts/oci/cpi/support/flavor_manifest.go b/api/oci/cpi/support/flavor_manifest.go similarity index 94% rename from pkg/contexts/oci/cpi/support/flavor_manifest.go rename to api/oci/cpi/support/flavor_manifest.go index 02603de87..a6e596afb 100644 --- a/pkg/contexts/oci/cpi/support/flavor_manifest.go +++ b/api/oci/cpi/support/flavor_manifest.go @@ -6,9 +6,9 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/opencontainers/go-digest" - "github.com/open-component-model/ocm/pkg/common/accessobj" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/utils/accessobj" ) type ManifestAccess struct { diff --git a/api/oci/cpi/support/namespace.go b/api/oci/cpi/support/namespace.go new file mode 100644 index 000000000..3c655605d --- /dev/null +++ b/api/oci/cpi/support/namespace.go @@ -0,0 +1,113 @@ +package support + +import ( + "github.com/mandelsoft/goutils/errors" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/refmgmt" +) + +// BlobProvider manages the technical access to blobs. +type BlobProvider interface { + refmgmt.Allocatable + cpi.BlobSource + cpi.BlobSink +} + +// NamespaceContainer is the interface used by subsequent access objects +// to access the base implementation. +type NamespaceContainer interface { + SetImplementation(impl NamespaceAccessImpl) + + IsReadOnly() bool + // IsClosed() bool + + cpi.BlobSource + cpi.BlobSink + + Close() error + + // GetBlobDescriptor(digest digest.Digest) *cpi.Descriptor + + GetArtifact(i NamespaceAccessImpl, vers string) (cpi.ArtifactAccess, error) + NewArtifact(i NamespaceAccessImpl, arts ...cpi.Artifact) (cpi.ArtifactAccess, error) + + AddArtifact(artifact cpi.Artifact, tags ...string) (access blobaccess.BlobAccess, err error) + + AddTags(digest digest.Digest, tags ...string) error + ListTags() ([]string, error) + HasArtifact(vers string) (bool, error) +} + +//////////////////////////////////////////////////////////////////////////////// + +type NamespaceAccessImpl interface { + cpi.NamespaceAccessImpl + + // GetBlobDescriptor(digest digest.Digest) *cpi.Descriptor + IsReadOnly() bool + + WithContainer(container NamespaceContainer) NamespaceAccessImpl +} + +type namespaceAccessImpl struct { + *cpi.NamespaceAccessImplBase + NamespaceContainer // inherit as many as possible methods for cpi.NamespaceAccessImpl +} + +var _ NamespaceAccessImpl = (*namespaceAccessImpl)(nil) + +func NewNamespaceAccessImpl(namespace string, c NamespaceContainer, repo cpi.RepositoryViewManager) (NamespaceAccessImpl, error) { + base, err := cpi.NewNamespaceAccessImplBase(namespace, repo) + if err != nil { + return nil, err + } + impl := &namespaceAccessImpl{ + NamespaceAccessImplBase: base, + NamespaceContainer: c, + } + + c.SetImplementation(impl) + return impl, nil +} + +func (n *namespaceAccessImpl) Close() error { + return accessio.Close(n.NamespaceAccessImplBase, n.NamespaceContainer) +} + +func NewNamespaceAccess(namespace string, c NamespaceContainer, repo cpi.RepositoryViewManager, kind ...string) (cpi.NamespaceAccess, error) { + impl, err := NewNamespaceAccessImpl(namespace, c, repo) + if err != nil { + return nil, err + } + return cpi.NewNamespaceAccess(impl, kind...), nil +} + +func GetArtifactSetContainer(i cpi.NamespaceAccessImpl) (NamespaceContainer, error) { + if c, ok := i.(*namespaceAccessImpl); ok { + return c.NamespaceContainer, nil + } + return nil, errors.ErrNotSupported() +} + +func (i *namespaceAccessImpl) WithContainer(c NamespaceContainer) NamespaceAccessImpl { + return &namespaceAccessImpl{ + NamespaceAccessImplBase: i.NamespaceAccessImplBase, + NamespaceContainer: c, + } +} + +func (i *namespaceAccessImpl) GetArtifact(vers string) (cpi.ArtifactAccess, error) { + return i.NamespaceContainer.GetArtifact(i, vers) +} + +func (i *namespaceAccessImpl) AddArtifact(artifact cpi.Artifact, tags ...string) (access blobaccess.BlobAccess, err error) { + return i.NamespaceContainer.AddArtifact(artifact, tags...) +} + +func (i *namespaceAccessImpl) NewArtifact(arts ...cpi.Artifact) (cpi.ArtifactAccess, error) { + return i.NamespaceContainer.NewArtifact(i, arts...) +} diff --git a/api/oci/cpi/utils.go b/api/oci/cpi/utils.go new file mode 100644 index 000000000..5b6623169 --- /dev/null +++ b/api/oci/cpi/utils.go @@ -0,0 +1,63 @@ +package cpi + +import ( + "strings" + + "ocm.software/ocm/api/oci/grammar" +) + +type StringList []string + +func (s *StringList) Add(n string) { + for _, e := range *s { + if n == e { + return + } + } + *s = append(*s, n) +} + +func FilterByNamespacePrefix(prefix string, list []string) []string { + result := []string{} + sub := prefix + if prefix != "" && !strings.HasSuffix(prefix, grammar.RepositorySeparator) { + sub = prefix + grammar.RepositorySeparator + } + for _, k := range list { + if k == prefix || strings.HasPrefix(k, sub) { + result = append(result, k) + } + } + return result +} + +func FilterChildren(closure bool, prefix string, list []string) []string { + if closure { + return FilterByNamespacePrefix(prefix, list) + } + sub := prefix + if prefix != "" && !strings.HasSuffix(prefix, grammar.RepositorySeparator) { + sub = prefix + grammar.RepositorySeparator + } + set := map[string]bool{} + for _, n := range list { + if n == prefix { + set[n] = true + } else if strings.HasPrefix(n, sub) { + rest := n[len(sub):] + i := strings.Index(rest, grammar.RepositorySeparator) + if i < 0 { + set[n] = true + } else { + set[n[:i+len(sub)]] = true + } + } + } + result := make([]string, 0, len(set)) + for _, n := range list { + if set[n] { + result = append(result, n) + } + } + return result +} diff --git a/api/oci/cpi/utils_test.go b/api/oci/cpi/utils_test.go new file mode 100644 index 000000000..0791a6f4e --- /dev/null +++ b/api/oci/cpi/utils_test.go @@ -0,0 +1,62 @@ +package cpi_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/oci/cpi" +) + +var _ = Describe("OCI CPI utils", func() { + list := []string{ + "a/b/c/d", + "a/b/c", + "a/b", + "a/c/d", + "a/c", + "b/c", + } + + It("calculates exclusive number", func() { + Expect(cpi.FilterByNamespacePrefix("a/b/", list)).To(Equal([]string{ + "a/b/c/d", + "a/b/c", + })) + }) + It("calculates inclusive number", func() { + Expect(cpi.FilterByNamespacePrefix("a/b", list)).To(Equal([]string{ + "a/b/c/d", + "a/b/c", + "a/b", + })) + }) + + It("calculates closure", func() { + Expect(cpi.FilterChildren(true, "a/b", list)).To(Equal([]string{ + "a/b/c/d", + "a/b/c", + "a/b", + })) + }) + + It("calculates children", func() { + Expect(cpi.FilterChildren(false, "a/b/", list)).To(Equal([]string{ + "a/b/c", + })) + }) + + It("calculates inclusive children", func() { + Expect(cpi.FilterChildren(false, "a/b", list)).To(Equal([]string{ + "a/b/c", + "a/b", + })) + }) + + It("calculates children closure", func() { + Expect(cpi.FilterChildren(true, "a/b", cpi.FilterByNamespacePrefix("a/b", list))).To(Equal([]string{ + "a/b/c/d", + "a/b/c", + "a/b", + })) + }) +}) diff --git a/api/oci/cpi/view.go b/api/oci/cpi/view.go new file mode 100644 index 000000000..c32e7a14c --- /dev/null +++ b/api/oci/cpi/view.go @@ -0,0 +1,397 @@ +package cpi + +import ( + "fmt" + "io" + + "github.com/mandelsoft/goutils/errors" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/internal" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/refmgmt/resource" +) + +var ErrClosed = resource.ErrClosed + +//////////////////////////////////////////////////////////////////////////////// + +type _RepositoryView interface { + resource.ResourceViewInt[Repository] // here you have to redeclare +} + +type RepositoryViewManager = resource.ViewManager[Repository] // here you have to use an alias + +type RepositoryImpl interface { + internal.RepositoryImpl + resource.ResourceImplementation[Repository] +} + +type _RepositoryImplBase = resource.ResourceImplBase[Repository] + +type RepositoryImplBase struct { + _RepositoryImplBase + ctx Context +} + +func (b *RepositoryImplBase) GetContext() Context { + return b.ctx +} + +func NewRepositoryImplBase(ctx Context) RepositoryImplBase { + return RepositoryImplBase{ + _RepositoryImplBase: resource.ResourceImplBase[Repository]{}, + ctx: ctx, + } +} + +type repositoryView struct { + _RepositoryView + impl RepositoryImpl +} + +var ( + _ Repository = (*repositoryView)(nil) + _ internal.ConsumerIdentityProvider = (*repositoryView)(nil) +) + +func GetRepositoryImplementation(n Repository) (RepositoryImpl, error) { + if v, ok := n.(*repositoryView); ok { + return v.impl, nil + } + return nil, errors.ErrNotSupported("repository implementation type", fmt.Sprintf("%T", n)) +} + +func repositoryViewCreator(i RepositoryImpl, v resource.CloserView, d RepositoryViewManager) Repository { + return &repositoryView{ + _RepositoryView: resource.NewView[Repository](v, d), + impl: i, + } +} + +func NewRepository(impl RepositoryImpl, name ...string) Repository { + return resource.NewResource[Repository](impl, repositoryViewCreator, utils.OptionalDefaulted("OCI repo", name...), true) +} + +func (r *repositoryView) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { + return credentials.GetProvidedConsumerId(r.impl, uctx...) +} + +func (r *repositoryView) GetIdentityMatcher() string { + return credentials.GetProvidedIdentityMatcher(r.impl) +} + +func (r *repositoryView) GetSpecification() internal.RepositorySpec { + return r.impl.GetSpecification() +} + +func (r *repositoryView) GetContext() Context { + return r.impl.GetContext() +} + +func (r *repositoryView) NamespaceLister() (lister internal.NamespaceLister) { + return r.impl.NamespaceLister() +} + +func (r *repositoryView) ExistsArtifact(name string, ref string) (ok bool, err error) { + err = r.Execute(func() error { + ok, err = r.impl.ExistsArtifact(name, ref) + return err + }) + return ok, err +} + +func (r *repositoryView) LookupArtifact(name string, ref string) (acc ArtifactAccess, err error) { + err = r.Execute(func() error { + acc, err = r.impl.LookupArtifact(name, ref) + return err + }) + return acc, err +} + +func (r *repositoryView) LookupNamespace(name string) (acc NamespaceAccess, err error) { + err = r.Execute(func() error { + acc, err = r.impl.LookupNamespace(name) + return err + }) + return acc, err +} + +//////////////////////////////////////////////////////////////////////////////// + +type _NamespaceAccessView interface { + resource.ResourceViewInt[NamespaceAccess] // here you have to redeclare +} + +type NamespaceAccessViewManager = resource.ViewManager[NamespaceAccess] // here you have to use an alias + +type NamespaceAccessImpl interface { + internal.NamespaceAccessImpl + + resource.ResourceImplementation[NamespaceAccess] + + GetNamespace() string +} + +type _NamespaceAccessImplBase = resource.ResourceImplBase[NamespaceAccess] + +type NamespaceAccessImplBase struct { + *_NamespaceAccessImplBase + namespace string +} + +func NewNamespaceAccessImplBase(namespace string, repo RepositoryViewManager, closer ...io.Closer) (*NamespaceAccessImplBase, error) { + base, err := resource.NewResourceImplBase[NamespaceAccess](repo, closer...) + if err != nil { + return nil, err + } + return &NamespaceAccessImplBase{ + _NamespaceAccessImplBase: base, + namespace: namespace, + }, nil +} + +func (b *NamespaceAccessImplBase) GetNamespace() string { + return b.namespace +} + +type namespaceAccessView struct { + _NamespaceAccessView + impl NamespaceAccessImpl +} + +var _ NamespaceAccess = (*namespaceAccessView)(nil) + +func GetNamespaceAccessImplementation(n NamespaceAccess) (NamespaceAccessImpl, error) { + if v, ok := n.(*namespaceAccessView); ok { + return v.impl, nil + } + return nil, errors.ErrNotSupported("namespace implementation type", fmt.Sprintf("%T", n)) +} + +func namespaceAccessViewCreator(i NamespaceAccessImpl, v resource.CloserView, d NamespaceAccessViewManager) NamespaceAccess { + return &namespaceAccessView{ + _NamespaceAccessView: resource.NewView[NamespaceAccess](v, d), + impl: i, + } +} + +func NewNamespaceAccess(impl NamespaceAccessImpl, kind ...string) NamespaceAccess { + return resource.NewResource[NamespaceAccess](impl, namespaceAccessViewCreator, fmt.Sprintf("%s %s", utils.OptionalDefaulted("namespace", kind...), impl.GetNamespace()), true) +} + +func (n *namespaceAccessView) GetNamespace() string { + return n.impl.GetNamespace() +} + +func (n *namespaceAccessView) GetArtifact(version string) (acc internal.ArtifactAccess, err error) { + err = n.Execute(func() error { + acc, err = n.impl.GetArtifact(version) + return err + }) + return acc, err +} + +func (n *namespaceAccessView) GetBlobData(digest digest.Digest) (size int64, acc internal.DataAccess, err error) { + err = n.Execute(func() error { + size, acc, err = n.impl.GetBlobData(digest) + return err + }) + return size, acc, err +} + +func (n *namespaceAccessView) AddBlob(access internal.BlobAccess) error { + return n.Execute(func() error { + return n.impl.AddBlob(access) + }) +} + +func (n *namespaceAccessView) HasArtifact(vers string) (ok bool, err error) { + err = n.Execute(func() error { + ok, err = n.impl.HasArtifact(vers) + return err + }) + return ok, err +} + +func (n *namespaceAccessView) AddArtifact(a internal.Artifact, tags ...string) (acc internal.BlobAccess, err error) { + err = n.Execute(func() error { + acc, err = n.impl.AddArtifact(a, tags...) + return err + }) + return acc, err +} + +func (n *namespaceAccessView) AddTags(digest digest.Digest, tags ...string) error { + return n.Execute(func() error { + return n.impl.AddTags(digest, tags...) + }) +} + +func (n *namespaceAccessView) ListTags() (list []string, err error) { + err = n.Execute(func() error { + list, err = n.impl.ListTags() + return err + }) + return list, err +} + +func (n *namespaceAccessView) NewArtifact(artifact ...Artifact) (acc internal.ArtifactAccess, err error) { + err = n.Execute(func() error { + acc, err = n.impl.NewArtifact(artifact...) + return err + }) + return acc, err +} + +//////////////////////////////////////////////////////////////////////////////// + +type _ArtifactAccessView interface { + resource.ResourceViewInt[ArtifactAccess] +} + +type ArtifactAccessViewManager = resource.ViewManager[ArtifactAccess] + +type ArtifactAccessImpl interface { + internal.ArtifactAccessImpl + + resource.ResourceImplementation[ArtifactAccess] + + // creation of slave objects require the original view they are created for. + + ManifestAccess(ArtifactAccess) ManifestAccess + IndexAccess(ArtifactAccess) IndexAccess +} + +type ArtifactAccessImplBase = resource.ResourceImplBase[ArtifactAccess] + +func NewArtifactAccessImplBase(ns NamespaceAccessViewManager, closer ...io.Closer) (*ArtifactAccessImplBase, error) { + return resource.NewResourceImplBase[ArtifactAccess](ns, closer...) +} + +type artifactAccessView struct { + _ArtifactAccessView + impl ArtifactAccessImpl +} + +var _ ArtifactAccess = (*artifactAccessView)(nil) + +func artifactAccessViewCreator(i ArtifactAccessImpl, v resource.CloserView, d resource.ViewManager[ArtifactAccess]) ArtifactAccess { + return &artifactAccessView{ + _ArtifactAccessView: resource.NewView[ArtifactAccess](v, d), + impl: i, + } +} + +func NewArtifactAccess(impl ArtifactAccessImpl) ArtifactAccess { + return resource.NewResource[ArtifactAccess](impl, artifactAccessViewCreator, "artifact", true) +} + +func (a *artifactAccessView) IsManifest() bool { + return a.impl.IsManifest() +} + +func (a *artifactAccessView) IsIndex() bool { + return a.impl.IsIndex() +} + +func (a *artifactAccessView) IsValid() bool { + return a.impl.IsValid() +} + +func (a *artifactAccessView) Digest() digest.Digest { + return a.impl.Digest() +} + +func (a *artifactAccessView) Blob() (internal.BlobAccess, error) { + return a.impl.Blob() +} + +func (a *artifactAccessView) GetDescriptor() *artdesc.Artifact { + return a.impl.GetDescriptor() +} + +func (a *artifactAccessView) Artifact() *artdesc.Artifact { + return a.impl.Artifact() +} + +func (a *artifactAccessView) Manifest() (*artdesc.Manifest, error) { + return a.impl.Manifest() +} + +func (a *artifactAccessView) ManifestAccess() internal.ManifestAccess { + return a.impl.ManifestAccess(a) +} + +func (a *artifactAccessView) Index() (*artdesc.Index, error) { + return a.impl.Index() +} + +func (a *artifactAccessView) IndexAccess() internal.IndexAccess { + return a.impl.IndexAccess(a) +} + +func (a *artifactAccessView) GetBlobData(digest digest.Digest) (size int64, acc internal.DataAccess, err error) { + size = -1 + err = a.Execute(func() error { + size, acc, err = a.impl.GetBlobData(digest) + return err + }) + return size, acc, err +} + +func (a *artifactAccessView) AddBlob(access internal.BlobAccess) error { + if err := utils.ValidateObject(access); err != nil { + return err + } + return a.Execute(func() error { + return a.impl.AddBlob(access) + }) +} + +func (a *artifactAccessView) GetBlob(digest digest.Digest) (acc internal.BlobAccess, err error) { + err = a.Execute(func() error { + acc, err = a.impl.GetBlob(digest) + return err + }) + return acc, err +} + +func (a *artifactAccessView) GetArtifact(digest digest.Digest) (acc internal.ArtifactAccess, err error) { + err = a.Execute(func() error { + acc, err = a.impl.GetArtifact(digest) + return err + }) + return acc, err +} + +func (a *artifactAccessView) AddArtifact(artifact internal.Artifact, platform *artdesc.Platform) (acc internal.BlobAccess, err error) { + err = a.Execute(func() error { + acc, err = a.impl.AddArtifact(artifact, platform) + return err + }) + return acc, err +} + +func (a *artifactAccessView) NewArtifact(art ...Artifact) (acc ArtifactAccess, err error) { + err = a.Execute(func() error { + acc, err = a.impl.NewArtifact(art...) + return err + }) + return acc, err +} + +func (a *artifactAccessView) AddLayer(access internal.BlobAccess, descriptor *artdesc.Descriptor) (index int, err error) { + if err := utils.ValidateObject(access); err != nil { + return -1, err + } + + index = -1 + err = a.Execute(func() error { + index, err = a.impl.AddLayer(access, descriptor) + return err + }) + return index, err +} diff --git a/api/oci/extensions/actions/init.go b/api/oci/extensions/actions/init.go new file mode 100644 index 000000000..3d0109d5d --- /dev/null +++ b/api/oci/extensions/actions/init.go @@ -0,0 +1,5 @@ +package actions + +import ( + _ "ocm.software/ocm/api/oci/extensions/actions/oci-repository-prepare" +) diff --git a/api/oci/extensions/actions/oci-repository-prepare/exec.go b/api/oci/extensions/actions/oci-repository-prepare/exec.go new file mode 100644 index 000000000..cb2b75367 --- /dev/null +++ b/api/oci/extensions/actions/oci-repository-prepare/exec.go @@ -0,0 +1,12 @@ +package oci_repository_prepare + +import ( + "github.com/mandelsoft/goutils/generics" + + "ocm.software/ocm/api/datacontext/action/handlers" + common "ocm.software/ocm/api/utils/misc" +) + +func Execute(hdlrs handlers.Registry, host, repo string, creds common.Properties) (*ActionResult, error) { + return generics.CastR[*ActionResult](hdlrs.Execute(Spec(host, repo), creds)) +} diff --git a/api/oci/extensions/actions/oci-repository-prepare/type.go b/api/oci/extensions/actions/oci-repository-prepare/type.go new file mode 100644 index 000000000..d3b160f5b --- /dev/null +++ b/api/oci/extensions/actions/oci-repository-prepare/type.go @@ -0,0 +1,81 @@ +package oci_repository_prepare + +import ( + "path" + + "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/datacontext/action/api" + "ocm.software/ocm/api/utils" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" +) + +const Type = "oci.repository.prepare" + +func init() { + api.RegisterAction(Type, "Prepare the usage of a repository in an OCI registry.", usage, + []string{identity.ID_HOSTNAME, identity.ID_PORT, identity.ID_PATHPREFIX}) + + api.RegisterType(api.NewActionType[*ActionSpecV1, *ActionResultV1](Type, "v1")) +} + +var usage = ` +The hostname of the target repository is used as selector. The action should +assure, that the requested repository is available on the target OCI registry. + +Spec version v1 uses the following specification fields: +- hostname *string*: The hostname of the OCI registry. +- repository *string*: The OCI repository name. +` + +//////////////////////////////////////////////////////////////////////////////// +// internal version + +type ActionSpec = ActionSpecV1 + +type ActionResult = ActionResultV1 + +func Spec(host string, repo string) *ActionSpec { + return &ActionSpec{ + ObjectVersionedType: runtime.ObjectVersionedType{runtime.TypeName(Type, "v1")}, + Hostname: host, + Repository: repo, + } +} + +func Result(msg string) *ActionResult { + return &ActionResult{ + CommonResult: api.CommonResult{ + ObjectVersionedType: runtime.ObjectVersionedType{runtime.TypeName(Type, "v1")}, + Message: msg, + }, + } +} + +//////////////////////////////////////////////////////////////////////////////// +// serialization formats + +type ActionSpecV1 struct { + runtime.ObjectVersionedType + Hostname string `json:"hostname"` + Repository string `json:"repository"` +} + +func (s *ActionSpecV1) Selector() api.Selector { + return api.Selector(s.Hostname) +} + +func (s *ActionSpecV1) GetConsumerAttributes() common.Properties { + host, port, base := utils.SplitLocator(s.Hostname) + return common.Properties{ + cpi.ID_TYPE: identity.CONSUMER_TYPE, + identity.ID_HOSTNAME: host, + identity.ID_PATHPREFIX: path.Join(base, s.Repository), + identity.ID_PORT: port, + } +} + +type ActionResultV1 struct { + api.CommonResult `json:",inline"` +} diff --git a/api/oci/extensions/attrs/cacheattr/attr.go b/api/oci/extensions/attrs/cacheattr/attr.go new file mode 100644 index 000000000..054423207 --- /dev/null +++ b/api/oci/extensions/attrs/cacheattr/attr.go @@ -0,0 +1,75 @@ +package cacheattr + +import ( + "fmt" + "os" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ATTR_KEY = "github.com/mandelsoft/oci/cache" + ATTR_SHORT = "cache" +) + +func init() { + datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) +} + +type AttributeType struct{} + +func (a AttributeType) Name() string { + return ATTR_KEY +} + +func (a AttributeType) Description() string { + return ` +*string* +Filesystem folder to use for caching OCI blobs +` +} + +func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { + if _, ok := v.(accessio.BlobCache); !ok { + return nil, fmt.Errorf("accessio.BlobCache required") + } + return nil, nil +} + +func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { + var value string + err := unmarshaller.Unmarshal(data, &value) + if value != "" { + value, err = utils.ResolvePath(value) + if err != nil { + return nil, err + } + // TODO: This should use the virtual filesystem. + err = os.MkdirAll(value, 0o700) + if err == nil { + return accessio.NewStaticBlobCache(value) + } + } else if err == nil { + err = errors.Newf("file path missing") + } + return value, err +} + +//////////////////////////////////////////////////////////////////////////////// + +func Get(ctx datacontext.Context) accessio.BlobCache { + a := ctx.GetAttributes().GetAttribute(ATTR_KEY) + if a == nil { + return nil + } + return a.(accessio.BlobCache) +} + +func Set(ctx datacontext.Context, cache accessio.BlobCache) error { + return ctx.GetAttributes().SetAttribute(ATTR_KEY, cache) +} diff --git a/api/oci/extensions/attrs/cacheattr/attr_test.go b/api/oci/extensions/attrs/cacheattr/attr_test.go new file mode 100644 index 000000000..2a42e035f --- /dev/null +++ b/api/oci/extensions/attrs/cacheattr/attr_test.go @@ -0,0 +1,56 @@ +package cacheattr_test + +import ( + "os" + "reflect" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/extensions/attrs/cacheattr" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/runtime" +) + +var _ = Describe("attribute", func() { + var ctx ocm.Context + var cfgctx config.Context + var cache accessio.BlobCache + + BeforeEach(func() { + var err error + cfgctx = config.WithSharedAttributes(datacontext.New(nil)).New() + credctx := credentials.WithConfigs(cfgctx).New() + ocictx := oci.WithCredentials(credctx).New() + ctx = ocm.WithOCIRepositories(ocictx).New() + cache, err = accessio.NewDefaultBlobCache() + Expect(err).To(Succeed()) + }) + AfterEach(func() { + cache.Unref() + }) + + It("local setting", func() { + Expect(cacheattr.Get(ctx)).To(BeNil()) + Expect(cacheattr.Set(ctx, cache)).To(Succeed()) + Expect(cacheattr.Get(ctx)).To(BeIdenticalTo(cache)) + }) + + It("global setting", func() { + Expect(cacheattr.Get(cfgctx)).To(BeNil()) + Expect(cacheattr.Set(ctx, cache)).To(Succeed()) + Expect(cacheattr.Get(ctx)).To(BeIdenticalTo(cache)) + }) + + It("parses string", func() { + dir := os.TempDir() + cache, err := cacheattr.AttributeType{}.Decode([]byte(dir), runtime.DefaultYAMLEncoding) + Expect(err).To(Succeed()) + Expect(reflect.TypeOf(cache).String()).To(Equal("*accessio.blobCache")) + }) +}) diff --git a/pkg/contexts/oci/attrs/cacheattr/suite_test.go b/api/oci/extensions/attrs/cacheattr/suite_test.go similarity index 100% rename from pkg/contexts/oci/attrs/cacheattr/suite_test.go rename to api/oci/extensions/attrs/cacheattr/suite_test.go diff --git a/api/oci/extensions/attrs/init.go b/api/oci/extensions/attrs/init.go new file mode 100644 index 000000000..abc6564a8 --- /dev/null +++ b/api/oci/extensions/attrs/init.go @@ -0,0 +1,5 @@ +package attrs + +import ( + _ "ocm.software/ocm/api/oci/extensions/attrs/cacheattr" +) diff --git a/pkg/contexts/oci/repositories/artifactset/artifactset.go b/api/oci/extensions/repositories/artifactset/artifactset.go similarity index 96% rename from pkg/contexts/oci/repositories/artifactset/artifactset.go rename to api/oci/extensions/repositories/artifactset/artifactset.go index b708bc2a4..9a1e337c5 100644 --- a/pkg/contexts/oci/repositories/artifactset/artifactset.go +++ b/api/oci/extensions/repositories/artifactset/artifactset.go @@ -7,13 +7,13 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" "github.com/opencontainers/go-digest" - "github.com/open-component-model/ocm/pkg/blobaccess/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/oci/annotations" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi/support" + "ocm.software/ocm/api/oci/annotations" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/oci/cpi/support" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" ) const ( diff --git a/pkg/contexts/oci/repositories/artifactset/artifactset_test.go b/api/oci/extensions/repositories/artifactset/artifactset_test.go similarity index 94% rename from pkg/contexts/oci/repositories/artifactset/artifactset_test.go rename to api/oci/extensions/repositories/artifactset/artifactset_test.go index d49b03050..9b5562383 100644 --- a/pkg/contexts/oci/repositories/artifactset/artifactset_test.go +++ b/api/oci/extensions/repositories/artifactset/artifactset_test.go @@ -8,17 +8,17 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset/testhelper" - . "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf/testhelper" + . "ocm.software/ocm/api/oci/extensions/repositories/artifactset/testhelper" + . "ocm.software/ocm/api/oci/extensions/repositories/ctf/testhelper" "github.com/mandelsoft/goutils/finalizer" "github.com/mandelsoft/vfs/pkg/osfs" "github.com/mandelsoft/vfs/pkg/vfs" - "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/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/mime" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/mime" ) func defaultManifestFill(a *artifactset.ArtifactSet) { diff --git a/pkg/contexts/oci/repositories/artifactset/error.go b/api/oci/extensions/repositories/artifactset/error.go similarity index 100% rename from pkg/contexts/oci/repositories/artifactset/error.go rename to api/oci/extensions/repositories/artifactset/error.go diff --git a/api/oci/extensions/repositories/artifactset/filesystemaccess.go b/api/oci/extensions/repositories/artifactset/filesystemaccess.go new file mode 100644 index 000000000..99387d733 --- /dev/null +++ b/api/oci/extensions/repositories/artifactset/filesystemaccess.go @@ -0,0 +1,45 @@ +package artifactset + +import ( + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/oci/cpi/support" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +type FileSystemBlobAccess struct { + *accessobj.FileSystemBlobAccess +} + +func NewFileSystemBlobAccess(access *accessobj.AccessObject) *FileSystemBlobAccess { + return &FileSystemBlobAccess{accessobj.NewFileSystemBlobAccess(access)} +} + +func (i *FileSystemBlobAccess) GetArtifact(access support.NamespaceAccessImpl, digest digest.Digest) (acc cpi.ArtifactAccess, err error) { + v, err := access.View() + if err != nil { + return nil, err + } + defer v.Close() + _, data, err := i.GetBlobData(digest) + if err == nil { + blob := blobaccess.ForDataAccess("", -1, "", data) + acc, err = support.NewArtifactForBlob(access, blob) + } + return +} + +func (i *FileSystemBlobAccess) AddArtifactBlob(artifact cpi.Artifact) (cpi.BlobAccess, error) { + blob, err := artifact.Blob() + if err != nil { + return nil, err + } + + err = i.AddBlob(blob) + if err != nil { + return nil, err + } + return blob, nil +} diff --git a/api/oci/extensions/repositories/artifactset/format.go b/api/oci/extensions/repositories/artifactset/format.go new file mode 100644 index 000000000..485f97dbd --- /dev/null +++ b/api/oci/extensions/repositories/artifactset/format.go @@ -0,0 +1,269 @@ +package artifactset + +import ( + "sync" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + mime2 "ocm.software/ocm/api/utils/mime" +) + +const ( + // The artifact descriptor name for artifact format. + ArtifactSetDescriptorFileName = "artifact-descriptor.json" + BlobsDirectoryName = "blobs" + + OCIArtifactSetDescriptorFileName = "index.json" + OCILayouFileName = "oci-layout" +) + +var DefaultArtifactSetDescriptorFileName = OCIArtifactSetDescriptorFileName + +func IsOCIDefaultFormat() bool { + return DefaultArtifactSetDescriptorFileName == OCIArtifactSetDescriptorFileName +} + +func DescriptorFileName(format string) string { + switch format { + case FORMAT_OCI: + return OCIArtifactSetDescriptorFileName + case FORMAT_OCM: + return ArtifactSetDescriptorFileName + case "": + return DefaultArtifactSetDescriptorFileName + } + return "" +} + +type accessObjectInfo struct { + accessobj.DefaultAccessObjectInfo +} + +var _ accessobj.AccessObjectInfo = (*accessObjectInfo)(nil) + +func NewAccessObjectInfo(fmts ...string) accessobj.AccessObjectInfo { + a := &accessObjectInfo{ + accessobj.DefaultAccessObjectInfo{ + ObjectTypeName: "artifactset", + ElementDirectoryName: BlobsDirectoryName, + ElementTypeName: "blob", + DescriptorHandlerFactory: NewStateHandler, + }, + } + oci := IsOCIDefaultFormat() + if len(fmts) > 0 { + switch fmts[0] { + case FORMAT_OCM: + oci = false + case FORMAT_OCI: + oci = true + case "": + } + } + if oci { + a.setOCI() + } else { + a.setOCM() + } + return a +} + +func (a *accessObjectInfo) setOCI() { + a.DescriptorFileName = OCIArtifactSetDescriptorFileName + a.AdditionalFiles = []string{OCILayouFileName} +} + +func (a *accessObjectInfo) setOCM() { + a.DescriptorFileName = ArtifactSetDescriptorFileName + a.AdditionalFiles = nil +} + +func (a *accessObjectInfo) setupOCIFS(fs vfs.FileSystem, mode vfs.FileMode) error { + data := `{ + "imageLayoutVersion": "1.0.0" +} +` + return vfs.WriteFile(fs, OCILayouFileName, []byte(data), mode) +} + +func (a *accessObjectInfo) SetupFileSystem(fs vfs.FileSystem, mode vfs.FileMode) error { + if err := a.SetupFor(fs); err != nil { + return err + } + if err := a.DefaultAccessObjectInfo.SetupFileSystem(fs, mode); err != nil { + return err + } + if len(a.AdditionalFiles) > 0 { + return a.setupOCIFS(fs, mode) + } + return nil +} + +func (a *accessObjectInfo) SetupFor(fs vfs.FileSystem) error { + ok, err := vfs.FileExists(fs, OCIArtifactSetDescriptorFileName) + if err != nil { + return err + } + if ok { + a.setOCI() + return nil + } + + ok, err = vfs.FileExists(fs, ArtifactSetDescriptorFileName) + if err != nil { + return err + } + if ok { + a.setOCM() + return nil + } + + ok, err = vfs.FileExists(fs, OCILayouFileName) + if err != nil { + return err + } + if ok { + a.setOCI() + return nil + } + + // keep configured format + return nil +} + +//////////////////////////////////////////////////////////////////////////////// + +type Object = ArtifactSet + +type FormatHandler interface { + accessio.Option + + Format() accessio.FileFormat + + Open(acc accessobj.AccessMode, path string, opts accessio.Options) (*Object, error) + Create(path string, opts accessio.Options, mode vfs.FileMode) (*Object, error) + Write(obj *Object, path string, opts accessio.Options, mode vfs.FileMode) error +} + +type formatHandler struct { + accessobj.FormatHandler +} + +var ( + FormatDirectory = RegisterFormat(accessobj.FormatDirectory) + FormatTAR = RegisterFormat(accessobj.FormatTAR) + FormatTGZ = RegisterFormat(accessobj.FormatTGZ) +) + +//////////////////////////////////////////////////////////////////////////////// + +var ( + fileFormats = map[accessio.FileFormat]FormatHandler{} + lock sync.RWMutex +) + +func RegisterFormat(f accessobj.FormatHandler) FormatHandler { + lock.Lock() + defer lock.Unlock() + h := &formatHandler{f} + fileFormats[f.Format()] = h + return h +} + +func GetFormats() []string { + lock.RLock() + defer lock.RUnlock() + return accessio.GetFormatsFor(fileFormats) +} + +func GetFormat(name accessio.FileFormat) FormatHandler { + lock.RLock() + defer lock.RUnlock() + return fileFormats[name] +} + +func SupportedFormats() []accessio.FileFormat { + lock.RLock() + defer lock.RUnlock() + result := make([]accessio.FileFormat, 0, len(fileFormats)) + for f := range fileFormats { + result = append(result, f) + } + return result +} + +//////////////////////////////////////////////////////////////////////////////// + +func OpenFromBlob(acc accessobj.AccessMode, blob blobaccess.BlobAccess, opts ...accessio.Option) (*Object, error) { + return OpenFromDataAccess(acc, blob.MimeType(), blob, opts...) +} + +func OpenFromDataAccess(acc accessobj.AccessMode, mime string, data blobaccess.DataAccess, opts ...accessio.Option) (*Object, error) { + o, err := accessio.AccessOptions(nil, opts...) + if err != nil { + return nil, err + } + if o.GetFile() != nil || o.GetReader() != nil { + return nil, errors.ErrInvalid("file or reader option not possible for blob access") + } + reader, err := data.Reader() + if err != nil { + return nil, err + } + defer reader.Close() + o.SetReader(reader) + fmt := accessio.FormatTar + + if mime2.IsGZip(mime) { + fmt = accessio.FormatTGZ + } + o.SetFileFormat(fmt) + return Open(acc&accessobj.ACC_READONLY, "", 0, o) +} + +func Open(acc accessobj.AccessMode, path string, mode vfs.FileMode, olist ...accessio.Option) (*Object, error) { + o, create, err := accessobj.HandleAccessMode(acc, path, &Options{}, olist...) + if err != nil { + return nil, err + } + h, ok := fileFormats[*o.GetFileFormat()] + if !ok { + return nil, errors.ErrUnknown(accessobj.KIND_FILEFORMAT, o.GetFileFormat().String()) + } + if create { + return h.Create(path, o, mode) + } + return h.Open(acc, path, o) +} + +func Create(acc accessobj.AccessMode, path string, mode vfs.FileMode, opts ...accessio.Option) (*Object, error) { + o, err := accessio.AccessOptions(&Options{}, opts...) + if err != nil { + return nil, err + } + o.DefaultFormat(accessio.FormatDirectory) + h, ok := fileFormats[*o.GetFileFormat()] + if !ok { + return nil, errors.ErrUnknown(accessobj.KIND_FILEFORMAT, o.GetFileFormat().String()) + } + return h.Create(path, o, mode) +} + +//////////////////////////////////////////////////////////////////////////////// + +func (h *formatHandler) Open(acc accessobj.AccessMode, path string, opts accessio.Options) (*Object, error) { + return _Wrap(h.FormatHandler.Open(NewAccessObjectInfo(GetFormatVersion(opts)), acc, path, opts)) +} + +func (h *formatHandler) Create(path string, opts accessio.Options, mode vfs.FileMode) (*Object, error) { + return _Wrap(h.FormatHandler.Create(NewAccessObjectInfo(GetFormatVersion(opts)), path, opts, mode)) +} + +// WriteToFilesystem writes the current object to a filesystem. +func (h *formatHandler) Write(obj *Object, path string, opts accessio.Options, mode vfs.FileMode) error { + return h.FormatHandler.Write(obj.container.base.Access(), path, opts, mode) +} diff --git a/api/oci/extensions/repositories/artifactset/options.go b/api/oci/extensions/repositories/artifactset/options.go new file mode 100644 index 000000000..2760c0168 --- /dev/null +++ b/api/oci/extensions/repositories/artifactset/options.go @@ -0,0 +1,77 @@ +package artifactset + +import ( + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/utils/accessio" +) + +type Options struct { + accessio.StandardOptions + + FormatVersion string `json:"formatVersion,omitempty"` +} + +func NewOptions(olist ...accessio.Option) (*Options, error) { + opts := &Options{} + err := accessio.ApplyOptions(opts, olist...) + if err != nil { + return nil, err + } + return opts, nil +} + +type FormatVersionOption interface { + SetFormatVersion(string) + GetFormatVersion() string +} + +func GetFormatVersion(opts accessio.Options) string { + if o, ok := opts.(FormatVersionOption); ok { + return o.GetFormatVersion() + } + return "" +} + +var _ FormatVersionOption = (*Options)(nil) + +func (o *Options) SetFormatVersion(s string) { + o.FormatVersion = s +} + +func (o *Options) GetFormatVersion() string { + return o.FormatVersion +} + +func (o *Options) ApplyOption(opts accessio.Options) error { + err := o.StandardOptions.ApplyOption(opts) + if err != nil { + return err + } + if o.FormatVersion != "" { + if s, ok := opts.(FormatVersionOption); ok { + s.SetFormatVersion(o.FormatVersion) + } else { + return errors.ErrNotSupported("format version option") + } + } + return nil +} + +type optFmt struct { + format string +} + +var _ accessio.Option = (*optFmt)(nil) + +func StructureFormat(fmt string) accessio.Option { + return &optFmt{fmt} +} + +func (o *optFmt) ApplyOption(opts accessio.Options) error { + if s, ok := opts.(FormatVersionOption); ok { + s.SetFormatVersion(o.format) + return nil + } + return errors.ErrNotSupported("format version option") +} diff --git a/api/oci/extensions/repositories/artifactset/repo_test.go b/api/oci/extensions/repositories/artifactset/repo_test.go new file mode 100644 index 000000000..afd9c5bbf --- /dev/null +++ b/api/oci/extensions/repositories/artifactset/repo_test.go @@ -0,0 +1,68 @@ +package artifactset_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/helper/builder" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/mime" +) + +var _ = Describe("", func() { + var env *builder.Builder + + BeforeEach(func() { + env = builder.NewBuilder() + // ocmlog.Context().AddRule(logging.NewConditionRule(logging.DebugLevel, accessio.ALLOC_REALM)) + }) + + AfterEach(func() { + env.Cleanup() + }) + + It("maps artifact set to repo", func() { + env.ArtifactSet("/tmp/set", accessio.FormatDirectory, func() { + env.Manifest("v1", func() { + env.Config(func() { + env.BlobStringData(mime.MIME_JSON, "{}") + }) + env.Layer(func() { + env.BlobStringData(mime.MIME_OCTET, "testdata") + }) + }) + }) + + spec, err := artifactset.NewRepositorySpec(accessobj.ACC_READONLY, "/tmp/set", accessio.PathFileSystem(env)) + Expect(err).To(Succeed()) + + r, err := cpi.DefaultContext().RepositoryForSpec(spec) + Expect(err).To(Succeed()) + defer Close(r) + ns, err := r.LookupNamespace("") + Expect(err).To(Succeed()) + defer Close(ns) + + Expect(ns.ListTags()).To(Equal([]string{"v1"})) + + a, err := ns.GetArtifact("v1") + Expect(err).To(Succeed()) + defer Close(a) + + Expect(a.IsManifest()).To(BeTrue()) + m := a.ManifestAccess() + + cfg, err := m.GetConfigBlob() + Expect(err).To(Succeed()) + Expect(cfg.Get()).To(Equal([]byte("{}"))) + + Expect(len(m.GetDescriptor().Layers)).To(Equal(1)) + blob, err := m.GetBlob(m.GetDescriptor().Layers[0].Digest) + Expect(err).To(Succeed()) + Expect(blob.Get()).To(Equal([]byte("testdata"))) + }) +}) diff --git a/api/oci/extensions/repositories/artifactset/repository.go b/api/oci/extensions/repositories/artifactset/repository.go new file mode 100644 index 000000000..23368b40f --- /dev/null +++ b/api/oci/extensions/repositories/artifactset/repository.go @@ -0,0 +1,111 @@ +package artifactset + +import ( + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/utils/accessio" +) + +type RepositoryImpl struct { + cpi.RepositoryImplBase + spec *RepositorySpec + arch *ArtifactSet +} + +var _ cpi.RepositoryImpl = (*RepositoryImpl)(nil) + +func NewRepository(ctx cpi.Context, s *RepositorySpec) (cpi.Repository, error) { + if s.PathFileSystem == nil { + s.PathFileSystem = vfsattr.Get(ctx) + } + r := &RepositoryImpl{ + RepositoryImplBase: cpi.NewRepositoryImplBase(ctx), + spec: s, + } + _, err := r.open() + if err != nil { + return nil, err + } + return cpi.NewRepository(r, "OCI artifactset"), nil +} + +func (r *RepositoryImpl) Get() *ArtifactSet { + if r.arch != nil { + return r.arch + } + return nil +} + +func (r *RepositoryImpl) open() (*ArtifactSet, error) { + a, err := Open(r.spec.AccessMode, r.spec.FilePath, 0o700, &Options{}, &r.spec.Options, accessio.PathFileSystem(r.spec.PathFileSystem)) + if err != nil { + return nil, err + } + r.arch = a + return a, nil +} + +func (r *RepositoryImpl) GetSpecification() cpi.RepositorySpec { + return r.spec +} + +func (r *RepositoryImpl) NamespaceLister() cpi.NamespaceLister { + return anonymous +} + +func (r *RepositoryImpl) ExistsArtifact(name string, ref string) (bool, error) { + if name != "" { + return false, nil + } + return r.arch.HasArtifact(ref) +} + +func (r *RepositoryImpl) LookupArtifact(name string, ref string) (cpi.ArtifactAccess, error) { + if name != "" { + return nil, cpi.ErrUnknownArtifact(name, ref) + } + return r.arch.GetArtifact(ref) +} + +func (r *RepositoryImpl) LookupNamespace(name string) (cpi.NamespaceAccess, error) { + if name != "" { + return nil, errors.ErrNotSupported("namespace", name) + } + return r.arch.Dup() +} + +func (r RepositoryImpl) Close() error { + if r.arch != nil { + return r.arch.Close() + } + return nil +} + +// NamespaceLister handles the namespaces provided by an artifact set. +// This is always single anonymous namespace, which by ddefinition +// is the empty string. +type NamespaceLister struct{} + +var anonymous cpi.NamespaceLister = &NamespaceLister{} + +// NumNamespaces returns the number of namespaces with a given prefix +// for an artifact set. This is either one (the anonymous namespace) if +// the prefix is empty (all namespaces) or zero if a prefix is given. +func (n *NamespaceLister) NumNamespaces(prefix string) (int, error) { + if prefix == "" { + return 1, nil + } + return 0, nil +} + +// GetNamespaces returns namespaces with a given prefix. +// This is the anonymous namespace ("") for an empty prefix +// or no namespace at all if a prefix is given. +func (n *NamespaceLister) GetNamespaces(prefix string, closure bool) ([]string, error) { + if prefix == "" { + return []string{""}, nil + } + return nil, nil +} diff --git a/api/oci/extensions/repositories/artifactset/state.go b/api/oci/extensions/repositories/artifactset/state.go new file mode 100644 index 000000000..8901e4684 --- /dev/null +++ b/api/oci/extensions/repositories/artifactset/state.go @@ -0,0 +1,15 @@ +package artifactset + +import ( + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/utils/accessobj" +) + +// NewStateHandler implements the factory interface for the artifact set +// state descriptor handling +// Basically this is an index state. +func NewStateHandler(fs vfs.FileSystem) accessobj.StateHandler { + return &cpi.IndexStateHandler{} +} diff --git a/pkg/contexts/oci/repositories/artifactset/suite_test.go b/api/oci/extensions/repositories/artifactset/suite_test.go similarity index 100% rename from pkg/contexts/oci/repositories/artifactset/suite_test.go rename to api/oci/extensions/repositories/artifactset/suite_test.go diff --git a/pkg/contexts/oci/repositories/artifactset/testhelper/formats.go b/api/oci/extensions/repositories/artifactset/testhelper/formats.go similarity index 86% rename from pkg/contexts/oci/repositories/artifactset/testhelper/formats.go rename to api/oci/extensions/repositories/artifactset/testhelper/formats.go index 3ac44a18b..3a4ad0922 100644 --- a/pkg/contexts/oci/repositories/artifactset/testhelper/formats.go +++ b/api/oci/extensions/repositories/artifactset/testhelper/formats.go @@ -5,7 +5,7 @@ import ( . "github.com/onsi/ginkgo/v2" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" ) func TestForAllFormats(msg string, f func(fmt string)) { diff --git a/api/oci/extensions/repositories/artifactset/type.go b/api/oci/extensions/repositories/artifactset/type.go new file mode 100644 index 000000000..512c9bc36 --- /dev/null +++ b/api/oci/extensions/repositories/artifactset/type.go @@ -0,0 +1,88 @@ +package artifactset + +import ( + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + Type = "ArtifactSet" + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +func init() { + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](Type)) + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1)) +} + +const ( + FORMAT_OCI = "oci/v1" + FORMAT_OCM = "ocm/v1" +) + +type RepositorySpec struct { + runtime.ObjectVersionedType `json:",inline"` + Options `json:",inline"` + + // FileFormat is the format of the repository file + FilePath string `json:"filePath"` + // AccessMode can be set to request readonly access or creation + AccessMode accessobj.AccessMode `json:"accessMode,omitempty"` + + FormatVersion string `json:"formatVersion,omitempty"` +} + +// NewRepositorySpec creates a new RepositorySpec. +func NewRepositorySpec(acc accessobj.AccessMode, filePath string, opts ...accessio.Option) (*RepositorySpec, error) { + o, err := accessio.AccessOptions(&Options{}, opts...) + if err != nil { + return nil, err + } + return &RepositorySpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(Type), + FilePath: filePath, + Options: *o.(*Options), + AccessMode: acc, + }, nil +} + +func (s *RepositorySpec) Name() string { + return s.FilePath +} + +func (s *RepositorySpec) GetFormatVersion() string { + if s.FormatVersion == "" { + return FORMAT_OCM + } + return s.FormatVersion +} + +func (s *RepositorySpec) UniformRepositorySpec() *cpi.UniformRepositorySpec { + u := &cpi.UniformRepositorySpec{ + Type: Type, + Info: s.FilePath, + } + return u +} + +func (a *RepositorySpec) GetType() string { + return Type +} + +func (a *RepositorySpec) Repository(ctx cpi.Context, creds credentials.Credentials) (cpi.Repository, error) { + return NewRepository(ctx, a) +} + +func (a *RepositorySpec) AsUniformSpec(cpi.Context) cpi.UniformRepositorySpec { + opts, _ := NewOptions(&a.Options) // now unknown option possible (same Options type) + p, err := vfs.Canonical(opts.GetPathFileSystem(), a.FilePath, false) + if err != nil { + return cpi.UniformRepositorySpec{Type: a.GetKind(), Info: a.FilePath} + } + return cpi.UniformRepositorySpec{Type: a.GetKind(), Info: p} +} diff --git a/api/oci/extensions/repositories/artifactset/uniform.go b/api/oci/extensions/repositories/artifactset/uniform.go new file mode 100644 index 000000000..4711617f4 --- /dev/null +++ b/api/oci/extensions/repositories/artifactset/uniform.go @@ -0,0 +1,49 @@ +package artifactset + +import ( + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" +) + +func init() { + h := &repospechandler{} + cpi.RegisterRepositorySpecHandler(h, "") + cpi.RegisterRepositorySpecHandler(h, Type) +} + +type repospechandler struct{} + +func (h *repospechandler) MapReference(ctx cpi.Context, u *cpi.UniformRepositorySpec) (cpi.RepositorySpec, error) { + path := u.Info + if u.Info == "" { + if u.Host == "" || u.Type == "" { + return nil, nil + } + path = u.Host + } + fs := vfsattr.Get(ctx) + + hint, f := accessobj.MapType(u.TypeHint, Type, accessio.FormatDirectory, false) + if !u.CreateIfMissing { + hint = "" + } + + create, ok, err := accessobj.CheckFile(Type, hint, accessio.TypeForTypeSpec(u.Type) == Type, path, fs, ArtifactSetDescriptorFileName) + if err == nil && !ok { + create, ok, err = accessobj.CheckFile(Type, hint, accessio.TypeForTypeSpec(u.Type) == Type, path, fs, OCIArtifactSetDescriptorFileName) + } + + if !ok || err != nil { + return nil, err + } + + mode := accessobj.ACC_WRITABLE + createHint := accessio.FormatNone + if create { + mode |= accessobj.ACC_CREATE + createHint = f + } + return NewRepositorySpec(mode, path, createHint, accessio.PathFileSystem(fs)) +} diff --git a/pkg/contexts/oci/repositories/artifactset/utils_synthesis.go b/api/oci/extensions/repositories/artifactset/utils_synthesis.go similarity index 90% rename from pkg/contexts/oci/repositories/artifactset/utils_synthesis.go rename to api/oci/extensions/repositories/artifactset/utils_synthesis.go index 17db6a721..2a6bd5641 100644 --- a/pkg/contexts/oci/repositories/artifactset/utils_synthesis.go +++ b/api/oci/extensions/repositories/artifactset/utils_synthesis.go @@ -9,14 +9,14 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" "github.com/opencontainers/go-digest" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/blobaccess/file" - "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/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/contexts/oci/transfer" - "github.com/open-component-model/ocm/pkg/contexts/oci/transfer/filters" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/oci/tools/transfer" + "ocm.software/ocm/api/oci/tools/transfer/filters" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/file" ) const SynthesizedBlobFormat = "+tar+gzip" diff --git a/pkg/contexts/oci/repositories/ctf/README.md b/api/oci/extensions/repositories/ctf/README.md similarity index 100% rename from pkg/contexts/oci/repositories/ctf/README.md rename to api/oci/extensions/repositories/ctf/README.md diff --git a/api/oci/extensions/repositories/ctf/ctf_test.go b/api/oci/extensions/repositories/ctf/ctf_test.go new file mode 100644 index 000000000..405bad923 --- /dev/null +++ b/api/oci/extensions/repositories/ctf/ctf_test.go @@ -0,0 +1,195 @@ +package ctf_test + +import ( + "archive/tar" + "compress/gzip" + "io" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/oci/extensions/repositories/ctf/testhelper" + + "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" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/oci/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess" + ocmlog "ocm.software/ocm/api/utils/logging" + "ocm.software/ocm/api/utils/refmgmt" +) + +var _ = Describe("ctf management", func() { + var tempfs vfs.FileSystem + + var spec *ctf.RepositorySpec + + ocmlog.Context().AddRule(logging.NewConditionRule(logging.TraceLevel, refmgmt.ALLOC_REALM)) + + BeforeEach(func() { + t, err := osfs.NewTempFileSystem() + Expect(err).To(Succeed()) + tempfs = t + + spec, err = ctf.NewRepositorySpec(accessobj.ACC_CREATE, "test", accessio.PathFileSystem(tempfs), accessobj.FormatDirectory) + Expect(err).To(Succeed()) + }) + + AfterEach(func() { + vfs.Cleanup(tempfs) + }) + + It("instantiate filesystem ctf", func() { + var finalize finalizer.Finalizer + defer Defer(finalize.Finalize) + + r := Must(ctf.FormatDirectory.Create(oci.DefaultContext(), "test", &spec.StandardOptions, 0o700)) + finalize.Close(r) + Expect(vfs.DirExists(tempfs, "test/"+ctf.BlobsDirectoryName)).To(BeTrue()) + + sub := finalize.Nested() + n := Must(r.LookupNamespace("mandelsoft/test")) + sub.Close(n) + DefaultManifestFill(n) + Expect(sub.Finalize()).To(Succeed()) + + Expect(r.ExistsArtifact("mandelsoft/test", TAG)).To(BeTrue()) + + art := Must(r.LookupArtifact("mandelsoft/test", TAG)) + Close(art, "art") + + Expect(finalize.Finalize()).To(Succeed()) + + Expect(vfs.FileExists(tempfs, "test/"+ctf.ArtifactIndexFileName)).To(BeTrue()) + + infos, err := vfs.ReadDir(tempfs, "test/"+artifactset.BlobsDirectoryName) + Expect(err).To(Succeed()) + blobs := []string{} + for _, fi := range infos { + blobs = append(blobs, fi.Name()) + } + Expect(blobs).To(ContainElements( + "sha256."+DIGEST_MANIFEST, + "sha256."+DIGEST_CONFIG, + "sha256."+DIGEST_LAYER)) + }) + + It("instantiate filesystem ctf", func() { + r, err := spec.Repository(nil, nil) + Expect(err).To(Succeed()) + Expect(vfs.DirExists(tempfs, "test/"+ctf.BlobsDirectoryName)).To(BeTrue()) + + n, err := r.LookupNamespace("mandelsoft/test") + Expect(err).To(Succeed()) + DefaultManifestFill(n) + + Expect(n.Close()).To(Succeed()) + Expect(r.Close()).To(Succeed()) + Expect(vfs.FileExists(tempfs, "test/"+ctf.ArtifactIndexFileName)).To(BeTrue()) + + infos, err := vfs.ReadDir(tempfs, "test/"+artifactset.BlobsDirectoryName) + Expect(err).To(Succeed()) + blobs := []string{} + for _, fi := range infos { + blobs = append(blobs, fi.Name()) + } + Expect(blobs).To(ContainElements( + "sha256."+DIGEST_MANIFEST, + "sha256."+DIGEST_CONFIG, + "sha256."+DIGEST_LAYER)) + }) + + It("instantiate tgz artifact", func() { + ctf.FormatTGZ.ApplyOption(&spec.StandardOptions) + spec.FilePath = "test.tgz" + r, err := spec.Repository(nil, nil) + Expect(err).To(Succeed()) + + n, err := r.LookupNamespace("mandelsoft/test") + Expect(err).To(Succeed()) + DefaultManifestFill(n) + + Expect(n.Close()).To(Succeed()) + Expect(r.Close()).To(Succeed()) + Expect(vfs.FileExists(tempfs, "test.tgz")).To(BeTrue()) + + file, err := tempfs.Open("test.tgz") + Expect(err).To(Succeed()) + defer file.Close() + zip, err := gzip.NewReader(file) + Expect(err).To(Succeed()) + defer zip.Close() + tr := tar.NewReader(zip) + + files := []string{} + for { + header, err := tr.Next() + if err != nil { + if err == io.EOF { + break + } + Fail(err.Error()) + } + + switch header.Typeflag { + case tar.TypeDir: + Expect(header.Name).To(Equal(artifactset.BlobsDirectoryName)) + case tar.TypeReg: + files = append(files, header.Name) + } + } + Expect(files).To(ContainElements( + ctf.ArtifactIndexFileName, + "blobs/sha256."+DIGEST_MANIFEST, + "blobs/sha256."+DIGEST_CONFIG, + "blobs/sha256."+DIGEST_LAYER)) + }) + + Context("manifest", func() { + It("read from filesystem ctf", func() { + r, err := spec.Repository(nil, nil) + Expect(err).To(Succeed()) + Expect(vfs.DirExists(tempfs, "test/"+ctf.BlobsDirectoryName)).To(BeTrue()) + n, err := r.LookupNamespace("mandelsoft/test") + Expect(err).To(Succeed()) + DefaultManifestFill(n) + Expect(n.Close()).To(Succeed()) + Expect(r.Close()).To(Succeed()) + + r, err = ctf.Open(nil, accessobj.ACC_READONLY, "test", 0, accessio.PathFileSystem(tempfs)) + Expect(err).To(Succeed()) + defer r.Close() + + n, err = r.LookupNamespace("mandelsoft/test") + Expect(err).To(Succeed()) + + art, err := n.GetArtifact("sha256:" + DIGEST_MANIFEST) + Expect(err).To(Succeed()) + CheckArtifact(art) + art, err = n.GetArtifact(TAG) + Expect(err).To(Succeed()) + b, err := art.GetDescriptor().ToBlobAccess() + Expect(err).To(Succeed()) + Expect(b.Digest()).To(Equal(digest.Digest("sha256:" + DIGEST_MANIFEST))) + + _, err = n.GetArtifact("dummy") + Expect(err).To(Equal(errors.ErrNotFound(cpi.KIND_OCIARTIFACT, "dummy", "mandelsoft/test"))) + + Expect(n.AddBlob(blobaccess.ForString("", "dummy"))).To(Equal(accessobj.ErrReadOnly)) + + n, err = r.LookupNamespace("mandelsoft/other") + Expect(err).To(Succeed()) + _, err = n.GetArtifact("sha256:" + DIGEST_MANIFEST) + Expect(err).To(Equal(errors.ErrNotFound(cpi.KIND_OCIARTIFACT, "sha256:"+DIGEST_MANIFEST, "mandelsoft/other"))) + }) + }) +}) diff --git a/api/oci/extensions/repositories/ctf/format.go b/api/oci/extensions/repositories/ctf/format.go new file mode 100644 index 000000000..5143cb2f3 --- /dev/null +++ b/api/oci/extensions/repositories/ctf/format.go @@ -0,0 +1,171 @@ +package ctf + +import ( + "sort" + "strings" + "sync" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/oci/extensions/repositories/ctf/format" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +const ( + ArtifactIndexFileName = format.ArtifactIndexFileName + BlobsDirectoryName = format.BlobsDirectoryName +) + +var accessObjectInfo = &accessobj.DefaultAccessObjectInfo{ + DescriptorFileName: ArtifactIndexFileName, + ObjectTypeName: "repository", + ElementDirectoryName: BlobsDirectoryName, + ElementTypeName: "blob", + DescriptorHandlerFactory: NewStateHandler, +} + +type Object = Repository + +type FormatHandler interface { + accessio.Option + + Format() accessio.FileFormat + + Open(ctx cpi.ContextProvider, acc accessobj.AccessMode, path string, opts accessio.Options) (*Object, error) + Create(ctx cpi.ContextProvider, path string, opts accessio.Options, mode vfs.FileMode) (*Object, error) + Write(obj *Object, path string, opts accessio.Options, mode vfs.FileMode) error +} + +type formatHandler struct { + accessobj.FormatHandler +} + +var ( + FormatDirectory = RegisterFormat(accessobj.FormatDirectory) + FormatTAR = RegisterFormat(accessobj.FormatTAR) + FormatTGZ = RegisterFormat(accessobj.FormatTGZ) +) + +//////////////////////////////////////////////////////////////////////////////// + +var ( + fileFormats = map[accessio.FileFormat]FormatHandler{} + lock sync.RWMutex +) + +func RegisterFormat(f accessobj.FormatHandler) FormatHandler { + lock.Lock() + defer lock.Unlock() + h := &formatHandler{f} + fileFormats[f.Format()] = h + return h +} + +func GetFormats() []string { + lock.RLock() + defer lock.RUnlock() + return accessio.GetFormatsFor(fileFormats) +} + +func GetFormat(name accessio.FileFormat) FormatHandler { + lock.RLock() + defer lock.RUnlock() + return fileFormats[name] +} + +func SupportedFormats() []accessio.FileFormat { + lock.RLock() + defer lock.RUnlock() + result := make([]accessio.FileFormat, 0, len(fileFormats)) + for f := range fileFormats { + result = append(result, f) + } + sort.Slice(result, func(i, j int) bool { return strings.Compare(string(result[i]), string(result[j])) < 0 }) + return result +} + +//////////////////////////////////////////////////////////////////////////////// + +const ( + ACC_CREATE = accessobj.ACC_CREATE + ACC_WRITABLE = accessobj.ACC_WRITABLE + ACC_READONLY = accessobj.ACC_READONLY +) + +func OpenFromBlob(ctx cpi.ContextProvider, acc accessobj.AccessMode, blob blobaccess.BlobAccess, opts ...accessio.Option) (*Object, error) { + o, err := accessio.AccessOptions(nil, opts...) + if err != nil { + return nil, err + } + if o.GetFile() != nil || o.GetReader() != nil { + return nil, errors.ErrInvalid("file or reader option nor possible for blob access") + } + reader, err := blob.Reader() + if err != nil { + return nil, err + } + defer reader.Close() + o.SetReader(reader) + fmt := accessio.FormatTar + mime := blob.MimeType() + if strings.HasSuffix(mime, "+gzip") { + fmt = accessio.FormatTGZ + } + o.SetFileFormat(fmt) + return Open(ctx, acc&accessobj.ACC_READONLY, "", 0, o) +} + +func Open(ctx cpi.ContextProvider, acc accessobj.AccessMode, path string, mode vfs.FileMode, opts ...accessio.Option) (*Object, error) { + o, create, err := accessobj.HandleAccessMode(acc, path, nil, opts...) + if err != nil { + return nil, err + } + h, ok := fileFormats[*o.GetFileFormat()] + if !ok { + return nil, errors.ErrUnknown(accessobj.KIND_FILEFORMAT, o.GetFileFormat().String()) + } + if create { + return h.Create(cpi.FromProvider(ctx), path, o, mode) + } + return h.Open(cpi.FromProvider(ctx), acc, path, o) +} + +func Create(ctx cpi.ContextProvider, acc accessobj.AccessMode, path string, mode vfs.FileMode, opts ...accessio.Option) (*Object, error) { + o, err := accessio.AccessOptions(nil, opts...) + if err != nil { + return nil, err + } + o.DefaultFormat(accessio.FormatDirectory) + h, ok := fileFormats[*o.GetFileFormat()] + if !ok { + return nil, errors.ErrUnknown(accessobj.KIND_FILEFORMAT, o.GetFileFormat().String()) + } + return h.Create(ctx.OCIContext(), path, o, mode) +} + +func (h *formatHandler) Open(ctx cpi.ContextProvider, acc accessobj.AccessMode, path string, opts accessio.Options) (*Object, error) { + obj, err := h.FormatHandler.Open(accessObjectInfo, acc, path, opts) + if err != nil { + return nil, err + } + spec, err := NewRepositorySpec(acc, path, opts) + return _Wrap(ctx, spec, obj, err) +} + +func (h *formatHandler) Create(ctx cpi.ContextProvider, path string, opts accessio.Options, mode vfs.FileMode) (*Object, error) { + obj, err := h.FormatHandler.Create(accessObjectInfo, path, opts, mode) + if err != nil { + return nil, err + } + spec, err := NewRepositorySpec(accessobj.ACC_CREATE, path, opts) + return _Wrap(ctx, spec, obj, err) +} + +// WriteToFilesystem writes the current object to a filesystem. +func (h *formatHandler) Write(obj *Object, path string, opts accessio.Options, mode vfs.FileMode) error { + return h.FormatHandler.Write(obj.impl.base.Access(), path, opts, mode) +} diff --git a/api/oci/extensions/repositories/ctf/format/const.go b/api/oci/extensions/repositories/ctf/format/const.go new file mode 100644 index 000000000..24a646d9a --- /dev/null +++ b/api/oci/extensions/repositories/ctf/format/const.go @@ -0,0 +1,20 @@ +package format + +import ( + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/utils/accessobj" +) + +const ( + DirMode = accessobj.DirMode + FileMode = accessobj.FileMode +) + +var ModTime = accessobj.ModTime + +const ( + // BlobsDirectoryName is the name of the directory holding the artifact archives. + BlobsDirectoryName = artifactset.BlobsDirectoryName + // ArtifactIndexFileName is the artifact index descriptor name for CommanTransportFormat. + ArtifactIndexFileName = "artifact-index.json" +) diff --git a/pkg/contexts/oci/repositories/ctf/formatspec.md b/api/oci/extensions/repositories/ctf/formatspec.md similarity index 100% rename from pkg/contexts/oci/repositories/ctf/formatspec.md rename to api/oci/extensions/repositories/ctf/formatspec.md diff --git a/pkg/contexts/oci/repositories/ctf/index/ctfindex.go b/api/oci/extensions/repositories/ctf/index/ctfindex.go similarity index 100% rename from pkg/contexts/oci/repositories/ctf/index/ctfindex.go rename to api/oci/extensions/repositories/ctf/index/ctfindex.go diff --git a/api/oci/extensions/repositories/ctf/index/index.go b/api/oci/extensions/repositories/ctf/index/index.go new file mode 100644 index 000000000..dabbd888d --- /dev/null +++ b/api/oci/extensions/repositories/ctf/index/index.go @@ -0,0 +1,216 @@ +package index + +import ( + "sort" + "strings" + "sync" + + "github.com/opencontainers/go-digest" + "github.com/opencontainers/image-spec/specs-go" + + "ocm.software/ocm/api/oci/cpi" +) + +type RepositoryIndex struct { + lock sync.RWMutex + byDigest map[digest.Digest][]*ArtifactMeta + byRepository map[string]map[string]*ArtifactMeta +} + +func NewMeta(repo string, tag string, digest digest.Digest) *ArtifactMeta { + return &ArtifactMeta{ + Repository: repo, + Tag: tag, + Digest: digest, + } +} + +func NewRepositoryIndex() *RepositoryIndex { + return &RepositoryIndex{ + byDigest: map[digest.Digest][]*ArtifactMeta{}, + byRepository: map[string]map[string]*ArtifactMeta{}, + } +} + +func (r *RepositoryIndex) RepositoryList() []string { + result := []string{} + for k := range r.byRepository { + result = append(result, k) + } + return result +} + +func (r *RepositoryIndex) AddTagsFor(repo string, digest digest.Digest, tags ...string) error { + r.lock.Lock() + defer r.lock.Unlock() + + a := r.getArtifactInfo(repo, digest.String()) + if a == nil { + return cpi.ErrUnknownArtifact(repo, digest.String()) + } + for _, tag := range tags { + n := *a + n.Tag = tag + r.addArtifactInfo(n) + } + return nil +} + +func (r *RepositoryIndex) AddArtifactInfo(n *ArtifactMeta) { + r.lock.Lock() + defer r.lock.Unlock() + r.addArtifactInfo(*n) +} + +func (r *RepositoryIndex) addArtifactInfo(m ArtifactMeta) { + repos := r.byRepository[m.Repository] + if len(repos) == 0 { + repos = map[string]*ArtifactMeta{} + r.byRepository[m.Repository] = repos + } + + list := r.byDigest[m.Digest] + if list == nil { + list = []*ArtifactMeta{&m} + } else { + for _, e := range list { + if m.Repository == e.Repository && m.Digest == e.Digest { + if e.Tag == "" || e.Tag == m.Tag { + e.Tag = m.Tag + if e.Tag != "" { + repos[m.Tag] = e + } + return + } + } + } + list = append(list, &m) + } + r.byDigest[m.Digest] = list + + repos["@"+m.Digest.String()] = &m + if m.Tag != "" { + repos[m.Tag] = &m + } +} + +func (r *RepositoryIndex) HasArtifact(repo, tag string) bool { + r.lock.RLock() + defer r.lock.RUnlock() + repos := r.byRepository[repo] + if repos == nil { + return false + } + m := repos[tag] + return m != nil +} + +func (r *RepositoryIndex) GetTags(repo string) []string { + r.lock.RLock() + defer r.lock.RUnlock() + + repos := r.byRepository[repo] + if repos == nil { + return nil + } + result := []string{} + digests := map[digest.Digest]bool{} + for t, a := range repos { + if !strings.HasPrefix(t, "@") { + result = append(result, t) + digests[a.Digest] = true + } else if !digests[a.Digest] { + digests[a.Digest] = false + } + } + /* TODO: how to query untagged entries at api level + for d, found := range digests { + if !found { + result = append(result, "@"+d.String()) + } + } + */ + return result +} + +func (r *RepositoryIndex) GetArtifacts(repo string) []string { + r.lock.RLock() + defer r.lock.RUnlock() + + repos := r.byRepository[repo] + if repos == nil { + return nil + } + result := []string{} + for t := range repos { + result = append(result, t) + } + return result +} + +func (r *RepositoryIndex) GetArtifactInfos(digest digest.Digest) []*ArtifactMeta { + r.lock.RLock() + defer r.lock.RUnlock() + return r.byDigest[digest] +} + +func (r *RepositoryIndex) GetArtifactInfo(repo, reference string) *ArtifactMeta { + r.lock.RLock() + defer r.lock.RUnlock() + return r.getArtifactInfo(repo, reference) +} + +func (r *RepositoryIndex) getArtifactInfo(repo, reference string) *ArtifactMeta { + repos := r.byRepository[repo] + if repos == nil { + return nil + } + m := repos[reference] + if m == nil && !strings.HasPrefix(reference, "@") { + m = repos["@"+reference] + } + if m == nil { + return nil + } + result := *m + return &result +} + +func (r *RepositoryIndex) GetDescriptor() *ArtifactIndex { + r.lock.RLock() + defer r.lock.RUnlock() + index := &ArtifactIndex{ + Versioned: specs.Versioned{SchemaVersion}, + } + + repos := make([]string, len(r.byRepository)) + i := 0 + for repo := range r.byRepository { + repos[i] = repo + i++ + } + sort.Strings(repos) + for _, name := range repos { + repo := r.byRepository[name] + versions := make([]string, len(repo)) + i := 0 + for vers := range repo { + versions[i] = vers + i++ + } + sort.Strings(versions) + + for _, name := range versions { + vers := repo[name] + if "@"+vers.Digest.String() != name || vers.Tag == "" { + d := &ArtifactMeta{ + Repository: vers.Repository, + Tag: vers.Tag, + Digest: vers.Digest, + } + index.Index = append(index.Index, *d) + } + } + } + return index +} diff --git a/pkg/contexts/oci/repositories/ctf/index/index_test.go b/api/oci/extensions/repositories/ctf/index/index_test.go similarity index 98% rename from pkg/contexts/oci/repositories/ctf/index/index_test.go rename to api/oci/extensions/repositories/ctf/index/index_test.go index e0a0df01a..9ccfc1a49 100644 --- a/pkg/contexts/oci/repositories/ctf/index/index_test.go +++ b/api/oci/extensions/repositories/ctf/index/index_test.go @@ -3,7 +3,7 @@ package index_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf/index" + . "ocm.software/ocm/api/oci/extensions/repositories/ctf/index" ) var _ = Describe("index", func() { diff --git a/pkg/contexts/oci/repositories/ctf/index/suite_test.go b/api/oci/extensions/repositories/ctf/index/suite_test.go similarity index 100% rename from pkg/contexts/oci/repositories/ctf/index/suite_test.go rename to api/oci/extensions/repositories/ctf/index/suite_test.go diff --git a/api/oci/extensions/repositories/ctf/namespace.go b/api/oci/extensions/repositories/ctf/namespace.go new file mode 100644 index 000000000..ac57fd3cb --- /dev/null +++ b/api/oci/extensions/repositories/ctf/namespace.go @@ -0,0 +1,100 @@ +package ctf + +import ( + "github.com/mandelsoft/goutils/errors" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/oci/cpi/support" + "ocm.software/ocm/api/oci/extensions/repositories/ctf/index" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +func NewNamespace(repo *RepositoryImpl, name string) (cpi.NamespaceAccess, error) { + return support.NewNamespaceAccess(name, newNamespaceContainer(repo), repo, "CTF namespace") +} + +type namespaceContainer struct { + impl support.NamespaceAccessImpl + repo *RepositoryImpl +} + +var _ support.NamespaceContainer = (*namespaceContainer)(nil) + +func newNamespaceContainer(repo *RepositoryImpl) support.NamespaceContainer { + return &namespaceContainer{ + repo: repo, + } +} + +func (n *namespaceContainer) SetImplementation(impl support.NamespaceAccessImpl) { + n.impl = impl +} + +func (n *namespaceContainer) IsReadOnly() bool { + return n.repo.IsReadOnly() +} + +func (n *namespaceContainer) Close() error { + return nil +} + +func (n *namespaceContainer) GetBlobDescriptor(digest digest.Digest) *cpi.Descriptor { + return nil +} + +func (n *namespaceContainer) ListTags() ([]string, error) { + return n.repo.getIndex().GetTags(n.impl.GetNamespace()), nil // return digests as tags, also +} + +func (n *namespaceContainer) GetBlobData(digest digest.Digest) (int64, cpi.DataAccess, error) { + return n.repo.base.GetBlobData(digest) +} + +func (n *namespaceContainer) AddBlob(blob cpi.BlobAccess) error { + n.repo.base.Lock() + defer n.repo.base.Unlock() + + return n.repo.base.AddBlob(blob) +} + +func (n *namespaceContainer) GetArtifact(i support.NamespaceAccessImpl, vers string) (cpi.ArtifactAccess, error) { + meta := n.repo.getIndex().GetArtifactInfo(n.impl.GetNamespace(), vers) + if meta == nil { + return nil, errors.ErrNotFound(cpi.KIND_OCIARTIFACT, vers, n.impl.GetNamespace()) + } + return n.repo.base.GetArtifact(i, meta.Digest) +} + +func (n *namespaceContainer) HasArtifact(vers string) (bool, error) { + meta := n.repo.getIndex().GetArtifactInfo(n.impl.GetNamespace(), vers) + return meta != nil, nil +} + +func (n *namespaceContainer) AddArtifact(artifact cpi.Artifact, tags ...string) (access blobaccess.BlobAccess, err error) { + n.repo.base.Lock() + defer n.repo.base.Unlock() + + blob, err := n.repo.base.AddArtifactBlob(artifact) + if err != nil { + return nil, err + } + n.repo.getIndex().AddArtifactInfo(&index.ArtifactMeta{ + Repository: n.impl.GetNamespace(), + Tag: "", + Digest: blob.Digest(), + }) + return blob, n.AddTags(blob.Digest(), tags...) +} + +func (n *namespaceContainer) AddTags(digest digest.Digest, tags ...string) error { + return n.repo.getIndex().AddTagsFor(n.impl.GetNamespace(), digest, tags...) +} + +func (n *namespaceContainer) NewArtifact(i support.NamespaceAccessImpl, art ...cpi.Artifact) (cpi.ArtifactAccess, error) { + if n.IsReadOnly() { + return nil, accessio.ErrReadOnly + } + return support.NewArtifact(i, art...) +} diff --git a/api/oci/extensions/repositories/ctf/repository.go b/api/oci/extensions/repositories/ctf/repository.go new file mode 100644 index 000000000..6ec2b0a89 --- /dev/null +++ b/api/oci/extensions/repositories/ctf/repository.go @@ -0,0 +1,143 @@ +package ctf + +import ( + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/oci/extensions/repositories/ctf/index" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/refmgmt" +) + +/* + A common transport archive is just a folder with artifact archives. + in tar format and an index.json file. The name of the archive + is the digest of the artifact descriptor. + + The artifact archive is a filesystem structure with a file + artifact-descriptor.json and a folder blobs containing + the flat blob files with the name according to the blob digest. + + Digests used as filename will replace the ":" by a "." +*/ + +type Repository struct { + cpi.Repository + impl *RepositoryImpl +} + +func (r *Repository) Write(path string, mode vfs.FileMode, opts ...accessio.Option) error { + if r.IsClosed() { + return cpi.ErrClosed + } + return r.impl.Write(path, mode, opts...) +} + +func (r *Repository) Close() error { // why ??? + return r.Repository.Close() +} + +//////////////////////////////////////////////////////////////////////////////// + +// RepositoryImpl is closed, if all views are released. +type RepositoryImpl struct { + cpi.RepositoryImplBase + + spec *RepositorySpec + base *artifactset.FileSystemBlobAccess +} + +var _ cpi.RepositoryImpl = (*RepositoryImpl)(nil) + +// New returns a new representation based repository. +func New(ctx cpi.Context, spec *RepositorySpec, setup accessobj.Setup, closer accessobj.Closer, mode vfs.FileMode) (*Repository, error) { + if spec.GetPathFileSystem() == nil { + spec.SetPathFileSystem(vfsattr.Get(ctx)) + } + base, err := accessobj.NewAccessObject(accessObjectInfo, spec.AccessMode, spec.GetRepresentation(), setup, closer, mode) + return _Wrap(ctx, spec, base, err) +} + +func _Wrap(ctx cpi.ContextProvider, spec *RepositorySpec, obj *accessobj.AccessObject, err error) (*Repository, error) { + if err != nil { + return nil, err + } + impl := &RepositoryImpl{ + RepositoryImplBase: cpi.NewRepositoryImplBase(cpi.FromProvider(ctx)), + spec: spec, + base: artifactset.NewFileSystemBlobAccess(obj), + } + r := cpi.NewRepository(impl, "OCI CTF") + return &Repository{r, impl}, nil +} + +func (r *RepositoryImpl) GetSpecification() cpi.RepositorySpec { + return r.spec +} + +func (r *RepositoryImpl) NamespaceLister() cpi.NamespaceLister { + return r +} + +func (r *RepositoryImpl) NumNamespaces(prefix string) (int, error) { + return len(cpi.FilterByNamespacePrefix(prefix, r.getIndex().RepositoryList())), nil +} + +func (r *RepositoryImpl) GetNamespaces(prefix string, closure bool) ([]string, error) { + return cpi.FilterChildren(closure, prefix, r.getIndex().RepositoryList()), nil +} + +//////////////////////////////////////////////////////////////////////////////// +// forward + +func (r *RepositoryImpl) IsReadOnly() bool { + return r.base.IsReadOnly() +} + +func (r *RepositoryImpl) Write(path string, mode vfs.FileMode, opts ...accessio.Option) error { + return r.base.Write(path, mode, opts...) +} + +func (r *RepositoryImpl) Update() error { + return r.base.Update() +} + +func (r *RepositoryImpl) Close() error { + return r.base.Close() +} + +func (a *RepositoryImpl) getIndex() *index.RepositoryIndex { + if a.IsReadOnly() { + return a.base.GetState().GetOriginalState().(*index.RepositoryIndex) + } + return a.base.GetState().GetState().(*index.RepositoryIndex) +} + +//////////////////////////////////////////////////////////////////////////////// +// cpi.Repository methods + +func (r *RepositoryImpl) ExistsArtifact(name string, tag string) (bool, error) { + return r.getIndex().HasArtifact(name, tag), nil +} + +func (r *RepositoryImpl) LookupArtifact(name string, ref string) (acc cpi.ArtifactAccess, err error) { + ns, err := NewNamespace(r, name) + if err != nil { + return nil, err + } + + defer refmgmt.PropagateCloseTemporary(&err, ns) // temporary namespace object not exposed. + + a := r.getIndex().GetArtifactInfo(name, ref) + if a == nil { + return nil, cpi.ErrUnknownArtifact(name, ref) + } + return ns.GetArtifact(ref) +} + +func (r *RepositoryImpl) LookupNamespace(name string) (cpi.NamespaceAccess, error) { + return NewNamespace(r, name) +} diff --git a/api/oci/extensions/repositories/ctf/state.go b/api/oci/extensions/repositories/ctf/state.go new file mode 100644 index 000000000..034bd66f7 --- /dev/null +++ b/api/oci/extensions/repositories/ctf/state.go @@ -0,0 +1,47 @@ +package ctf + +import ( + "fmt" + "reflect" + + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/oci/extensions/repositories/ctf/index" + "ocm.software/ocm/api/utils/accessobj" +) + +type StateHandler struct{} + +var _ accessobj.StateHandler = &StateHandler{} + +func NewStateHandler(fs vfs.FileSystem) accessobj.StateHandler { + return &StateHandler{} +} + +func (i StateHandler) Initial() interface{} { + return index.NewRepositoryIndex() +} + +func (i StateHandler) Encode(d interface{}) ([]byte, error) { + return index.Encode(d.(*index.RepositoryIndex).GetDescriptor()) +} + +func (i StateHandler) Decode(data []byte) (interface{}, error) { + idx, err := index.Decode(data) + if err != nil { + return nil, fmt.Errorf("unable to parse artifact index read from %s: %w", ArtifactIndexFileName, err) + } + if idx.SchemaVersion != index.SchemaVersion { + return nil, fmt.Errorf("unknown schema version %d for artifact index %s", index.SchemaVersion, ArtifactIndexFileName) + } + + artifacts := index.NewRepositoryIndex() + for i := range idx.Index { + artifacts.AddArtifactInfo(&idx.Index[i]) + } + return artifacts, nil +} + +func (i StateHandler) Equivalent(a, b interface{}) bool { + return reflect.DeepEqual(a, b) +} diff --git a/pkg/contexts/oci/repositories/ctf/suite_test.go b/api/oci/extensions/repositories/ctf/suite_test.go similarity index 100% rename from pkg/contexts/oci/repositories/ctf/suite_test.go rename to api/oci/extensions/repositories/ctf/suite_test.go diff --git a/pkg/contexts/oci/repositories/ctf/synthesis_test.go b/api/oci/extensions/repositories/ctf/synthesis_test.go similarity index 80% rename from pkg/contexts/oci/repositories/ctf/synthesis_test.go rename to api/oci/extensions/repositories/ctf/synthesis_test.go index 11ac47c36..6b636c004 100644 --- a/pkg/contexts/oci/repositories/ctf/synthesis_test.go +++ b/api/oci/extensions/repositories/ctf/synthesis_test.go @@ -4,7 +4,7 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf/testhelper" + . "ocm.software/ocm/api/oci/extensions/repositories/ctf/testhelper" "github.com/mandelsoft/filepath/pkg/filepath" "github.com/mandelsoft/goutils/finalizer" @@ -12,21 +12,21 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" "github.com/opencontainers/go-digest" - "github.com/open-component-model/ocm/pkg/blobaccess/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/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "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/digester/digesters/artifact" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/hasher/sha256" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/oci/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/digester/digesters/artifact" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/hasher/sha256" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/mime" ) type dummyMethod struct { diff --git a/pkg/contexts/oci/repositories/ctf/testhelper/fill.go b/api/oci/extensions/repositories/ctf/testhelper/fill.go similarity index 86% rename from pkg/contexts/oci/repositories/ctf/testhelper/fill.go rename to api/oci/extensions/repositories/ctf/testhelper/fill.go index 653994e27..f19c0f14f 100644 --- a/pkg/contexts/oci/repositories/ctf/testhelper/fill.go +++ b/api/oci/extensions/repositories/ctf/testhelper/fill.go @@ -7,11 +7,11 @@ import ( "github.com/mandelsoft/goutils/testutils" "github.com/opencontainers/go-digest" - "github.com/open-component-model/ocm/pkg/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/mime" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/mime" ) const ( diff --git a/api/oci/extensions/repositories/ctf/type.go b/api/oci/extensions/repositories/ctf/type.go new file mode 100644 index 000000000..433442212 --- /dev/null +++ b/api/oci/extensions/repositories/ctf/type.go @@ -0,0 +1,83 @@ +package ctf + +import ( + "strings" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + Type = cpi.CommonTransportFormat + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +func init() { + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](Type)) + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1)) +} + +// RepositorySpec describes an OCI registry interface backed by an oci registry. +type RepositorySpec struct { + runtime.ObjectVersionedType `json:",inline"` + accessio.StandardOptions `json:",inline"` + + // FilePath is the file for the repository in the filesystem.. + FilePath string `json:"filePath"` + // AccessMode can be set to request readonly access or creation + AccessMode accessobj.AccessMode `json:"accessMode,omitempty"` +} + +var _ cpi.RepositorySpec = (*RepositorySpec)(nil) + +var _ cpi.IntermediateRepositorySpecAspect = (*RepositorySpec)(nil) + +// NewRepositorySpec creates a new RepositorySpec. +func NewRepositorySpec(mode accessobj.AccessMode, filePath string, opts ...accessio.Option) (*RepositorySpec, error) { + o, err := accessio.AccessOptions(nil, opts...) + if err != nil { + return nil, err + } + if o.GetFileFormat() == nil { + for _, v := range SupportedFormats() { + if strings.HasSuffix(filePath, "."+v.String()) { + o.SetFileFormat(v) + break + } + } + } + o.Default() + return &RepositorySpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(Type), + FilePath: filePath, + StandardOptions: *o.(*accessio.StandardOptions), + AccessMode: mode, + }, nil +} + +func (a *RepositorySpec) IsIntermediate() bool { + return true +} + +func (a *RepositorySpec) GetType() string { + return Type +} + +func (s *RepositorySpec) Name() string { + return s.FilePath +} + +func (s *RepositorySpec) UniformRepositorySpec() *cpi.UniformRepositorySpec { + u := &cpi.UniformRepositorySpec{ + Type: Type, + Info: s.FilePath, + } + return u +} + +func (a *RepositorySpec) Repository(ctx cpi.Context, creds credentials.Credentials) (cpi.Repository, error) { + return Open(ctx, a.AccessMode, a.FilePath, 0o700, &a.StandardOptions) +} diff --git a/api/oci/extensions/repositories/ctf/uniform.go b/api/oci/extensions/repositories/ctf/uniform.go new file mode 100644 index 000000000..d351c14fe --- /dev/null +++ b/api/oci/extensions/repositories/ctf/uniform.go @@ -0,0 +1,68 @@ +package ctf + +import ( + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" +) + +const AltType = "ctf" + +func init() { + h := &repospechandler{} + cpi.RegisterRepositorySpecHandler(h, "") + cpi.RegisterRepositorySpecHandler(h, Type) + cpi.RegisterRepositorySpecHandler(h, AltType) + for _, f := range SupportedFormats() { + cpi.RegisterRepositorySpecHandler(h, string(f)) + } +} + +type repospechandler struct{} + +func (h *repospechandler) MapReference(ctx cpi.Context, u *cpi.UniformRepositorySpec) (cpi.RepositorySpec, error) { + return MapReference(ctx, u) +} + +func explicit(t string) bool { + for _, f := range SupportedFormats() { + if t == string(f) { + return true + } + } + return t == Type || t == AltType +} + +func MapReference(ctx cpi.Context, u *cpi.UniformRepositorySpec) (cpi.RepositorySpec, error) { + path := u.Info + if u.Info == "" { + if u.Host == "" || u.Type == "" { + return nil, nil + } + path = u.Host + } + fs := vfsattr.Get(ctx) + + typ, _ := accessobj.MapType(u.Type, Type, accessio.FormatNone, true, AltType) + hint, f := accessobj.MapType(u.TypeHint, Type, accessio.FormatDirectory, true, AltType) + if !u.CreateIfMissing { + hint = "" + } + create, ok, err := accessobj.CheckFile(Type, hint, explicit(accessio.TypeForTypeSpec(u.Type)), path, fs, ArtifactIndexFileName) + if !ok || (err != nil && typ == "") { + if err != nil { + return nil, err + } + if !ok { + return nil, nil + } + } + mode := accessobj.ACC_WRITABLE + createHint := accessio.FormatNone + if create { + mode |= accessobj.ACC_CREATE + createHint = f + } + return NewRepositorySpec(mode, path, createHint, accessio.PathFileSystem(fs)) +} diff --git a/pkg/contexts/oci/repositories/docker/README.md b/api/oci/extensions/repositories/docker/README.md similarity index 100% rename from pkg/contexts/oci/repositories/docker/README.md rename to api/oci/extensions/repositories/docker/README.md diff --git a/api/oci/extensions/repositories/docker/artifact.go b/api/oci/extensions/repositories/docker/artifact.go new file mode 100644 index 000000000..b1b5567ca --- /dev/null +++ b/api/oci/extensions/repositories/docker/artifact.go @@ -0,0 +1,69 @@ +package docker + +import ( + "sync" + + "github.com/containers/image/v5/types" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +type dockerSource struct { + lock sync.RWMutex + src types.ImageSource + img types.Image + refcount int +} + +var _ accessio.BlobSource = (*dockerSource)(nil) + +func newDockerSource(img types.Image, src types.ImageSource) *dockerSource { + return &dockerSource{ + src: src, + img: img, + refcount: 1, + } +} + +func (c *dockerSource) Ref() error { + c.lock.Lock() + defer c.lock.Unlock() + if c.refcount == 0 { + return accessio.ErrClosed + } + c.refcount++ + return nil +} + +func (c *dockerSource) Unref() error { + c.lock.Lock() + defer c.lock.Unlock() + if c.refcount == 0 { + return accessio.ErrClosed + } + c.refcount-- + return c.src.Close() +} + +func (d *dockerSource) GetBlobData(digest digest.Digest) (int64, blobaccess.DataAccess, error) { + info := d.img.ConfigInfo() + if info.Digest == digest { + data, err := d.img.ConfigBlob(dummyContext) + if err != nil { + return -1, nil, err + } + return info.Size, blobaccess.DataAccessForData(data), nil + } + info.Digest = "" + for _, l := range d.img.LayerInfos() { + if l.Digest == digest { + info = l + acc, err := NewDataAccess(d.src, info, false) + return l.Size, acc, err + } + } + return -1, nil, cpi.ErrBlobNotFound(digest) +} diff --git a/pkg/contexts/oci/repositories/docker/client.go b/api/oci/extensions/repositories/docker/client.go similarity index 100% rename from pkg/contexts/oci/repositories/docker/client.go rename to api/oci/extensions/repositories/docker/client.go diff --git a/api/oci/extensions/repositories/docker/convert.go b/api/oci/extensions/repositories/docker/convert.go new file mode 100644 index 000000000..7191eb371 --- /dev/null +++ b/api/oci/extensions/repositories/docker/convert.go @@ -0,0 +1,203 @@ +package docker + +import ( + "bytes" + "context" + "fmt" + "io" + + "github.com/containers/image/v5/image" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/types" + "github.com/opencontainers/go-digest" + "github.com/sirupsen/logrus" + + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +// fakeSource implements required methods to call the manifest conversion. +type fakeSource struct { + types.ImageSource + art cpi.BlobAccess + blobs cpi.BlobSource + ref types.ImageReference +} + +func (f *fakeSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) { + if instanceDigest != nil { + return nil, "", fmt.Errorf("manifest lists are not supported") + } + data, err := f.art.Get() + if err != nil { + return nil, "", err + } + return data, f.art.MimeType(), nil +} + +func (f *fakeSource) GetBlob(ctx context.Context, bi types.BlobInfo, bc types.BlobInfoCache) (io.ReadCloser, int64, error) { + _, blob, err := f.blobs.GetBlobData(bi.Digest) + if err != nil { + return nil, blobaccess.BLOB_UNKNOWN_SIZE, err + } + + r, err := blob.Reader() + return r, bi.Size, err +} + +func (f *fakeSource) Reference() types.ImageReference { + return f.ref +} + +//////////////////////////////////////////////////////////////////////////////// + +type artBlobCache struct { + access cpi.ArtifactAccess +} + +var _ accessio.BlobCache = (*artBlobCache)(nil) + +func ArtifactAsBlobCache(access cpi.ArtifactAccess) accessio.BlobCache { + return &artBlobCache{access} +} + +func (a *artBlobCache) Ref() error { + return nil +} + +func (a *artBlobCache) Unref() error { + return nil +} + +func (a *artBlobCache) GetBlobData(digest digest.Digest) (int64, blobaccess.DataAccess, error) { + blob, err := a.access.GetBlob(digest) + if err != nil { + return -1, nil, err + } + return blob.Size(), blob, err +} + +func (a *artBlobCache) AddBlob(blob blobaccess.BlobAccess) (int64, digest.Digest, error) { + err := a.access.AddBlob(blob) + if err != nil { + return -1, "", err + } + return blob.Size(), blob.Digest(), err +} + +func (c *artBlobCache) AddData(data blobaccess.DataAccess) (int64, digest.Digest, error) { + return c.AddBlob(blobaccess.ForDataAccess(blobaccess.BLOB_UNKNOWN_DIGEST, blobaccess.BLOB_UNKNOWN_SIZE, "", data)) +} + +//////////////////////////////////////////////////////////////////////////////// + +func blobSource(art cpi.Artifact, blobs accessio.BlobSource) (accessio.BlobSource, error) { + var err error + if blobs == nil { + if t, ok := art.(cpi.ArtifactAccess); !ok { + return nil, fmt.Errorf("blob source required") + } else { + blobs = ArtifactAsBlobCache(t) + } + } else { + if t, ok := art.(cpi.ArtifactAccess); ok { + blobs, err = accessio.NewCascadedBlobCacheForSource(blobs, ArtifactAsBlobCache(t)) + if err != nil { + return nil, err + } + } + } + return blobs, nil +} + +func Convert(art cpi.Artifact, blobs accessio.BlobSource, dst types.ImageDestination) (cpi.BlobAccess, error) { + blobs, err := blobSource(art, blobs) + if err != nil { + return nil, err + } + artblob, err := art.Blob() + if err != nil { + return nil, err + } + ociImage := &fakeSource{ + art: artblob, + blobs: blobs, + ref: dst.Reference(), + } + + m, err := art.Manifest() + if err != nil { + return nil, err + } + for i, l := range m.Layers { + size, blob, err := blobs.GetBlobData(l.Digest) + if err != nil { + return nil, err + } + r, err := blob.Reader() + if err != nil { + return nil, err + } + defer r.Close() + bi := types.BlobInfo{ + Digest: l.Digest, + Size: size, + URLs: l.URLs, + Annotations: l.Annotations, + MediaType: l.MediaType, + } + logrus.Infof("put blob for layer %d", i) + _, err = dst.PutBlob(dummyContext, r, bi, nil, false) + if err != nil { + return nil, err + } + } + + un := image.UnparsedInstance(ociImage, nil) + img, err := image.FromUnparsedImage(dummyContext, nil, un) + if err != nil { + return nil, err + } + + opts := types.ManifestUpdateOptions{ + ManifestMIMEType: manifest.DockerV2Schema2MediaType, + InformationOnly: types.ManifestUpdateInformation{ + Destination: dst, + }, + } + + img, err = img.UpdatedImage(dummyContext, opts) + if err != nil { + return nil, err + } + + bi := img.ConfigInfo() + blob, err := img.ConfigBlob(dummyContext) + if err != nil { + return nil, err + } + var reader io.ReadCloser + if blob == nil { + _, orig, err := blobs.GetBlobData(bi.Digest) + if err != nil { + return nil, err + } + reader, err = orig.Reader() + if err != nil { + return nil, err + } + } else { + reader = io.NopCloser(bytes.NewReader(blob)) + } + _, err = dst.PutBlob(dummyContext, reader, bi, nil, true) + if err != nil { + return nil, err + } + man, _, err := img.Manifest(dummyContext) + if err != nil { + return nil, err + } + + return artblob, dst.PutManifest(dummyContext, man, nil) +} diff --git a/api/oci/extensions/repositories/docker/namespace.go b/api/oci/extensions/repositories/docker/namespace.go new file mode 100644 index 000000000..2a346cdd1 --- /dev/null +++ b/api/oci/extensions/repositories/docker/namespace.go @@ -0,0 +1,278 @@ +package docker + +import ( + "fmt" + "strings" + "sync" + + "github.com/containers/image/v5/image" + "github.com/containers/image/v5/types" + dockertypes "github.com/docker/docker/api/types/image" + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/logging" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/oci/cpi/support" + "ocm.software/ocm/api/oci/internal" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +type blobHandler struct { + accessio.BlobCache +} + +var _ support.BlobProvider = (*blobHandler)(nil) + +func newBlobHandler(cache accessio.BlobCache) support.BlobProvider { + return &blobHandler{cache} +} + +func (b blobHandler) AddBlob(access internal.BlobAccess) error { + _, _, err := b.BlobCache.AddBlob(access) + return err +} + +//////////////////////////////////////////////////////////////////////////////// + +// namespaceContainer delegates functionality but blob access to an underlying +// handler. +// blob access is handled locally. +type namespaceContainer struct { + *namespaceHandler + blobs support.BlobProvider +} + +var _ support.NamespaceContainer = (*namespaceContainer)(nil) + +func newNamespaceContainer(handler *namespaceHandler, blobs support.BlobProvider) *namespaceContainer { + return &namespaceContainer{ + namespaceHandler: handler, + blobs: blobs, + } +} + +func NewNamespace(repo *RepositoryImpl, name string) (cpi.NamespaceAccess, error) { + h, err := newNamespaceHandler(repo) + if err != nil { + return nil, err + } + // initial container wrapper releases base cache with close of namespace + // container on last namespace ref. + // base cache has initial user count of 1. + return support.NewNamespaceAccess(name, newNamespaceContainer(h, h.blobs), repo, "docker namespace") +} + +func (n *namespaceContainer) Close() error { + n.lock.Lock() + defer n.lock.Unlock() + + if n.blobs != nil { + err := n.blobs.Unref() + n.blobs = nil + if err != nil { + return fmt.Errorf("failed to unref: %w", err) + } + } + return nil +} + +func (n *namespaceContainer) GetBlobData(digest digest.Digest) (int64, cpi.DataAccess, error) { + return n.blobs.GetBlobData(digest) +} + +func (n *namespaceContainer) AddBlob(blob cpi.BlobAccess) error { + if err := n.blobs.AddBlob(blob); err != nil { + return fmt.Errorf("failed to add blob to cache: %w", err) + } + + return nil +} + +//////////////////////////////////////////////////////////////////////////////// + +type namespaceHandler struct { + impl support.NamespaceAccessImpl + lock sync.RWMutex + repo *RepositoryImpl + blobs support.BlobProvider + log logging.Logger +} + +func newNamespaceHandler(repo *RepositoryImpl) (*namespaceHandler, error) { + cache, err := accessio.NewCascadedBlobCache(nil) + if err != nil { + return nil, err + } + + return &namespaceHandler{ + repo: repo, + blobs: newBlobHandler(cache), + log: repo.GetContext().Logger(), + }, nil +} + +func (n *namespaceHandler) SetImplementation(impl support.NamespaceAccessImpl) { + n.impl = impl +} + +func (n *namespaceHandler) IsReadOnly() bool { + return n.repo.IsReadOnly() +} + +func (n *namespaceContainer) GetBlobDescriptor(digest digest.Digest) *cpi.Descriptor { + return nil +} + +func (n *namespaceHandler) ListTags() ([]string, error) { + opts := dockertypes.ListOptions{} + list, err := n.repo.client.ImageList(dummyContext, opts) + if err != nil { + return nil, err + } + var result []string + if n.impl.GetNamespace() == "" { + for _, e := range list { + // ID is always the config digest + // filter images without a repo tag for empty namespace + if len(e.RepoTags) == 0 { + d, err := digest.Parse(e.ID) + if err == nil { + result = append(result, d.String()[:12]) + } + } + } + } else { + prefix := n.impl.GetNamespace() + ":" + for _, e := range list { + for _, t := range e.RepoTags { + if strings.HasPrefix(t, prefix) { + result = append(result, t[len(prefix):]) + } + } + } + } + return result, nil +} + +func (n *namespaceHandler) GetArtifact(i support.NamespaceAccessImpl, vers string) (cpi.ArtifactAccess, error) { + ref, err := ParseRef(n.impl.GetNamespace(), vers) + if err != nil { + return nil, err + } + src, err := ref.NewImageSource(dummyContext, n.repo.sysctx) + if err != nil { + return nil, err + } + + opts := types.ManifestUpdateOptions{ + ManifestMIMEType: artdesc.MediaTypeImageManifest, + } + un := image.UnparsedInstance(src, nil) + img, err := image.FromUnparsedImage(dummyContext, n.repo.sysctx, un) + if err != nil { + src.Close() + return nil, err + } + + img, err = img.UpdatedImage(dummyContext, opts) + if err != nil { + src.Close() + return nil, err + } + + data, mime, err := img.Manifest(dummyContext) + if err != nil { + src.Close() + return nil, err + } + + cache, err := accessio.NewCascadedBlobCacheForSource(n.blobs, newDockerSource(img, src)) + if err != nil { + return nil, err + } + + priv := i.WithContainer(newNamespaceContainer(n, newBlobHandler(cache))) + // assure explicit close of wrapper container for artifact close + return support.NewArtifactForBlob(priv, blobaccess.ForData(mime, data), priv) +} + +func (n *namespaceHandler) HasArtifact(vers string) (bool, error) { + list, err := n.ListTags() + if err != nil { + return false, err + } + for _, e := range list { + if e == vers { + return true, nil + } + } + return false, nil +} + +func (n *namespaceContainer) AddArtifact(artifact cpi.Artifact, tags ...string) (access blobaccess.BlobAccess, err error) { + tag := "latest" + if len(tags) > 0 { + tag = tags[0] + } + ref, err := ParseRef(n.impl.GetNamespace(), tag) + if err != nil { + return nil, err + } + dst, err := ref.NewImageDestination(dummyContext, nil) + if err != nil { + return nil, err + } + defer dst.Close() + + blob, err := Convert(artifact, n.blobs, dst) + if err != nil { + return nil, err + } + err = dst.Commit(dummyContext, nil) + if err != nil { + return nil, err + } + + return blob, nil +} + +func (n *namespaceContainer) AddTags(digest digest.Digest, tags ...string) error { + if ok, _ := artdesc.IsDigest(digest.String()); ok { + return errors.ErrNotSupported("image access by digest") + } + + src := n.impl.GetNamespace() + ":" + digest.String() + + if pattern.MatchString(digest.String()) { + // this definitely no digest, but the library expects it this way + src = digest.String() + } + + for _, tag := range tags { + err := n.repo.client.ImageTag(dummyContext, src, n.impl.GetNamespace()+":"+tag) + if err != nil { + return fmt.Errorf("failed to add image tag: %w", err) + } + } + + return nil +} + +func (n *namespaceContainer) NewArtifact(i support.NamespaceAccessImpl, art ...cpi.Artifact) (cpi.ArtifactAccess, error) { + if n.IsReadOnly() { + return nil, accessio.ErrReadOnly + } + var m cpi.Artifact + if len(art) == 0 || art[0] == nil { + m = artdesc.NewManifest() + } else { + m = art[0] + if !m.IsValid() { + m = artdesc.NewManifest() + } + } + return support.NewArtifact(i, m) +} diff --git a/api/oci/extensions/repositories/docker/repository.go b/api/oci/extensions/repositories/docker/repository.go new file mode 100644 index 000000000..52836d19b --- /dev/null +++ b/api/oci/extensions/repositories/docker/repository.go @@ -0,0 +1,121 @@ +package docker + +import ( + "strings" + + "github.com/containers/image/v5/types" + dockertypes "github.com/docker/docker/api/types/image" + "github.com/docker/docker/client" + + "ocm.software/ocm/api/oci/cpi" +) + +type RepositoryImpl struct { + cpi.RepositoryImplBase + spec *RepositorySpec + sysctx *types.SystemContext + client *client.Client +} + +var _ cpi.RepositoryImpl = (*RepositoryImpl)(nil) + +func NewRepository(ctx cpi.Context, spec *RepositorySpec) (cpi.Repository, error) { + client, err := newDockerClient(spec.DockerHost) + if err != nil { + return nil, err + } + + sysctx := &types.SystemContext{ + DockerDaemonHost: client.DaemonHost(), + } + + i := &RepositoryImpl{ + RepositoryImplBase: cpi.NewRepositoryImplBase(ctx), + spec: spec, + sysctx: sysctx, + client: client, + } + return cpi.NewRepository(i, "docker"), nil +} + +func (r *RepositoryImpl) Close() error { + return nil +} + +func (r *RepositoryImpl) IsReadOnly() bool { + return true +} + +func (r *RepositoryImpl) GetSpecification() cpi.RepositorySpec { + return r.spec +} + +func (r *RepositoryImpl) NamespaceLister() cpi.NamespaceLister { + return r +} + +func (r *RepositoryImpl) NumNamespaces(prefix string) (int, error) { + repos, err := r.GetRepositories() + if err != nil { + return -1, err + } + return len(cpi.FilterByNamespacePrefix(prefix, repos)), nil +} + +func (r *RepositoryImpl) GetNamespaces(prefix string, closure bool) ([]string, error) { + repos, err := r.GetRepositories() + if err != nil { + return nil, err + } + return cpi.FilterChildren(closure, prefix, repos), nil +} + +func (r *RepositoryImpl) GetRepositories() ([]string, error) { + opts := dockertypes.ListOptions{} + list, err := r.client.ImageList(dummyContext, opts) + if err != nil { + return nil, err + } + var result cpi.StringList + for _, e := range list { + if len(e.RepoTags) > 0 { + for _, t := range e.RepoTags { + i := strings.Index(t, ":") + if i > 0 { + if t[:i] != "" { + result.Add(t[:i]) + } + } + } + } else { + result.Add("") + } + } + return result, nil +} + +func (r *RepositoryImpl) ExistsArtifact(name string, version string) (bool, error) { + ref, err := ParseRef(name, version) + if err != nil { + return false, err + } + opts := dockertypes.ListOptions{} + opts.Filters.Add("reference", ref.StringWithinTransport()) + list, err := r.client.ImageList(dummyContext, opts) + if err != nil { + return false, err + } + return len(list) > 0, nil +} + +func (r *RepositoryImpl) LookupArtifact(name string, version string) (cpi.ArtifactAccess, error) { + n, err := r.LookupNamespace(name) + if err != nil { + return nil, err + } + return n.GetArtifact(version) +} + +func (r *RepositoryImpl) LookupNamespace(name string) (cpi.NamespaceAccess, error) { + return NewNamespace(r, name) +} diff --git a/api/oci/extensions/repositories/docker/type.go b/api/oci/extensions/repositories/docker/type.go new file mode 100644 index 000000000..dbe79e531 --- /dev/null +++ b/api/oci/extensions/repositories/docker/type.go @@ -0,0 +1,48 @@ +package docker + +import ( + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + Type = "DockerDaemon" + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +func init() { + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](Type)) + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1)) +} + +// RepositorySpec describes an OCI registry interface backed by an oci registry. +type RepositorySpec struct { + runtime.ObjectVersionedType `json:",inline"` + DockerHost string `json:"dockerHost,omitempty"` +} + +// NewRepositorySpec creates a new RepositorySpec for an optional host. +func NewRepositorySpec(host ...string) *RepositorySpec { + return &RepositorySpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(Type), + DockerHost: utils.Optional(host...), + } +} + +func (a *RepositorySpec) GetType() string { + return Type +} + +func (a *RepositorySpec) Name() string { + return Type +} + +func (a *RepositorySpec) UniformRepositorySpec() *cpi.UniformRepositorySpec { + return cpi.UniformRepositorySpecForHostURL(Type, a.DockerHost) +} + +func (a *RepositorySpec) Repository(ctx cpi.Context, creds credentials.Credentials) (cpi.Repository, error) { + return NewRepository(ctx, a) +} diff --git a/api/oci/extensions/repositories/docker/uniform.go b/api/oci/extensions/repositories/docker/uniform.go new file mode 100644 index 000000000..3d7e15224 --- /dev/null +++ b/api/oci/extensions/repositories/docker/uniform.go @@ -0,0 +1,26 @@ +package docker + +import ( + "ocm.software/ocm/api/oci/cpi" +) + +func init() { + cpi.RegisterRepositorySpecHandler(&repospechandler{}, Type) +} + +type repospechandler struct{} + +func (h *repospechandler) MapReference(ctx cpi.Context, u *cpi.UniformRepositorySpec) (cpi.RepositorySpec, error) { + host := u.Host + if u.Scheme != "" && host != "" { + host = u.Scheme + "://" + u.Host + } + if u.Info != "" { + if u.Info == "default" { + host = "" + } else if host == "" { + host = u.Info + } + } + return NewRepositorySpec(host), nil +} diff --git a/api/oci/extensions/repositories/docker/utils.go b/api/oci/extensions/repositories/docker/utils.go new file mode 100644 index 000000000..5b83f7477 --- /dev/null +++ b/api/oci/extensions/repositories/docker/utils.go @@ -0,0 +1,124 @@ +package docker + +import ( + "context" + "fmt" + "io" + "regexp" + "strings" + "sync" + + "github.com/containers/image/v5/docker/daemon" + "github.com/containers/image/v5/types" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/utils/accessio" +) + +var dummyContext = context.Background() + +var pattern = regexp.MustCompile("^[0-9a-f]{12}$") + +func ParseGenericRef(ref string) (string, string, error) { + if strings.TrimSpace(ref) == "" { + return "", "", fmt.Errorf("invalid docker reference %q", ref) + } + parts := strings.Split(ref, ":") + if len(parts) > 2 { + return "", "", fmt.Errorf("invalid docker reference %q", ref) + } + if len(parts) == 1 { + // expect docker id + if pattern.MatchString(parts[0]) { + return "", parts[0], nil + } + } + _, err := daemon.ParseReference(ref) + if err != nil { + return "", "", err + } + return parts[0], parts[1], nil +} + +func ParseRef(name, version string) (types.ImageReference, error) { + if version == "" || name == "" { + id := version + if id == "" { + id = name + } + // check for docker daemon image id + if pattern.MatchString(id) { + // this definitely no digest, but the library expects it this way + return daemon.NewReference(digest.Digest(id), nil) + } + return nil, fmt.Errorf("no docker daemon image id") + } + return daemon.ParseReference(name + ":" + version) +} + +func ImageId(art cpi.Artifact) digest.Digest { + m, err := art.Manifest() + if err != nil { + return "" + } + return digest.Digest(m.Config.Digest.Hex()[:12]) +} + +// TODO add cache + +type dataAccess struct { + accessio.NopCloser + lock sync.Mutex + info types.BlobInfo + src types.ImageSource + reader io.ReadCloser +} + +var _ cpi.DataAccess = (*dataAccess)(nil) + +func NewDataAccess(src types.ImageSource, info types.BlobInfo, delayed bool) (*dataAccess, error) { + var reader io.ReadCloser + var err error + + if !delayed { + reader, _, err = src.GetBlob(context.Background(), info, nil) + if err != nil { + return nil, err + } + } + return &dataAccess{ + info: info, + src: src, + reader: reader, + }, nil +} + +func (d *dataAccess) Get() ([]byte, error) { + return readAll(d.Reader()) +} + +func (d *dataAccess) Reader() (io.ReadCloser, error) { + d.lock.Lock() + reader := d.reader + d.reader = nil + d.lock.Unlock() + if reader != nil { + return reader, nil + } + reader, _, err := d.src.GetBlob(context.Background(), d.info, nil) + return reader, err +} + +func readAll(reader io.ReadCloser, err error) ([]byte, error) { + if err != nil { + return nil, err + } + defer reader.Close() + + data, err := io.ReadAll(reader) + if err != nil { + return nil, err + } + return data, nil +} diff --git a/api/oci/extensions/repositories/empty/repository.go b/api/oci/extensions/repositories/empty/repository.go new file mode 100644 index 000000000..255b97b6a --- /dev/null +++ b/api/oci/extensions/repositories/empty/repository.go @@ -0,0 +1,61 @@ +package empty + +import ( + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/oci/cpi" +) + +type Repository struct { + ctx cpi.Context +} + +var _ cpi.Repository = (*Repository)(nil) + +func NewRepository(ctx cpi.Context) *Repository { + return &Repository{ctx} +} + +func (r *Repository) GetContext() cpi.Context { + return r.ctx +} + +func (r *Repository) IsClosed() bool { + return false +} + +func (r *Repository) Dup() (cpi.Repository, error) { + return r, nil +} + +func (r Repository) GetSpecification() cpi.RepositorySpec { + return NewRepositorySpec() +} + +func (r *Repository) NamespaceLister() cpi.NamespaceLister { + return r +} + +func (r *Repository) NumNamespaces(prefix string) (int, error) { + return 0, nil +} + +func (r *Repository) GetNamespaces(prefix string, closure bool) ([]string, error) { + return nil, nil +} + +func (r Repository) ExistsArtifact(name string, version string) (bool, error) { + return false, nil +} + +func (r Repository) LookupArtifact(name string, version string) (cpi.ArtifactAccess, error) { + return nil, cpi.ErrUnknownArtifact(name, version) +} + +func (r Repository) LookupNamespace(name string) (cpi.NamespaceAccess, error) { + return nil, errors.ErrNotSupported("write access") +} + +func (r Repository) Close() error { + return nil +} diff --git a/api/oci/extensions/repositories/empty/type.go b/api/oci/extensions/repositories/empty/type.go new file mode 100644 index 000000000..c95c89ba5 --- /dev/null +++ b/api/oci/extensions/repositories/empty/type.go @@ -0,0 +1,51 @@ +package empty + +import ( + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + Type = "Empty" + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +const ATTR_REPOS = "ocm.software/ocm/api/oci/extensions/repositories/empty" + +func init() { + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](Type)) + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1)) +} + +// RepositorySpec describes an OCI registry interface backed by an oci registry. +type RepositorySpec struct { + runtime.ObjectVersionedType `json:",inline"` +} + +// NewRepositorySpec creates a new RepositorySpec. +func NewRepositorySpec() *RepositorySpec { + return &RepositorySpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(Type), + } +} + +func (a *RepositorySpec) GetType() string { + return Type +} + +func (a *RepositorySpec) Name() string { + return Type +} + +func (a *RepositorySpec) UniformRepositorySpec() *cpi.UniformRepositorySpec { + u := &cpi.UniformRepositorySpec{ + Type: Type, + } + return u +} + +func (a *RepositorySpec) Repository(ctx cpi.Context, creds credentials.Credentials) (cpi.Repository, error) { + return ctx.GetAttributes().GetOrCreateAttribute(ATTR_REPOS, func(datacontext.Context) interface{} { return NewRepository(ctx) }).(cpi.Repository), nil +} diff --git a/api/oci/extensions/repositories/empty/uniform.go b/api/oci/extensions/repositories/empty/uniform.go new file mode 100644 index 000000000..326b250a8 --- /dev/null +++ b/api/oci/extensions/repositories/empty/uniform.go @@ -0,0 +1,19 @@ +package empty + +import ( + "ocm.software/ocm/api/oci/cpi" +) + +func init() { + cpi.RegisterRepositorySpecHandler(&repospechandler{}, Type) +} + +type repospechandler struct{} + +func (h *repospechandler) MapReference(ctx cpi.Context, u *cpi.UniformRepositorySpec) (cpi.RepositorySpec, error) { + if u.Info != "" || u.Host == "" { + return nil, nil + } + + return NewRepositorySpec(), nil +} diff --git a/api/oci/extensions/repositories/init.go b/api/oci/extensions/repositories/init.go new file mode 100644 index 000000000..2d9efaeb2 --- /dev/null +++ b/api/oci/extensions/repositories/init.go @@ -0,0 +1,9 @@ +package repositories + +import ( + _ "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + _ "ocm.software/ocm/api/oci/extensions/repositories/ctf" + _ "ocm.software/ocm/api/oci/extensions/repositories/docker" + _ "ocm.software/ocm/api/oci/extensions/repositories/empty" + _ "ocm.software/ocm/api/oci/extensions/repositories/ocireg" +) diff --git a/pkg/contexts/oci/repositories/ocireg/README.md b/api/oci/extensions/repositories/ocireg/README.md similarity index 100% rename from pkg/contexts/oci/repositories/ocireg/README.md rename to api/oci/extensions/repositories/ocireg/README.md diff --git a/pkg/contexts/oci/repositories/ocireg/blobs.go b/api/oci/extensions/repositories/ocireg/blobs.go similarity index 88% rename from pkg/contexts/oci/repositories/ocireg/blobs.go rename to api/oci/extensions/repositories/ocireg/blobs.go index 9c503f129..0ecf1b299 100644 --- a/pkg/contexts/oci/repositories/ocireg/blobs.go +++ b/api/oci/extensions/repositories/ocireg/blobs.go @@ -8,11 +8,11 @@ import ( "github.com/opencontainers/go-digest" "github.com/sirupsen/logrus" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/oci/attrs/cacheattr" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/docker/resolve" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/oci/extensions/attrs/cacheattr" + "ocm.software/ocm/api/tech/docker/resolve" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" ) type BlobContainer interface { diff --git a/api/oci/extensions/repositories/ocireg/logging.go b/api/oci/extensions/repositories/ocireg/logging.go new file mode 100644 index 000000000..9e78b02c7 --- /dev/null +++ b/api/oci/extensions/repositories/ocireg/logging.go @@ -0,0 +1,7 @@ +package ocireg + +import ( + ocmlog "ocm.software/ocm/api/utils/logging" +) + +var REALM = ocmlog.DefineSubRealm("OCI repository handling", "oci", "ocireg") diff --git a/api/oci/extensions/repositories/ocireg/namespace.go b/api/oci/extensions/repositories/ocireg/namespace.go new file mode 100644 index 000000000..6089cea06 --- /dev/null +++ b/api/oci/extensions/repositories/ocireg/namespace.go @@ -0,0 +1,259 @@ +package ocireg + +import ( + "context" + "fmt" + + "github.com/containerd/errdefs" + "github.com/mandelsoft/goutils/errors" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/oci/cpi/support" + "ocm.software/ocm/api/oci/extensions/actions/oci-repository-prepare" + "ocm.software/ocm/api/tech/docker/resolve" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/logging" + common "ocm.software/ocm/api/utils/misc" +) + +type NamespaceContainer struct { + impl support.NamespaceAccessImpl + repo *RepositoryImpl + resolver resolve.Resolver + lister resolve.Lister + fetcher resolve.Fetcher + pusher resolve.Pusher + blobs *BlobContainers + checked bool +} + +var _ support.NamespaceContainer = (*NamespaceContainer)(nil) + +func NewNamespace(repo *RepositoryImpl, name string) (cpi.NamespaceAccess, error) { + ref := repo.GetRef(name, "") + resolver, err := repo.getResolver(name) + if err != nil { + return nil, err + } + fetcher, err := resolver.Fetcher(context.Background(), ref) + if err != nil { + return nil, err + } + pusher, err := resolver.Pusher(context.Background(), ref) + if err != nil { + return nil, err + } + lister, err := resolver.Lister(context.Background(), ref) + if err != nil { + return nil, err + } + c := &NamespaceContainer{ + repo: repo, + resolver: resolver, + lister: lister, + fetcher: fetcher, + pusher: pusher, + blobs: NewBlobContainers(repo.GetContext(), fetcher, pusher), + } + return support.NewNamespaceAccess(name, c, repo) +} + +func (n *NamespaceContainer) Close() error { + return n.blobs.Release() +} + +func (n *NamespaceContainer) SetImplementation(impl support.NamespaceAccessImpl) { + n.impl = impl +} + +func (n *NamespaceContainer) getPusher(vers string) (resolve.Pusher, error) { + err := n.assureCreated() + if err != nil { + return nil, err + } + + ref := n.repo.GetRef(n.impl.GetNamespace(), vers) + resolver := n.resolver + + n.repo.GetContext().Logger().Trace("get pusher", "ref", ref) + if ok, _ := artdesc.IsDigest(vers); !ok { + var err error + + resolver, err = n.repo.getResolver(n.impl.GetNamespace()) + if err != nil { + return nil, fmt.Errorf("unable get resolver: %w", err) + } + } + + return resolver.Pusher(dummyContext, ref) +} + +func (n *NamespaceContainer) push(vers string, blob cpi.BlobAccess) error { + p, err := n.getPusher(vers) + if err != nil { + return fmt.Errorf("unable to get pusher: %w", err) + } + n.repo.GetContext().Logger().Trace("pushing", "version", vers) + return push(dummyContext, p, blob) +} + +func (n *NamespaceContainer) IsReadOnly() bool { + return n.repo.IsReadOnly() +} + +func (n *NamespaceContainer) GetBlobDescriptor(digest digest.Digest) *cpi.Descriptor { + return nil +} + +func (n *NamespaceContainer) GetBlobData(digest digest.Digest) (int64, cpi.DataAccess, error) { + n.repo.GetContext().Logger().Debug("getting blob", "digest", digest) + blob, err := n.blobs.Get("") + if err != nil { + return -1, nil, fmt.Errorf("failed to retrieve blob data: %w", err) + } + size, acc, err := blob.GetBlobData(digest) + n.repo.GetContext().Logger().Debug("getting blob done", "digest", digest, "size", size, "error", logging.ErrorMessage(err)) + return size, acc, err +} + +func (n *NamespaceContainer) AddBlob(blob cpi.BlobAccess) error { + log := n.repo.GetContext().Logger() + log.Debug("adding blob", "digest", blob.Digest()) + blobData, err := n.blobs.Get("") + if err != nil { + return fmt.Errorf("failed to retrieve blob data: %w", err) + } + err = n.assureCreated() + if err != nil { + return err + } + if _, _, err := blobData.AddBlob(blob); err != nil { + log.Debug("adding blob failed", "digest", blob.Digest(), "error", err.Error()) + return fmt.Errorf("unable to add blob (OCI repository %s): %w", n.impl.GetNamespace(), err) + } + log.Debug("adding blob done", "digest", blob.Digest()) + return nil +} + +func (n *NamespaceContainer) ListTags() ([]string, error) { + return n.lister.List(dummyContext) +} + +func (n *NamespaceContainer) GetArtifact(i support.NamespaceAccessImpl, vers string) (cpi.ArtifactAccess, error) { + ref := n.repo.GetRef(n.impl.GetNamespace(), vers) + n.repo.GetContext().Logger().Debug("get artifact", "ref", ref) + _, desc, err := n.resolver.Resolve(context.Background(), ref) + n.repo.GetContext().Logger().Debug("done", "digest", desc.Digest, "size", desc.Size, "mimetype", desc.MediaType, "error", logging.ErrorMessage(err)) + if err != nil { + if errdefs.IsNotFound(err) { + return nil, errors.ErrNotFound(cpi.KIND_OCIARTIFACT, ref, n.impl.GetNamespace()) + } + return nil, err + } + blobData, err := n.blobs.Get(desc.MediaType) + if err != nil { + return nil, fmt.Errorf("failed to retrieve blob data, blob data was empty: %w", err) + } + _, acc, err := blobData.GetBlobData(desc.Digest) + if err != nil { + return nil, err + } + return support.NewArtifactForBlob(i, blobaccess.ForDataAccess(desc.Digest, desc.Size, desc.MediaType, acc)) +} + +func (n *NamespaceContainer) HasArtifact(vers string) (bool, error) { + ref := n.repo.GetRef(n.impl.GetNamespace(), vers) + n.repo.GetContext().Logger().Debug("check artifact", "ref", ref) + _, desc, err := n.resolver.Resolve(context.Background(), ref) + n.repo.GetContext().Logger().Debug("done", "digest", desc.Digest, "size", desc.Size, "mimetype", desc.MediaType, "error", logging.ErrorMessage(err)) + if err != nil { + if errdefs.IsNotFound(err) { + return false, nil + } + return false, err + } + return true, nil +} + +func (n *NamespaceContainer) assureCreated() error { + if n.checked { + return nil + } + var props common.Properties + if creds, err := n.repo.getCreds(n.impl.GetNamespace()); err == nil && creds != nil { + props = creds.Properties() + } + r, err := oci_repository_prepare.Execute(n.repo.GetContext().GetActions(), n.repo.info.HostPort(), n.impl.GetNamespace(), props) + n.checked = true + if err != nil { + return err + } + if r != nil { + n.repo.GetContext().Logger().Debug("prepare action executed", "message", r.Message) + } + return nil +} + +func (n *NamespaceContainer) AddArtifact(artifact cpi.Artifact, tags ...string) (access blobaccess.BlobAccess, err error) { + blob, err := artifact.Blob() + if err != nil { + return nil, err + } + + if n.repo.info.Legacy { + blob = artdesc.MapArtifactBlobMimeType(blob, true) + } + + n.repo.GetContext().Logger().Debug("adding artifact", "digest", blob.Digest(), "mimetype", blob.MimeType()) + blobData, err := n.blobs.Get(blob.MimeType()) + if err != nil { + return nil, fmt.Errorf("failed to retrieve blob data: %w", err) + } + + _, _, err = blobData.AddBlob(blob) + if err != nil { + return nil, err + } + + if len(tags) > 0 { + for _, tag := range tags { + if err := n.push(tag, blob); err != nil { + return nil, err + } + } + } + + return blob, err +} + +func (n *NamespaceContainer) AddTags(digest digest.Digest, tags ...string) error { + _, desc, err := n.resolver.Resolve(context.Background(), n.repo.GetRef(n.impl.GetNamespace(), digest.String())) + if err != nil { + return fmt.Errorf("unable to resolve: %w", err) + } + + acc, err := NewDataAccess(n.fetcher, desc.Digest, desc.MediaType, false) + if err != nil { + return fmt.Errorf("error creating new data access: %w", err) + } + + blob := blobaccess.ForDataAccess(desc.Digest, desc.Size, desc.MediaType, acc) + for _, tag := range tags { + err := n.push(tag, blob) + if err != nil { + return fmt.Errorf("unable to push: %w", err) + } + } + + return nil +} + +func (n *NamespaceContainer) NewArtifact(i support.NamespaceAccessImpl, art ...cpi.Artifact) (cpi.ArtifactAccess, error) { + if n.IsReadOnly() { + return nil, accessio.ErrReadOnly + } + return support.NewArtifact(i, art...) +} diff --git a/api/oci/extensions/repositories/ocireg/repository.go b/api/oci/extensions/repositories/ocireg/repository.go new file mode 100644 index 000000000..dacfe3420 --- /dev/null +++ b/api/oci/extensions/repositories/ocireg/repository.go @@ -0,0 +1,218 @@ +package ocireg + +import ( + "context" + "crypto/tls" + "crypto/x509" + "path" + "strings" + + "github.com/containerd/containerd/remotes/docker/config" + "github.com/containerd/errdefs" + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/logging" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/datacontext/attrs/rootcertsattr" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/tech/docker" + "ocm.software/ocm/api/tech/docker/resolve" + "ocm.software/ocm/api/utils" + ocmlog "ocm.software/ocm/api/utils/logging" + "ocm.software/ocm/api/utils/refmgmt" +) + +type RepositoryInfo struct { + Scheme string + Locator string + Creds credentials.Credentials + Legacy bool +} + +func (r *RepositoryInfo) HostPort() string { + i := strings.Index(r.Locator, "/") + if i < 0 { + return r.Locator + } else { + return r.Locator[:i] + } +} + +func (r *RepositoryInfo) HostInfo() (string, string, string) { + return utils.SplitLocator(r.Locator) +} + +type RepositoryImpl struct { + cpi.RepositoryImplBase + logger logging.UnboundLogger + spec *RepositorySpec + info *RepositoryInfo +} + +var ( + _ cpi.RepositoryImpl = (*RepositoryImpl)(nil) + _ credentials.ConsumerIdentityProvider = &RepositoryImpl{} +) + +func NewRepository(ctx cpi.Context, spec *RepositorySpec, info *RepositoryInfo) (cpi.Repository, error) { + urs := spec.UniformRepositorySpec() + if urs.Scheme == "http" { + ocmlog.Logger(REALM).Warn("using insecure http for oci registry {{host}}", "host", urs.Host) + } + i := &RepositoryImpl{ + RepositoryImplBase: cpi.NewRepositoryImplBase(ctx), + logger: logging.DynamicLogger(ctx, REALM, logging.NewAttribute(ocmlog.ATTR_HOST, urs.Host)), + spec: spec, + info: info, + } + return cpi.NewRepository(i), nil +} + +func GetRepositoryImplementation(r cpi.Repository) (*RepositoryImpl, error) { + i, err := cpi.GetRepositoryImplementation(r) + if err != nil { + return nil, err + } + return i.(*RepositoryImpl), nil +} + +func (r *RepositoryImpl) GetSpecification() cpi.RepositorySpec { + return r.spec +} + +func (r *RepositoryImpl) Close() error { + return nil +} + +func (r *RepositoryImpl) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { + if c, ok := utils.Optional(uctx...).(credentials.StringUsageContext); ok { + return identity.GetConsumerId(r.info.Locator, c.String()) + } + return identity.GetConsumerId(r.info.Locator, "") +} + +func (r *RepositoryImpl) GetIdentityMatcher() string { + return identity.CONSUMER_TYPE +} + +func (r *RepositoryImpl) NamespaceLister() cpi.NamespaceLister { + return nil +} + +func (r *RepositoryImpl) IsReadOnly() bool { + return false +} + +func (r *RepositoryImpl) getCreds(comp string) (credentials.Credentials, error) { + if r.info.Creds != nil { + return r.info.Creds, nil + } + return identity.GetCredentials(r.GetContext(), r.info.Locator, comp) +} + +func (r *RepositoryImpl) getResolver(comp string) (resolve.Resolver, error) { + creds, err := r.getCreds(comp) + if err != nil { + if !errors.IsErrUnknownKind(err, credentials.KIND_CONSUMER) { + return nil, err + } + } + logger := r.logger.BoundLogger().WithValues(ocmlog.ATTR_NAMESPACE, comp) + if creds == nil { + logger.Trace("no credentials") + } + + opts := docker.ResolverOptions{ + Hosts: docker.ConvertHosts(config.ConfigureHosts(context.Background(), config.HostOptions{ + Credentials: func(host string) (string, string, error) { + if creds != nil { + p := creds.GetProperty(credentials.ATTR_IDENTITY_TOKEN) + if p == "" { + p = creds.GetProperty(credentials.ATTR_PASSWORD) + } + pw := "" + if p != "" { + pw = "***" + } + logger.Trace("query credentials", ocmlog.ATTR_USER, creds.GetProperty(credentials.ATTR_USERNAME), "pass", pw) + return creds.GetProperty(credentials.ATTR_USERNAME), p, nil + } + logger.Trace("no credentials") + return "", "", nil + }, + DefaultScheme: r.info.Scheme, + //nolint:gosec // used like the default, there are OCI servers (quay.io) not working with min version. + DefaultTLS: func() *tls.Config { + if r.info.Scheme == "http" { + return nil + } + return &tls.Config{ + // MinVersion: tls.VersionTLS13, + RootCAs: func() *x509.CertPool { + var rootCAs *x509.CertPool + if creds != nil { + c := creds.GetProperty(credentials.ATTR_CERTIFICATE_AUTHORITY) + if c != "" { + rootCAs = x509.NewCertPool() + rootCAs.AppendCertsFromPEM([]byte(c)) + } + } + if rootCAs == nil { + rootCAs = rootcertsattr.Get(r.GetContext()).GetRootCertPool(true) + } + return rootCAs + }(), + } + }(), + })), + } + + return docker.NewResolver(opts), nil +} + +func (r *RepositoryImpl) GetRef(comp, vers string) string { + base := path.Join(r.info.Locator, comp) + if vers == "" { + return base + } + if ok, d := artdesc.IsDigest(vers); ok { + return base + "@" + d.String() + } + return base + ":" + vers +} + +func (r *RepositoryImpl) GetBaseURL() string { + return r.spec.BaseURL +} + +func (r *RepositoryImpl) ExistsArtifact(name string, version string) (bool, error) { + res, err := r.getResolver(name) + if err != nil { + return false, err + } + ref := r.GetRef(name, version) + _, _, err = res.Resolve(context.Background(), ref) + if err != nil { + if errdefs.IsNotFound(err) { + return false, nil + } + return false, err + } + return true, nil +} + +func (r *RepositoryImpl) LookupArtifact(name string, version string) (acc cpi.ArtifactAccess, err error) { + ns, err := NewNamespace(r, name) + if err != nil { + return nil, err + } + defer refmgmt.PropagateCloseTemporary(&err, ns) // temporary namespace object not exposed. + + return ns.GetArtifact(version) +} + +func (r *RepositoryImpl) LookupNamespace(name string) (cpi.NamespaceAccess, error) { + return NewNamespace(r, name) +} diff --git a/api/oci/extensions/repositories/ocireg/type.go b/api/oci/extensions/repositories/ocireg/type.go new file mode 100644 index 000000000..8c3d92b1b --- /dev/null +++ b/api/oci/extensions/repositories/ocireg/type.go @@ -0,0 +1,144 @@ +package ocireg + +import ( + "fmt" + "net/url" + "strings" + + "github.com/containerd/containerd/reference" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + LegacyType = "ociRegistry" + Type = "OCIRegistry" + TypeV1 = Type + runtime.VersionSeparator + "v1" + + ShortType = "oci" + ShortTypeV1 = ShortType + runtime.VersionSeparator + "v1" +) + +func init() { + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](LegacyType)) + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](Type)) + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1)) + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](ShortType)) + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](ShortTypeV1)) +} + +// Is checks the kind. +func Is(spec cpi.RepositorySpec) bool { + return spec != nil && spec.GetKind() == Type || spec.GetKind() == LegacyType +} + +func IsKind(k string) bool { + return k == Type || k == LegacyType +} + +// RepositorySpec describes an OCI registry interface backed by an oci registry. +type RepositorySpec struct { + runtime.ObjectVersionedType `json:",inline"` + // BaseURL is the base url of the repository to resolve artifacts. + BaseURL string `json:"baseUrl"` + LegacyTypes *bool `json:"legacyTypes,omitempty"` +} + +var ( + _ cpi.RepositorySpec = (*RepositorySpec)(nil) + _ credentials.ConsumerIdentityProvider = (*RepositorySpec)(nil) +) + +// NewRepositorySpec creates a new RepositorySpec. +func NewRepositorySpec(baseURL string) *RepositorySpec { + return &RepositorySpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(Type), + BaseURL: baseURL, + } +} + +func NewLegacyRepositorySpec(baseURL string) *RepositorySpec { + return &RepositorySpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(LegacyType), + BaseURL: baseURL, + } +} + +func (a *RepositorySpec) GetType() string { + return Type +} + +func (a *RepositorySpec) Name() string { + return a.BaseURL +} + +func (a *RepositorySpec) UniformRepositorySpec() *cpi.UniformRepositorySpec { + return cpi.UniformRepositorySpecForHostURL(Type, a.BaseURL) +} + +func (a *RepositorySpec) getInfo(creds credentials.Credentials) (*RepositoryInfo, error) { + var u *url.URL + info := &RepositoryInfo{} + legacy := false + ref, err := reference.Parse(a.BaseURL) + if err == nil { + u, err = url.Parse("https://" + ref.Locator) + if err != nil { + return nil, err + } + info.Locator = ref.Locator + if ref.Object != "" { + return nil, fmt.Errorf("invalid repository locator %q", a.BaseURL) + } + } else { + u, err = url.Parse(a.BaseURL) + if err != nil { + return nil, err + } + info.Locator = u.Host + } + if a.LegacyTypes != nil { + legacy = *a.LegacyTypes + } else { + idx := strings.Index(info.Locator, "/") + host := info.Locator + if idx > 0 { + host = info.Locator[:idx] + } + if host == "docker.io" { + legacy = true + } + } + info.Scheme = u.Scheme + info.Creds = creds + info.Legacy = legacy + + return info, nil +} + +func (a *RepositorySpec) Repository(ctx cpi.Context, creds credentials.Credentials) (cpi.Repository, error) { + info, err := a.getInfo(creds) + if err != nil { + return nil, err + } + return NewRepository(ctx, a, info) +} + +func (a *RepositorySpec) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { + info, err := a.getInfo(nil) + if err != nil { + return nil + } + if c, ok := utils.Optional(uctx...).(credentials.StringUsageContext); ok { + return identity.GetConsumerId(info.Locator, c.String()) + } + return identity.GetConsumerId(info.Locator, "") +} + +func (a *RepositorySpec) GetIdentityMatcher() string { + return identity.CONSUMER_TYPE +} diff --git a/api/oci/extensions/repositories/ocireg/uniform.go b/api/oci/extensions/repositories/ocireg/uniform.go new file mode 100644 index 000000000..b568ae435 --- /dev/null +++ b/api/oci/extensions/repositories/ocireg/uniform.go @@ -0,0 +1,37 @@ +package ocireg + +import ( + regex "github.com/mandelsoft/goutils/regexutils" + + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/oci/grammar" +) + +func init() { + cpi.RegisterRepositorySpecHandler(&repospechandler{}, Type, "") +} + +type repospechandler struct{} + +func (h *repospechandler) MapReference(ctx cpi.Context, u *cpi.UniformRepositorySpec) (cpi.RepositorySpec, error) { + scheme := u.Scheme + host := u.Host + if u.Host == "" && u.Scheme == "" && u.Info != "" { + host = u.Info + match := grammar.AnchoredSchemedRegexp.FindStringSubmatch(host) + if match != nil { + scheme = match[1] + host = match[2] + } + if !(regex.Anchored(grammar.HostPortRegexp).MatchString(host) || regex.Anchored(grammar.DomainPortRegexp).MatchString(host)) { + return nil, nil + } + } else if u.Info != "" || u.Host == "" { + return nil, nil + } + + if scheme != "" { + host = scheme + "://" + host + } + return NewRepositorySpec(host), nil +} diff --git a/api/oci/extensions/repositories/ocireg/utils.go b/api/oci/extensions/repositories/ocireg/utils.go new file mode 100644 index 000000000..17a96f040 --- /dev/null +++ b/api/oci/extensions/repositories/ocireg/utils.go @@ -0,0 +1,115 @@ +package ocireg + +import ( + "context" + "fmt" + "io" + "sync" + + "github.com/containerd/containerd/remotes" + "github.com/containerd/errdefs" + "github.com/containerd/log" + "github.com/opencontainers/go-digest" + "github.com/sirupsen/logrus" + + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/tech/docker/resolve" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/logging" +) + +// TODO: add cache + +type dataAccess struct { + accessio.NopCloser + lock sync.Mutex + fetcher remotes.Fetcher + desc artdesc.Descriptor + reader io.ReadCloser +} + +var _ cpi.DataAccess = (*dataAccess)(nil) + +func NewDataAccess(fetcher remotes.Fetcher, digest digest.Digest, mimeType string, delayed bool) (*dataAccess, error) { + var reader io.ReadCloser + var err error + desc := artdesc.Descriptor{ + MediaType: mimeType, + Digest: digest, + Size: blobaccess.BLOB_UNKNOWN_SIZE, + } + if !delayed { + reader, err = fetcher.Fetch(dummyContext, desc) + if err != nil { + return nil, err + } + } + return &dataAccess{ + fetcher: fetcher, + desc: desc, + reader: reader, + }, nil +} + +func (d *dataAccess) Get() ([]byte, error) { + return readAll(d.Reader()) +} + +func (d *dataAccess) Reader() (io.ReadCloser, error) { + d.lock.Lock() + reader := d.reader + d.reader = nil + d.lock.Unlock() + if reader != nil { + return reader, nil + } + return d.fetcher.Fetch(dummyContext, d.desc) +} + +func readAll(reader io.ReadCloser, err error) ([]byte, error) { + if err != nil { + return nil, err + } + defer reader.Close() + + data, err := io.ReadAll(reader) + if err != nil { + return nil, err + } + return data, nil +} + +func push(ctx context.Context, p resolve.Pusher, blob blobaccess.BlobAccess) error { + desc := *artdesc.DefaultBlobDescriptor(blob) + return pushData(ctx, p, desc, blob) +} + +func pushData(ctx context.Context, p resolve.Pusher, desc artdesc.Descriptor, data blobaccess.DataAccess) error { + key := remotes.MakeRefKey(ctx, desc) + if desc.Size == 0 { + desc.Size = -1 + } + + logging.Logger().Debug("*** push blob", "mediatype", desc.MediaType, "digest", desc.Digest, "key", key) + req, err := p.Push(ctx, desc, data) + if err != nil { + if errdefs.IsAlreadyExists(err) { + logging.Logger().Debug("blob already exists", "mediatype", desc.MediaType, "digest", desc.Digest) + + return nil + } + return fmt.Errorf("failed to push: %w", err) + } + return req.Commit(ctx, desc.Size, desc.Digest) +} + +var dummyContext = nologger() + +func nologger() context.Context { + ctx := context.Background() + logger := logrus.New() + logger.Level = logrus.ErrorLevel + return log.WithLogger(ctx, logrus.NewEntry(logger)) +} diff --git a/api/oci/gc_test.go b/api/oci/gc_test.go new file mode 100644 index 000000000..f05b65620 --- /dev/null +++ b/api/oci/gc_test.go @@ -0,0 +1,34 @@ +package oci_test + +import ( + "runtime" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + me "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/utils/runtimefinalizer" +) + +var _ = Describe("area test", func() { + It("can be garbage collected", func() { + ctx := me.New() + + r := runtimefinalizer.GetRuntimeFinalizationRecorder(ctx) + Expect(r).NotTo(BeNil()) + + runtime.GC() + time.Sleep(time.Second) + ctx.GetType() + Expect(r.Get()).To(BeNil()) + + ctx = nil + for i := 0; i < 100; i++ { + runtime.GC() + time.Sleep(time.Millisecond) + } + + Expect(r.Get()).To(ContainElement(ContainSubstring(me.CONTEXT_TYPE))) + }) +}) diff --git a/pkg/contexts/oci/grammar/grammar.go b/api/oci/grammar/grammar.go similarity index 100% rename from pkg/contexts/oci/grammar/grammar.go rename to api/oci/grammar/grammar.go diff --git a/pkg/contexts/oci/grammar/grammar_test.go b/api/oci/grammar/grammar_test.go similarity index 100% rename from pkg/contexts/oci/grammar/grammar_test.go rename to api/oci/grammar/grammar_test.go diff --git a/api/oci/init.go b/api/oci/init.go new file mode 100644 index 000000000..3fbf3c961 --- /dev/null +++ b/api/oci/init.go @@ -0,0 +1,8 @@ +package oci + +import ( + _ "ocm.software/ocm/api/oci/config" + _ "ocm.software/ocm/api/oci/extensions/actions" + _ "ocm.software/ocm/api/oci/extensions/attrs" + _ "ocm.software/ocm/api/oci/extensions/repositories" +) diff --git a/api/oci/interface.go b/api/oci/interface.go new file mode 100644 index 000000000..3cde1e783 --- /dev/null +++ b/api/oci/interface.go @@ -0,0 +1,64 @@ +package oci + +import ( + "context" + + "ocm.software/ocm/api/oci/internal" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +const ( + KIND_OCIARTIFACT = internal.KIND_OCIARTIFACT + KIND_MEDIATYPE = blobaccess.KIND_MEDIATYPE + KIND_BLOB = blobaccess.KIND_BLOB +) + +const CONTEXT_TYPE = internal.CONTEXT_TYPE + +const CommonTransportFormat = internal.CommonTransportFormat + +type ( + Context = internal.Context + ContextProvider = internal.ContextProvider + Repository = internal.Repository + RepositorySpecHandlers = internal.RepositorySpecHandlers + RepositorySpecHandler = internal.RepositorySpecHandler + UniformRepositorySpec = internal.UniformRepositorySpec + RepositoryTypeScheme = internal.RepositoryTypeScheme + RepositorySpec = internal.RepositorySpec + RepositoryType = internal.RepositoryType + IntermediateRepositorySpecAspect = internal.IntermediateRepositorySpecAspect + GenericRepositorySpec = internal.GenericRepositorySpec + ArtifactAccess = internal.ArtifactAccess + NamespaceLister = internal.NamespaceLister + NamespaceAccess = internal.NamespaceAccess + ManifestAccess = internal.ManifestAccess + IndexAccess = internal.IndexAccess + BlobAccess = internal.BlobAccess + DataAccess = internal.DataAccess + ConsumerIdentityProvider = internal.ConsumerIdentityProvider +) + +func DefaultContext() internal.Context { + return internal.DefaultContext +} + +func FromContext(ctx context.Context) Context { + return internal.ForContext(ctx) +} + +func FromProvider(p ContextProvider) Context { + return internal.FromProvider(p) +} + +func DefinedForContext(ctx context.Context) (Context, bool) { + return internal.DefinedForContext(ctx) +} + +func IsErrBlobNotFound(err error) bool { + return blobaccess.IsErrBlobNotFound(err) +} + +func ToGenericRepositorySpec(spec RepositorySpec) (*GenericRepositorySpec, error) { + return internal.ToGenericRepositorySpec(spec) +} diff --git a/api/oci/internal/builder.go b/api/oci/internal/builder.go new file mode 100644 index 000000000..005cfce7c --- /dev/null +++ b/api/oci/internal/builder.go @@ -0,0 +1,92 @@ +package internal + +import ( + "context" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext" +) + +type Builder struct { + ctx context.Context + credentials credentials.Context + reposcheme RepositoryTypeScheme + spechandlers RepositorySpecHandlers +} + +func (b *Builder) getContext() context.Context { + if b.ctx == nil { + return context.Background() + } + return b.ctx +} + +func (b Builder) WithContext(ctx context.Context) Builder { + b.ctx = ctx + return b +} + +func (b Builder) WithCredentials(ctx credentials.Context) Builder { + b.credentials = ctx + return b +} + +func (b Builder) WithRepositoyTypeScheme(scheme RepositoryTypeScheme) Builder { + b.reposcheme = scheme + return b +} + +func (b Builder) WithRepositorySpecHandlers(reg RepositorySpecHandlers) Builder { + b.spechandlers = reg + return b +} + +func (b Builder) Bound() (Context, context.Context) { + c := b.New() + return c, context.WithValue(b.getContext(), key, c) +} + +func (b Builder) New(m ...datacontext.BuilderMode) Context { + mode := datacontext.Mode(m...) + ctx := b.getContext() + + if b.credentials == nil { + var ok bool + b.credentials, ok = credentials.DefinedForContext(ctx) + if !ok && mode != datacontext.MODE_SHARED { + b.credentials = credentials.New(mode) + } else { + b.credentials = credentials.FromContext(ctx) + } + } + if b.reposcheme == nil { + switch mode { + case datacontext.MODE_INITIAL: + b.reposcheme = NewRepositoryTypeScheme(nil) + case datacontext.MODE_CONFIGURED: + b.reposcheme = NewRepositoryTypeScheme(nil) + b.reposcheme.AddKnownTypes(DefaultRepositoryTypeScheme) + case datacontext.MODE_EXTENDED: + b.reposcheme = NewRepositoryTypeScheme(nil, DefaultRepositoryTypeScheme) + case datacontext.MODE_DEFAULTED: + fallthrough + case datacontext.MODE_SHARED: + b.reposcheme = DefaultRepositoryTypeScheme + } + } + if b.spechandlers == nil { + switch mode { + case datacontext.MODE_INITIAL: + b.spechandlers = NewRepositorySpecHandlers() + case datacontext.MODE_CONFIGURED: + b.spechandlers = DefaultRepositorySpecHandlers.Copy() + case datacontext.MODE_EXTENDED: + fallthrough + case datacontext.MODE_DEFAULTED: + fallthrough + case datacontext.MODE_SHARED: + b.spechandlers = DefaultRepositorySpecHandlers + } + } + return datacontext.SetupContext(mode, newContext(b.credentials, b.reposcheme, b.spechandlers, b.credentials)) +} diff --git a/api/oci/internal/builder_test.go b/api/oci/internal/builder_test.go new file mode 100644 index 000000000..3ccb70113 --- /dev/null +++ b/api/oci/internal/builder_test.go @@ -0,0 +1,71 @@ +package internal_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext" + local "ocm.software/ocm/api/oci/internal" +) + +var _ = Describe("builder test", func() { + It("creates local", func() { + ctx := local.Builder{}.New(datacontext.MODE_SHARED) + + Expect(ctx.AttributesContext()).To(BeIdenticalTo(datacontext.DefaultContext)) + Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) + Expect(ctx.RepositoryTypes()).To(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) + + Expect(ctx.ConfigContext().GetId()).To(BeIdenticalTo(config.DefaultContext().GetId())) + + Expect(ctx.CredentialsContext()).To(BeIdenticalTo(credentials.DefaultContext())) + }) + + It("creates defaulted", func() { + ctx := local.Builder{}.New(datacontext.MODE_DEFAULTED) + + Expect(ctx.AttributesContext()).NotTo(BeIdenticalTo(datacontext.DefaultContext)) + Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) + Expect(ctx.RepositoryTypes()).To(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) + + Expect(ctx.ConfigContext()).NotTo(BeIdenticalTo(config.DefaultContext())) + Expect(ctx.ConfigContext().ConfigTypes()).To(BeIdenticalTo(config.DefaultContext().ConfigTypes())) + + Expect(ctx.CredentialsContext()).NotTo(BeIdenticalTo(credentials.DefaultContext())) + Expect(ctx.CredentialsContext().RepositoryTypes()).To(BeIdenticalTo(credentials.DefaultContext().RepositoryTypes())) + }) + + It("creates configured", func() { + ctx := local.Builder{}.New(datacontext.MODE_CONFIGURED) + + Expect(ctx.AttributesContext()).NotTo(BeIdenticalTo(datacontext.DefaultContext)) + Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) + Expect(ctx.RepositoryTypes()).NotTo(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) + Expect(ctx.RepositoryTypes().KnownTypeNames()).To(Equal(local.DefaultRepositoryTypeScheme.KnownTypeNames())) + + Expect(ctx.ConfigContext()).NotTo(BeIdenticalTo(config.DefaultContext())) + Expect(ctx.ConfigContext().ConfigTypes()).NotTo(BeIdenticalTo(config.DefaultContext().ConfigTypes())) + Expect(ctx.ConfigContext().ConfigTypes().KnownTypeNames()).To(Equal(config.DefaultContext().ConfigTypes().KnownTypeNames())) + + Expect(ctx.CredentialsContext()).NotTo(BeIdenticalTo(config.DefaultContext())) + Expect(ctx.CredentialsContext().RepositoryTypes()).NotTo(BeIdenticalTo(credentials.DefaultContext().RepositoryTypes())) + Expect(ctx.CredentialsContext().RepositoryTypes().KnownTypeNames()).To(Equal(credentials.DefaultContext().RepositoryTypes().KnownTypeNames())) + }) + + It("creates iniial", func() { + ctx := local.Builder{}.New(datacontext.MODE_INITIAL) + + Expect(ctx.AttributesContext()).NotTo(BeIdenticalTo(datacontext.DefaultContext)) + Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) + Expect(ctx.RepositoryTypes()).NotTo(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) + Expect(len(ctx.RepositoryTypes().KnownTypeNames())).To(Equal(0)) + + Expect(ctx.ConfigContext()).NotTo(BeIdenticalTo(config.DefaultContext())) + Expect(len(ctx.ConfigContext().ConfigTypes().KnownTypeNames())).To(Equal(0)) + + Expect(ctx.CredentialsContext()).NotTo(BeIdenticalTo(credentials.DefaultContext())) + Expect(len(ctx.CredentialsContext().RepositoryTypes().KnownTypeNames())).To(Equal(0)) + }) +}) diff --git a/api/oci/internal/context.go b/api/oci/internal/context.go new file mode 100644 index 000000000..883d8caac --- /dev/null +++ b/api/oci/internal/context.go @@ -0,0 +1,195 @@ +package internal + +import ( + "context" + "reflect" + "strings" + + "ocm.software/ocm/api/config" + cfgcpi "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" +) + +const CONTEXT_TYPE = "oci" + datacontext.OCM_CONTEXT_SUFFIX + +const CommonTransportFormat = "CommonTransportFormat" + +type ContextProvider interface { + OCIContext() Context +} + +type Context interface { + datacontext.Context + config.ContextProvider + credentials.ContextProvider + ContextProvider + + RepositorySpecHandlers() RepositorySpecHandlers + MapUniformRepositorySpec(u *UniformRepositorySpec) (RepositorySpec, error) + + RepositoryTypes() RepositoryTypeScheme + + RepositoryForSpec(spec RepositorySpec, creds ...credentials.CredentialsSource) (Repository, error) + RepositoryForConfig(data []byte, unmarshaler runtime.Unmarshaler, creds ...credentials.CredentialsSource) (Repository, error) + RepositorySpecForConfig(data []byte, unmarshaler runtime.Unmarshaler) (RepositorySpec, error) + + GetAlias(name string) RepositorySpec + SetAlias(name string, spec RepositorySpec) +} + +var key = reflect.TypeOf(_context{}) + +// DefaultContext is the default context initialized by init functions. +var DefaultContext = Builder{}.New(datacontext.MODE_SHARED) + +// ForContext returns the Context to use for context.Context. +// This is either an explicit context or the default context. +func ForContext(ctx context.Context) Context { + c, _ := datacontext.ForContextByKey(ctx, key, DefaultContext) + return c.(Context) +} + +func FromProvider(p ContextProvider) Context { + if p == nil { + return nil + } + return p.OCIContext() +} + +func DefinedForContext(ctx context.Context) (Context, bool) { + c, ok := datacontext.ForContextByKey(ctx, key, DefaultContext) + if c != nil { + return c.(Context), ok + } + return nil, ok +} + +//////////////////////////////////////////////////////////////////////////////// + +type _InternalContext = datacontext.InternalContext + +type _context struct { + _InternalContext + updater cfgcpi.Updater + + credentials credentials.Context + + knownRepositoryTypes RepositoryTypeScheme + specHandlers RepositorySpecHandlers + aliases map[string]RepositorySpec +} + +var ( + _ Context = (*_context)(nil) + _ datacontext.ViewCreator[Context] = (*_context)(nil) +) + +// gcWrapper is used as garbage collectable +// wrapper for a context implementation +// to establish a runtime finalizer. +type gcWrapper struct { + datacontext.GCWrapper + *_context +} + +func newView(c *_context, ref ...bool) Context { + if utils.Optional(ref...) { + return datacontext.FinalizedContext[gcWrapper](c) + } + return c +} + +func (w *gcWrapper) SetContext(c *_context) { + w._context = c +} + +func newContext(credctx credentials.Context, reposcheme RepositoryTypeScheme, specHandlers RepositorySpecHandlers, delegates datacontext.Delegates) Context { + c := &_context{ + credentials: datacontext.PersistentContextRef(credctx), + knownRepositoryTypes: reposcheme, + specHandlers: specHandlers, + aliases: map[string]RepositorySpec{}, + } + c._InternalContext = datacontext.NewContextBase(c, CONTEXT_TYPE, key, credctx.ConfigContext().GetAttributes(), delegates) + c.updater = cfgcpi.NewUpdaterForFactory(credctx.ConfigContext(), c.OCIContext) + return newView(c, true) +} + +func (c *_context) CreateView() Context { + return newView(c, true) +} + +func (c *_context) OCIContext() Context { + return newView(c) +} + +func (c *_context) Update() error { + return c.updater.Update() +} + +func (c *_context) AttributesContext() datacontext.AttributesContext { + return c.credentials.AttributesContext() +} + +func (c *_context) ConfigContext() config.Context { + return c.updater.GetContext() +} + +func (c *_context) CredentialsContext() credentials.Context { + return c.credentials +} + +func (c *_context) RepositoryTypes() RepositoryTypeScheme { + return c.knownRepositoryTypes +} + +func (c *_context) RepositorySpecHandlers() RepositorySpecHandlers { + return c.specHandlers +} + +func (c *_context) MapUniformRepositorySpec(u *UniformRepositorySpec) (RepositorySpec, error) { + return c.specHandlers.MapUniformRepositorySpec(c.OCIContext(), u) +} + +func (c *_context) RepositorySpecForConfig(data []byte, unmarshaler runtime.Unmarshaler) (RepositorySpec, error) { + return c.knownRepositoryTypes.Decode(data, unmarshaler) +} + +func (c *_context) RepositoryForSpec(spec RepositorySpec, creds ...credentials.CredentialsSource) (Repository, error) { + cred, err := credentials.CredentialsChain(creds).Credentials(c.CredentialsContext()) + if err != nil { + return nil, err + } + return spec.Repository(c.OCIContext(), cred) +} + +func (c *_context) RepositoryForConfig(data []byte, unmarshaler runtime.Unmarshaler, creds ...credentials.CredentialsSource) (Repository, error) { + spec, err := c.knownRepositoryTypes.Decode(data, unmarshaler) + if err != nil { + return nil, err + } + return c.RepositoryForSpec(spec, creds...) +} + +func (c *_context) GetAlias(name string) RepositorySpec { + err := c.updater.Update() + if err != nil { + return nil + } + c.updater.RLock() + defer c.updater.RUnlock() + spec := c.aliases[name] + if spec == nil && strings.HasSuffix(name, ".alias") { + spec = c.aliases[name[:len(name)-6]] + } + return spec +} + +func (c *_context) SetAlias(name string, spec RepositorySpec) { + c.updater.Lock() + defer c.updater.Unlock() + c.aliases[name] = spec +} diff --git a/api/oci/internal/errors.go b/api/oci/internal/errors.go new file mode 100644 index 000000000..464717f77 --- /dev/null +++ b/api/oci/internal/errors.go @@ -0,0 +1,19 @@ +package internal + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +const ( + KIND_OCIARTIFACT = "oci artifact" + KIND_BLOB = blobaccess.KIND_BLOB + KIND_MEDIATYPE = blobaccess.KIND_MEDIATYPE +) + +func ErrUnknownArtifact(name, version string) error { + return errors.ErrUnknown(KIND_OCIARTIFACT, fmt.Sprintf("%s:%s", name, version)) +} diff --git a/api/oci/internal/repository.go b/api/oci/internal/repository.go new file mode 100644 index 000000000..4e4362207 --- /dev/null +++ b/api/oci/internal/repository.go @@ -0,0 +1,157 @@ +package internal + +import ( + "io" + + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/refmgmt/resource" +) + +type RepositoryImpl interface { + GetSpecification() RepositorySpec + GetContext() Context + + NamespaceLister() NamespaceLister + ExistsArtifact(name string, ref string) (bool, error) + LookupArtifact(name string, ref string) (ArtifactAccess, error) + LookupNamespace(name string) (NamespaceAccess, error) + + io.Closer +} + +type Repository interface { + resource.ResourceView[Repository] + + RepositoryImpl +} + +// ConsumerIdentityProvider is an optional interface for repositories +// to tell about their credential requests. +type ConsumerIdentityProvider = credentials.ConsumerIdentityProvider + +type RepositorySource interface { + GetRepository() Repository +} + +type ( + BlobAccess = blobaccess.BlobAccess + DataAccess = blobaccess.DataAccess +) + +type BlobSource interface { + GetBlobData(digest digest.Digest) (int64, DataAccess, error) +} + +type BlobSink interface { + AddBlob(BlobAccess) error +} + +type ArtifactSink interface { + AddBlob(BlobAccess) error + AddArtifact(a Artifact, tags ...string) (BlobAccess, error) + AddTags(digest digest.Digest, tags ...string) error +} + +type ArtifactSource interface { + GetArtifact(version string) (ArtifactAccess, error) + GetBlobData(digest digest.Digest) (int64, DataAccess, error) +} + +type NamespaceAccessImpl interface { + ArtifactSource + ArtifactSink + GetNamespace() string + ListTags() ([]string, error) + + HasArtifact(vers string) (bool, error) + + NewArtifact(...Artifact) (ArtifactAccess, error) + io.Closer +} + +type NamespaceAccess interface { + resource.ResourceView[NamespaceAccess] + + NamespaceAccessImpl +} + +type Artifact artdesc.ArtifactDescriptor + +type ArtifactAccessImpl interface { + Artifact + BlobSource + BlobSink + + GetDescriptor() *artdesc.Artifact + GetBlob(digest digest.Digest) (BlobAccess, error) + + GetArtifact(digest digest.Digest) (ArtifactAccess, error) + AddBlob(BlobAccess) error + + AddArtifact(Artifact, *artdesc.Platform) (BlobAccess, error) + AddLayer(BlobAccess, *artdesc.Descriptor) (int, error) + + NewArtifact(...Artifact) (ArtifactAccess, error) + + io.Closer +} + +type ArtifactAccessSlaves interface { + ManifestAccess() ManifestAccess + IndexAccess() IndexAccess +} + +type ArtifactAccess interface { + resource.ResourceView[ArtifactAccess] + + ArtifactAccessImpl + ArtifactAccessSlaves +} + +type ManifestAccess interface { + Artifact + + GetDescriptor() *artdesc.Manifest + GetBlobDescriptor(digest digest.Digest) *artdesc.Descriptor + GetConfigBlob() (BlobAccess, error) + GetBlob(digest digest.Digest) (BlobAccess, error) + + AddBlob(BlobAccess) error + AddLayer(BlobAccess, *artdesc.Descriptor) (int, error) + SetConfigBlob(blob BlobAccess, d *artdesc.Descriptor) error +} + +type IndexAccess interface { + Artifact + + GetDescriptor() *artdesc.Index + GetBlobDescriptor(digest digest.Digest) *artdesc.Descriptor + GetBlob(digest digest.Digest) (BlobAccess, error) + + GetArtifact(digest digest.Digest) (ArtifactAccess, error) + /* + GetIndex(digest digest.Digest) (IndexAccess, error) + GetManifest(digest digest.Digest) (ManifestAccess, error) + */ + + AddBlob(BlobAccess) error + AddArtifact(Artifact, *artdesc.Platform) (BlobAccess, error) +} + +// NamespaceLister provides the optional repository list functionality of +// a repository. +type NamespaceLister interface { + // NumNamespaces returns the number of namespaces found for a prefix + // If the given prefix does not end with a /, a repository with the + // prefix name is included + NumNamespaces(prefix string) (int, error) + + // GetNamespaces returns the name of namespaces found for a prefix + // If the given prefix does not end with a /, a repository with the + // prefix name is included + GetNamespaces(prefix string, closure bool) ([]string, error) +} diff --git a/api/oci/internal/repotypes.go b/api/oci/internal/repotypes.go new file mode 100644 index 000000000..9125e57a2 --- /dev/null +++ b/api/oci/internal/repotypes.go @@ -0,0 +1,160 @@ +package internal + +import ( + "encoding/json" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/generics" + "github.com/modern-go/reflect2" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" +) + +type RepositoryType interface { + runtime.VersionedTypedObjectType[RepositorySpec] +} + +type IntermediateRepositorySpecAspect interface { + IsIntermediate() bool +} + +type RepositorySpec interface { + runtime.VersionedTypedObject + + Name() string + UniformRepositorySpec() *UniformRepositorySpec + Repository(Context, credentials.Credentials) (Repository, error) +} + +type ( + RepositorySpecDecoder = runtime.TypedObjectDecoder[RepositorySpec] + RepositoryTypeProvider = runtime.KnownTypesProvider[RepositorySpec, RepositoryType] +) + +type RepositoryTypeScheme interface { + runtime.TypeScheme[RepositorySpec, RepositoryType] +} + +type _Scheme = runtime.TypeScheme[RepositorySpec, RepositoryType] + +type repositoryTypeScheme struct { + _Scheme +} + +func NewRepositoryTypeScheme(defaultDecoder RepositorySpecDecoder, base ...RepositoryTypeScheme) RepositoryTypeScheme { + scheme := runtime.MustNewDefaultTypeScheme[RepositorySpec, RepositoryType](&UnknownRepositorySpec{}, true, defaultDecoder, utils.Optional(base...)) + return &repositoryTypeScheme{scheme} +} + +func NewStrictRepositoryTypeScheme(base ...RepositoryTypeScheme) runtime.VersionedTypeRegistry[RepositorySpec, RepositoryType] { + scheme := runtime.MustNewDefaultTypeScheme[RepositorySpec, RepositoryType](nil, false, nil, utils.Optional(base...)) + return &repositoryTypeScheme{scheme} +} + +func (t *repositoryTypeScheme) KnownTypes() runtime.KnownTypes[RepositorySpec, RepositoryType] { + return t._Scheme.KnownTypes() +} + +// DefaultRepositoryTypeScheme contains all globally known access serializer. +var DefaultRepositoryTypeScheme = NewRepositoryTypeScheme(nil) + +func RegisterRepositoryType(atype RepositoryType) { + DefaultRepositoryTypeScheme.Register(atype) +} + +func CreateRepositorySpec(t runtime.TypedObject) (RepositorySpec, error) { + return DefaultRepositoryTypeScheme.Convert(t) +} + +//////////////////////////////////////////////////////////////////////////////// + +type UnknownRepositorySpec struct { + runtime.UnstructuredVersionedTypedObject `json:",inline"` +} + +var ( + _ RepositorySpec = &UnknownRepositorySpec{} + _ runtime.Unknown = &UnknownRepositorySpec{} +) + +func (r *UnknownRepositorySpec) IsUnknown() bool { + return true +} + +func (r *UnknownRepositorySpec) Name() string { + return "unknown-" + r.GetKind() +} + +func (r *UnknownRepositorySpec) UniformRepositorySpec() *UniformRepositorySpec { + return UniformRepositorySpecForUnstructured(&r.UnstructuredVersionedTypedObject) +} + +func (r *UnknownRepositorySpec) Repository(Context, credentials.Credentials) (Repository, error) { + return nil, errors.ErrUnknown("repository type", r.GetType()) +} + +//////////////////////////////////////////////////////////////////////////////// + +type GenericRepositorySpec struct { + runtime.UnstructuredVersionedTypedObject `json:",inline"` +} + +var _ RepositorySpec = &GenericRepositorySpec{} + +func ToGenericRepositorySpec(spec RepositorySpec) (*GenericRepositorySpec, error) { + if reflect2.IsNil(spec) { + return nil, nil + } + if g, ok := spec.(*GenericRepositorySpec); ok { + return g, nil + } + data, err := json.Marshal(spec) + if err != nil { + return nil, err + } + return newGenericRepositorySpec(data, runtime.DefaultJSONEncoding) +} + +func NewGenericRepositorySpec(data []byte, unmarshaler runtime.Unmarshaler) (RepositorySpec, error) { + return generics.CastPointerR[RepositorySpec](newGenericRepositorySpec(data, unmarshaler)) +} + +func newGenericRepositorySpec(data []byte, unmarshaler runtime.Unmarshaler) (*GenericRepositorySpec, error) { + unstr := &runtime.UnstructuredVersionedTypedObject{} + if unmarshaler == nil { + unmarshaler = runtime.DefaultYAMLEncoding + } + err := unmarshaler.Unmarshal(data, unstr) + if err != nil { + return nil, err + } + return &GenericRepositorySpec{*unstr}, nil +} + +func (s *GenericRepositorySpec) Name() string { + return "generic-" + s.GetKind() +} + +func (s *GenericRepositorySpec) UniformRepositorySpec() *UniformRepositorySpec { + return UniformRepositorySpecForUnstructured(&s.UnstructuredVersionedTypedObject) +} + +func (s *GenericRepositorySpec) Evaluate(ctx Context) (RepositorySpec, error) { + raw, err := s.GetRaw() + if err != nil { + return nil, err + } + return ctx.RepositoryTypes().Decode(raw, runtime.DefaultJSONEncoding) +} + +func (s *GenericRepositorySpec) Repository(ctx Context, creds credentials.Credentials) (Repository, error) { + spec, err := s.Evaluate(ctx) + if err != nil { + return nil, err + } + return spec.Repository(ctx, creds) +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/pkg/contexts/oci/internal/suite_test.go b/api/oci/internal/suite_test.go similarity index 100% rename from pkg/contexts/oci/internal/suite_test.go rename to api/oci/internal/suite_test.go diff --git a/api/oci/internal/uniform.go b/api/oci/internal/uniform.go new file mode 100644 index 000000000..d35d102af --- /dev/null +++ b/api/oci/internal/uniform.go @@ -0,0 +1,233 @@ +package internal + +import ( + "encoding/json" + "fmt" + "net/url" + "strings" + "sync" + + "github.com/containerd/containerd/reference" + "github.com/mandelsoft/goutils/errors" + "github.com/sirupsen/logrus" + "golang.org/x/exp/slices" + + "ocm.software/ocm/api/oci/grammar" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + dockerHubDomain = "docker.io" + dockerHubLegacyDomain = "index.docker.io" +) + +// UniformRepositorySpec is a generic specification of the repository +// for handling as part of standard references. +type UniformRepositorySpec struct { + // Type + Type string `json:"type,omitempty"` + // Scheme + Scheme string `json:"scheme,omitempty"` + // Host is the hostname of an oci ref. + Host string `json:"host,omitempty"` + // Info is the file path used to host ctf component versions + Info string `json:"filePath,omitempty"` + + // CreateIfMissing indicates whether a file based or dynamic repo should be created if it does not exist + CreateIfMissing bool `json:"createIfMissing,omitempty"` + // TypeHint should be set if CreateIfMissing is true to help to decide what kind of repo to create + TypeHint string `json:"typeHint,omitempty"` +} + +// CredHost fallback to legacy docker domain if applicable +// this is how containerd translates the old domain for DockerHub to the new one, taken from containerd/reference/docker/reference.go:674. +func (u *UniformRepositorySpec) CredHost() string { + if u.Host == dockerHubDomain { + return dockerHubLegacyDomain + } + return u.Host +} + +func (u *UniformRepositorySpec) HostPort() (string, string) { + i := strings.Index(u.Host, ":") + if i < 0 { + return u.Host, "" + } + return u.Host[:i], u.Host[i+1:] +} + +// ComposeRef joins the actual repository spec and a given artifact spec. +func (u *UniformRepositorySpec) ComposeRef(art string) string { + if art == "" { + return u.String() + } + sep := "/" + if u.Info != "" { + sep = "//" + } + return fmt.Sprintf("%s%s%s", u.String(), sep, art) +} + +func (u *UniformRepositorySpec) RepositoryRef() string { + t := u.Type + if t != "" { + t += "::" + } + if u.Info != "" { + return fmt.Sprintf("%s%s", t, u.Info) + } + if u.Scheme == "" { + return fmt.Sprintf("%s%s", t, u.Host) + } + return fmt.Sprintf("%s%s://%s", t, u.Scheme, u.Host) +} + +func (u *UniformRepositorySpec) SetType(typ string) { + t, _ := grammar.SplitTypeSpec(typ) + u.Type = t + u.TypeHint = typ +} + +func (u *UniformRepositorySpec) String() string { + return u.RepositoryRef() +} + +func UniformRepositorySpecForHostURL(typ string, host string) *UniformRepositorySpec { + s := "" + h := host + var parsed *url.URL + ref, err := reference.Parse(host) + if err == nil { + parsed, err = url.Parse("https://" + ref.Locator) + } else { + parsed, err = url.Parse(host) + } + if err == nil { + s = parsed.Scheme + h = parsed.Host + } + u := &UniformRepositorySpec{ + Type: typ, + Scheme: s, + Host: h, + } + return u +} + +func UniformRepositorySpecForUnstructured(un *runtime.UnstructuredVersionedTypedObject) *UniformRepositorySpec { + m := un.Object.FlatCopy() + delete(m, runtime.ATTR_TYPE) + + d, err := json.Marshal(m) + if err != nil { + logrus.Error(err) + } + + return &UniformRepositorySpec{Type: un.Type, Info: string(d)} +} + +type RepositorySpecHandler interface { + MapReference(ctx Context, u *UniformRepositorySpec) (RepositorySpec, error) +} + +type RepositorySpecHandlers interface { + Register(hdlr RepositorySpecHandler, types ...string) + Copy() RepositorySpecHandlers + MapUniformRepositorySpec(ctx Context, u *UniformRepositorySpec) (RepositorySpec, error) +} + +var DefaultRepositorySpecHandlers = NewRepositorySpecHandlers() + +func RegisterRepositorySpecHandler(hdlr RepositorySpecHandler, types ...string) { + DefaultRepositorySpecHandlers.Register(hdlr, types...) +} + +type specHandlers struct { + lock sync.RWMutex + handlers map[string][]RepositorySpecHandler +} + +func NewRepositorySpecHandlers() RepositorySpecHandlers { + return &specHandlers{handlers: map[string][]RepositorySpecHandler{}} +} + +func (s *specHandlers) Register(hdlr RepositorySpecHandler, types ...string) { + s.lock.Lock() + defer s.lock.Unlock() + + if hdlr != nil { + for _, typ := range types { + s.handlers[typ] = append(s.handlers[typ], hdlr) + } + } +} + +func (s *specHandlers) Copy() RepositorySpecHandlers { + s.lock.RLock() + defer s.lock.RUnlock() + + n := NewRepositorySpecHandlers().(*specHandlers) + for typ, hdlrs := range s.handlers { + n.handlers[typ] = slices.Clone(hdlrs) + } + return n +} + +func (s *specHandlers) MapUniformRepositorySpec(ctx Context, u *UniformRepositorySpec) (RepositorySpec, error) { + var err error + s.lock.RLock() + defer s.lock.RUnlock() + deferr := errors.ErrNotSupported("uniform repository ref", u.String()) + + if u.Info != "" && string(u.Info[0]) == "{" && u.Host == "" && u.Scheme == "" { + data, err := runtime.CompleteSpecWithType(u.Type, []byte(u.Info)) + if err != nil { + return nil, err + } + return ctx.RepositorySpecForConfig(data, runtime.DefaultJSONEncoding) + } + + if u.Type == "" { + if u.Info != "" { + spec := ctx.GetAlias(u.Info) + if spec != nil { + return spec, nil + } + deferr = errors.ErrUnknown("repository", u.Info) + } + if u.Host != "" { + spec := ctx.GetAlias(u.Host) + if spec != nil { + return spec, nil + } + deferr = errors.ErrUnknown("repository", u.Host) + } + } + for _, h := range s.handlers[u.Type] { + spec, err := h.MapReference(ctx, u) + if err != nil || spec != nil { + return spec, err + } + } + if u.Info != "" { + spec := &runtime.UnstructuredVersionedTypedObject{} + err = runtime.DefaultJSONEncoding.Unmarshal([]byte(u.Info), spec) + if err == nil { + if spec.GetType() == spec.GetKind() && spec.GetVersion() == "v1" { // only type set, use it as version + spec.SetType(u.Type + runtime.VersionSeparator + spec.GetType()) + } + if spec.GetKind() != u.Type { + return nil, errors.ErrInvalid() + } + return ctx.RepositoryTypes().Convert(spec) + } + } + for _, h := range s.handlers["*"] { + spec, err := h.MapReference(ctx, u) + if err != nil || spec != nil { + return spec, err + } + } + + return nil, deferr +} diff --git a/api/oci/ociutils/handler.go b/api/oci/ociutils/handler.go new file mode 100644 index 000000000..ff3cc687f --- /dev/null +++ b/api/oci/ociutils/handler.go @@ -0,0 +1,30 @@ +package ociutils + +import ( + "sync" + + "ocm.software/ocm/api/oci/cpi" + common "ocm.software/ocm/api/utils/misc" +) + +type InfoHandler interface { + Description(pr common.Printer, m cpi.ManifestAccess, config []byte) + Info(m cpi.ManifestAccess, config []byte) interface{} +} + +var ( + lock sync.Mutex + handlers = map[string]InfoHandler{} +) + +func RegisterInfoHandler(mime string, h InfoHandler) { + lock.Lock() + defer lock.Unlock() + handlers[mime] = h +} + +func getHandler(mime string) InfoHandler { + lock.Lock() + defer lock.Unlock() + return handlers[mime] +} diff --git a/api/oci/ociutils/helm/art_test.go b/api/oci/ociutils/helm/art_test.go new file mode 100644 index 000000000..bed558bac --- /dev/null +++ b/api/oci/ociutils/helm/art_test.go @@ -0,0 +1,115 @@ +package helm_test + +import ( + "encoding/json" + "os" + "sort" + "strings" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/mandelsoft/vfs/pkg/osfs" + "github.com/mandelsoft/vfs/pkg/vfs" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chartutil" + + "ocm.software/ocm/api/helper/builder" + "ocm.software/ocm/api/helper/env" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/oci/ociutils/helm" + "ocm.software/ocm/api/tech/helm/loader" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +type Files []*chart.File + +var _ sort.Interface = (Files)(nil) + +func (f Files) Len() int { + return len(f) +} + +func (f Files) Less(i, j int) bool { + return strings.Compare(f[i].Name, f[j].Name) < 0 +} + +func (f Files) Swap(i, j int) { + f[i], f[j] = f[j], f[i] +} + +func norm(chart *chart.Chart) *chart.Chart { + dir, err := os.MkdirTemp("", "helmchart-") + Expect(err).To(Succeed()) + defer os.RemoveAll(dir) + + path, err := chartutil.Save(chart, dir) + Expect(err).To(Succeed()) + chart, err = loader.Load(path, osfs.New()) + Expect(err).To(Succeed()) + // sort.Sort(Files(chart.Raw)) + // sort.Sort(Files(chart.Files)) + // sort.Sort(Files(chart.Templates)) + return chart +} + +func get(blob blobaccess.DataAccess, expected []byte) []byte { + data, err := blob.Get() + ExpectWithOffset(1, err).To(Succeed()) + if expected != nil { + ExpectWithOffset(1, string(data)).To(Equal(string(expected))) + } + return data +} + +var _ = Describe("art parsing", func() { + It("succeeds", func() { + env := builder.NewBuilder(env.TestData()) + defer vfs.Cleanup(env) + + prov, err := env.ReadFile("/testdata/testchart.prov") + Expect(err).To(Succeed()) + chart, err := loader.Load("/testdata/testchart", env) + Expect(err).To(Succeed()) + meta, err := json.Marshal(chart.Metadata) + Expect(err).To(Succeed()) + + artblob, err := helm.SynthesizeArtifactBlob(loader.VFSLoader("/testdata/testchart", env)) + Expect(err).To(Succeed()) + defer Close(artblob) + set, err := artifactset.OpenFromBlob(accessobj.ACC_READONLY, artblob) + Expect(err).To(Succeed()) + defer Close(set) + art, err := set.GetArtifact(set.GetMain().String()) + Expect(err).To(Succeed()) + defer Close(art) + + ma := art.ManifestAccess() + m := ma.GetDescriptor() + Expect(len(m.Layers)).To(Equal(2)) + + config, err := art.ManifestAccess().GetConfigBlob() + Expect(err).To(Succeed()) + get(config, meta) + + _, data, err := set.GetBlobData(m.Layers[1].Digest) + Expect(err).To(Succeed()) + get(data, prov) + + _, data, err = set.GetBlobData(m.Layers[0].Digest) + Expect(err).To(Succeed()) + r, err := data.Reader() + Expect(err).To(Succeed()) + + blob, err := ma.GetBlob(m.Layers[1].Digest) + Expect(err).To(Succeed()) + get(blob, prov) + + // unfortunately charts are not directly comparable, because of the order in the arrays AND the modified Chart.yaml + found, err := loader.LoadArchive(r) + Expect(err).To(Succeed()) + Expect(norm(found)).To(Equal(norm(chart))) + }) +}) diff --git a/api/oci/ociutils/helm/artifact.go b/api/oci/ociutils/helm/artifact.go new file mode 100644 index 000000000..c60ad43e7 --- /dev/null +++ b/api/oci/ociutils/helm/artifact.go @@ -0,0 +1,109 @@ +package helm + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/vfs/pkg/osfs" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chartutil" + "helm.sh/helm/v3/pkg/registry" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/tech/helm/loader" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/file" +) + +func SynthesizeArtifactBlob(loader loader.Loader) (artifactset.ArtifactBlob, error) { + return artifactset.SythesizeArtifactSet(func(set *artifactset.ArtifactSet) (string, error) { + chart, blob, err := TransferAsArtifact(loader, set) + if err != nil { + return "", fmt.Errorf("unable to transfer as artifact: %w", err) + } + + if chart.Metadata.Version != "" { + err = set.AddTags(blob.Digest, chart.Metadata.Version) + if err != nil { + return "", fmt.Errorf("unable to add tag: %w", err) + } + } + + set.Annotate(artifactset.MAINARTIFACT_ANNOTATION, blob.Digest.String()) + + return artdesc.MediaTypeImageManifest, nil + }) +} + +func TransferAsArtifact(loader loader.Loader, ns oci.NamespaceAccess) (*chart.Chart, *artdesc.Descriptor, error) { + chart, err := loader.Chart() + if err != nil { + return nil, nil, err + } + err = chart.Validate() + if err != nil { + return nil, nil, errors.ErrInvalidWrap(err, "helm chart") + } + + provData, err := loader.Provenance() + if err != nil { + return nil, nil, err + } + + var blob blobaccess.BlobAccess + blob, err = loader.ChartArchive() + if err != nil { + return nil, nil, err + } + if blob == nil { + dir, err := os.MkdirTemp("", "helmchart-") + if err != nil { + return chart, nil, errors.Wrapf(err, "cannot create temporary directory for helm chart") + } + defer os.RemoveAll(dir) + path, err := chartutil.Save(chart, dir) + if err != nil { + return chart, nil, err + } + blob = file.BlobAccess(registry.ChartLayerMediaType, path, osfs.New()) + } else { + defer blob.Close() + } + meta := chart.Metadata + + configData, err := json.Marshal(meta) + if err != nil { + return chart, nil, err + } + + art, err := ns.NewArtifact() + if err != nil { + return chart, nil, err + } + defer art.Close() + m := art.ManifestAccess() + + err = m.SetConfigBlob(blobaccess.ForData(registry.ConfigMediaType, configData), nil) + if err != nil { + return chart, nil, err + } + _, err = m.AddLayer(blob, nil) + if err != nil { + return chart, nil, err + } + if provData != nil { + _, err = m.AddLayer(blobaccess.ForData(registry.ProvLayerMediaType, provData), nil) + if err != nil { + return chart, nil, err + } + } + blob, err = ns.AddArtifact(art) + if err != nil { + return chart, nil, err + } + return chart, artdesc.DefaultBlobDescriptor(blob), err +} diff --git a/pkg/contexts/oci/ociutils/helm/ignore/doc.go b/api/oci/ociutils/helm/ignore/doc.go similarity index 100% rename from pkg/contexts/oci/ociutils/helm/ignore/doc.go rename to api/oci/ociutils/helm/ignore/doc.go diff --git a/pkg/contexts/oci/ociutils/helm/ignore/rules.go b/api/oci/ociutils/helm/ignore/rules.go similarity index 100% rename from pkg/contexts/oci/ociutils/helm/ignore/rules.go rename to api/oci/ociutils/helm/ignore/rules.go diff --git a/pkg/contexts/oci/ociutils/helm/ignore/rules_test.go b/api/oci/ociutils/helm/ignore/rules_test.go similarity index 100% rename from pkg/contexts/oci/ociutils/helm/ignore/rules_test.go rename to api/oci/ociutils/helm/ignore/rules_test.go diff --git a/pkg/contexts/oci/ociutils/helm/ignore/testdata/.helmignore b/api/oci/ociutils/helm/ignore/testdata/.helmignore similarity index 100% rename from pkg/contexts/oci/ociutils/helm/ignore/testdata/.helmignore rename to api/oci/ociutils/helm/ignore/testdata/.helmignore diff --git a/pkg/contexts/oci/ociutils/helm/ignore/testdata/.joonix b/api/oci/ociutils/helm/ignore/testdata/.joonix similarity index 100% rename from pkg/contexts/oci/ociutils/helm/ignore/testdata/.joonix rename to api/oci/ociutils/helm/ignore/testdata/.joonix diff --git a/pkg/contexts/oci/ociutils/helm/ignore/testdata/a.txt b/api/oci/ociutils/helm/ignore/testdata/a.txt similarity index 100% rename from pkg/contexts/oci/ociutils/helm/ignore/testdata/a.txt rename to api/oci/ociutils/helm/ignore/testdata/a.txt diff --git a/pkg/contexts/oci/ociutils/helm/ignore/testdata/cargo/a.txt b/api/oci/ociutils/helm/ignore/testdata/cargo/a.txt similarity index 100% rename from pkg/contexts/oci/ociutils/helm/ignore/testdata/cargo/a.txt rename to api/oci/ociutils/helm/ignore/testdata/cargo/a.txt diff --git a/pkg/contexts/oci/ociutils/helm/ignore/testdata/cargo/b.txt b/api/oci/ociutils/helm/ignore/testdata/cargo/b.txt similarity index 100% rename from pkg/contexts/oci/ociutils/helm/ignore/testdata/cargo/b.txt rename to api/oci/ociutils/helm/ignore/testdata/cargo/b.txt diff --git a/pkg/contexts/oci/ociutils/helm/ignore/testdata/cargo/c.txt b/api/oci/ociutils/helm/ignore/testdata/cargo/c.txt similarity index 100% rename from pkg/contexts/oci/ociutils/helm/ignore/testdata/cargo/c.txt rename to api/oci/ociutils/helm/ignore/testdata/cargo/c.txt diff --git a/pkg/contexts/oci/ociutils/helm/ignore/testdata/helm.txt b/api/oci/ociutils/helm/ignore/testdata/helm.txt similarity index 100% rename from pkg/contexts/oci/ociutils/helm/ignore/testdata/helm.txt rename to api/oci/ociutils/helm/ignore/testdata/helm.txt diff --git a/pkg/contexts/oci/ociutils/helm/ignore/testdata/mast/a.txt b/api/oci/ociutils/helm/ignore/testdata/mast/a.txt similarity index 100% rename from pkg/contexts/oci/ociutils/helm/ignore/testdata/mast/a.txt rename to api/oci/ociutils/helm/ignore/testdata/mast/a.txt diff --git a/pkg/contexts/oci/ociutils/helm/ignore/testdata/mast/b.txt b/api/oci/ociutils/helm/ignore/testdata/mast/b.txt similarity index 100% rename from pkg/contexts/oci/ociutils/helm/ignore/testdata/mast/b.txt rename to api/oci/ociutils/helm/ignore/testdata/mast/b.txt diff --git a/pkg/contexts/oci/ociutils/helm/ignore/testdata/mast/c.txt b/api/oci/ociutils/helm/ignore/testdata/mast/c.txt similarity index 100% rename from pkg/contexts/oci/ociutils/helm/ignore/testdata/mast/c.txt rename to api/oci/ociutils/helm/ignore/testdata/mast/c.txt diff --git a/pkg/contexts/oci/ociutils/helm/ignore/testdata/rudder.txt b/api/oci/ociutils/helm/ignore/testdata/rudder.txt similarity index 100% rename from pkg/contexts/oci/ociutils/helm/ignore/testdata/rudder.txt rename to api/oci/ociutils/helm/ignore/testdata/rudder.txt diff --git a/pkg/contexts/oci/ociutils/helm/ignore/testdata/templates/.dotfile b/api/oci/ociutils/helm/ignore/testdata/templates/.dotfile similarity index 100% rename from pkg/contexts/oci/ociutils/helm/ignore/testdata/templates/.dotfile rename to api/oci/ociutils/helm/ignore/testdata/templates/.dotfile diff --git a/pkg/contexts/oci/ociutils/helm/ignore/testdata/tiller.txt b/api/oci/ociutils/helm/ignore/testdata/tiller.txt similarity index 100% rename from pkg/contexts/oci/ociutils/helm/ignore/testdata/tiller.txt rename to api/oci/ociutils/helm/ignore/testdata/tiller.txt diff --git a/pkg/contexts/oci/ociutils/helm/suite_test.go b/api/oci/ociutils/helm/suite_test.go similarity index 100% rename from pkg/contexts/oci/ociutils/helm/suite_test.go rename to api/oci/ociutils/helm/suite_test.go diff --git a/pkg/contexts/oci/ociutils/helm/sympath/walk.go b/api/oci/ociutils/helm/sympath/walk.go similarity index 100% rename from pkg/contexts/oci/ociutils/helm/sympath/walk.go rename to api/oci/ociutils/helm/sympath/walk.go diff --git a/pkg/contexts/oci/ociutils/helm/sympath/walk_test.go b/api/oci/ociutils/helm/sympath/walk_test.go similarity index 100% rename from pkg/contexts/oci/ociutils/helm/sympath/walk_test.go rename to api/oci/ociutils/helm/sympath/walk_test.go diff --git a/pkg/contexts/oci/ociutils/helm/testdata/testchart.prov b/api/oci/ociutils/helm/testdata/testchart.prov similarity index 100% rename from pkg/contexts/oci/ociutils/helm/testdata/testchart.prov rename to api/oci/ociutils/helm/testdata/testchart.prov diff --git a/pkg/contexts/oci/ociutils/helm/testdata/testchart/.helmignore b/api/oci/ociutils/helm/testdata/testchart/.helmignore similarity index 100% rename from pkg/contexts/oci/ociutils/helm/testdata/testchart/.helmignore rename to api/oci/ociutils/helm/testdata/testchart/.helmignore diff --git a/pkg/contexts/oci/ociutils/helm/testdata/testchart/Chart.yaml b/api/oci/ociutils/helm/testdata/testchart/Chart.yaml similarity index 100% rename from pkg/contexts/oci/ociutils/helm/testdata/testchart/Chart.yaml rename to api/oci/ociutils/helm/testdata/testchart/Chart.yaml diff --git a/pkg/contexts/oci/ociutils/helm/testdata/testchart/templates/NOTES.txt b/api/oci/ociutils/helm/testdata/testchart/templates/NOTES.txt similarity index 100% rename from pkg/contexts/oci/ociutils/helm/testdata/testchart/templates/NOTES.txt rename to api/oci/ociutils/helm/testdata/testchart/templates/NOTES.txt diff --git a/pkg/contexts/oci/ociutils/helm/testdata/testchart/templates/_helpers.tpl b/api/oci/ociutils/helm/testdata/testchart/templates/_helpers.tpl similarity index 100% rename from pkg/contexts/oci/ociutils/helm/testdata/testchart/templates/_helpers.tpl rename to api/oci/ociutils/helm/testdata/testchart/templates/_helpers.tpl diff --git a/pkg/contexts/oci/ociutils/helm/testdata/testchart/templates/deployment.yaml b/api/oci/ociutils/helm/testdata/testchart/templates/deployment.yaml similarity index 100% rename from pkg/contexts/oci/ociutils/helm/testdata/testchart/templates/deployment.yaml rename to api/oci/ociutils/helm/testdata/testchart/templates/deployment.yaml diff --git a/pkg/contexts/oci/ociutils/helm/testdata/testchart/templates/hpa.yaml b/api/oci/ociutils/helm/testdata/testchart/templates/hpa.yaml similarity index 100% rename from pkg/contexts/oci/ociutils/helm/testdata/testchart/templates/hpa.yaml rename to api/oci/ociutils/helm/testdata/testchart/templates/hpa.yaml diff --git a/pkg/contexts/oci/ociutils/helm/testdata/testchart/templates/ingress.yaml b/api/oci/ociutils/helm/testdata/testchart/templates/ingress.yaml similarity index 100% rename from pkg/contexts/oci/ociutils/helm/testdata/testchart/templates/ingress.yaml rename to api/oci/ociutils/helm/testdata/testchart/templates/ingress.yaml diff --git a/pkg/contexts/oci/ociutils/helm/testdata/testchart/templates/service.yaml b/api/oci/ociutils/helm/testdata/testchart/templates/service.yaml similarity index 100% rename from pkg/contexts/oci/ociutils/helm/testdata/testchart/templates/service.yaml rename to api/oci/ociutils/helm/testdata/testchart/templates/service.yaml diff --git a/pkg/contexts/oci/ociutils/helm/testdata/testchart/templates/serviceaccount.yaml b/api/oci/ociutils/helm/testdata/testchart/templates/serviceaccount.yaml similarity index 100% rename from pkg/contexts/oci/ociutils/helm/testdata/testchart/templates/serviceaccount.yaml rename to api/oci/ociutils/helm/testdata/testchart/templates/serviceaccount.yaml diff --git a/pkg/contexts/oci/ociutils/helm/testdata/testchart/templates/tests/test-connection.yaml b/api/oci/ociutils/helm/testdata/testchart/templates/tests/test-connection.yaml similarity index 100% rename from pkg/contexts/oci/ociutils/helm/testdata/testchart/templates/tests/test-connection.yaml rename to api/oci/ociutils/helm/testdata/testchart/templates/tests/test-connection.yaml diff --git a/pkg/contexts/oci/ociutils/helm/testdata/testchart/values.yaml b/api/oci/ociutils/helm/testdata/testchart/values.yaml similarity index 100% rename from pkg/contexts/oci/ociutils/helm/testdata/testchart/values.yaml rename to api/oci/ociutils/helm/testdata/testchart/values.yaml diff --git a/api/oci/ociutils/info.go b/api/oci/ociutils/info.go new file mode 100644 index 000000000..6d449e534 --- /dev/null +++ b/api/oci/ociutils/info.go @@ -0,0 +1,212 @@ +package ociutils + +import ( + "archive/tar" + "encoding/json" + "errors" + "fmt" + "io" + + "github.com/opencontainers/go-digest" + "sigs.k8s.io/yaml" + + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/compression" + "ocm.software/ocm/api/utils/mime" + common "ocm.software/ocm/api/utils/misc" +) + +type BlobInfo struct { + Error string `json:"error,omitempty"` + Unparsed string `json:"unparsed,omitempty"` + Content json.RawMessage `json:"content,omitempty"` + Type string `json:"type,omitempty"` + Digest digest.Digest `json:"digest,omitempty"` + Size int64 `json:"size,omitempty"` + Info interface{} `json:"info,omitempty"` +} +type ArtifactInfo struct { + Digest digest.Digest `json:"digest"` + Type string `json:"type"` + Descriptor interface{} `json:"descriptor"` + Config *BlobInfo `json:"config,omitempty"` + Layers []*BlobInfo `json:"layers,omitempty"` + Manifests []*BlobInfo `json:"manifests,omitempty"` +} + +func GetArtifactInfo(art cpi.ArtifactAccess, layerFiles bool) *ArtifactInfo { + if art.IsManifest() { + return GetManifestInfo(art.ManifestAccess(), layerFiles) + } + if art.IsIndex() { + return GetIndexInfo(art.IndexAccess(), layerFiles) + } + return &ArtifactInfo{Type: "unspecific"} +} + +func GetManifestInfo(m cpi.ManifestAccess, layerFiles bool) *ArtifactInfo { + info := &ArtifactInfo{ + Type: artdesc.MediaTypeImageManifest, + Descriptor: m.GetDescriptor(), + } + b, err := m.Blob() + if err == nil { + info.Digest = b.Digest() + } + man := m.GetDescriptor() + cfg := &BlobInfo{ + Content: nil, + Type: man.Config.MediaType, + Digest: man.Config.Digest, + Size: man.Config.Size, + } + info.Config = cfg + + config, err := blobaccess.BlobData(m.GetBlob(man.Config.Digest)) + if err != nil { + cfg.Error = "error getting config blob: " + err.Error() + } else { + cfg.Content = json.RawMessage(config) + } + h := getHandler(man.Config.MediaType) + + if h != nil { + pr, buf := common.NewBufferedPrinter() + h.Description(pr, m, config) + cfg.Info = buf.String() + } + for _, l := range man.Layers { + blobinfo := &BlobInfo{ + Type: l.MediaType, + Digest: l.Digest, + Size: l.Size, + } + blob, err := m.GetBlob(l.Digest) + if err != nil { + blobinfo.Error = "error getting blob: " + err.Error() + } else { + blobinfo.Info = GetLayerInfo(blob, layerFiles) + } + info.Layers = append(info.Layers, blobinfo) + } + return info +} + +type LayerInfo struct { + Description string `json:"description,omitempty"` + Error string `json:"error,omitempty"` + Unparsed string `json:"unparsed,omitempty"` + Content interface{} `json:"content,omitempty"` +} + +func GetLayerInfo(blob blobaccess.BlobAccess, layerFiles bool) *LayerInfo { + info := &LayerInfo{} + + if mime.IsJSON(blob.MimeType()) { + info.Description = "json document" + data, err := blob.Get() + if err != nil { + info.Error = "cannot read blob: " + err.Error() + return info + } + var j interface{} + err = json.Unmarshal(data, &j) + if err != nil { + if len(data) < 10000 { + info.Unparsed = string(data) + } + info.Error = "invalid json: " + err.Error() + return info + } + info.Content = j + return info + } + if mime.IsYAML(blob.MimeType()) { + info.Description = "yaml document" + data, err := blob.Get() + if err != nil { + info.Error = "cannot read blob: " + err.Error() + return info + } + var j interface{} + err = yaml.Unmarshal(data, &j) + if err != nil { + if len(data) < 10000 { + info.Unparsed = string(data) + } + info.Error = "invalid yaml: " + err.Error() + return info + } + info.Content = j + return info + } + if !layerFiles { + return nil + } + reader, err := blob.Reader() + if err != nil { + info.Error = "cannot read blob: " + err.Error() + return info + } + defer reader.Close() + reader, _, err = compression.AutoDecompress(reader) + if err != nil { + info.Error = "cannot decompress blob: " + err.Error() + return info + } + var files []string + tr := tar.NewReader(reader) + for { + header, err := tr.Next() + if err != nil { + if errors.Is(err, io.EOF) { + info.Content = files + return info + } + if len(files) == 0 { + info.Description = "no tar" + return info + } + info.Error = fmt.Sprintf("tar error: %s", err) + return info + } + if len(files) == 0 { + info.Description = "tar file" + } + + switch header.Typeflag { + case tar.TypeDir: + files = append(files, fmt.Sprintf("dir: %s\n", header.Name)) + case tar.TypeReg: + files = append(files, fmt.Sprintf("file: %s\n", header.Name)) + } + } +} + +func GetIndexInfo(i cpi.IndexAccess, layerFiles bool) *ArtifactInfo { + info := &ArtifactInfo{ + Type: artdesc.MediaTypeImageIndex, + Descriptor: i.GetDescriptor(), + } + b, err := i.Blob() + if err == nil { + info.Digest = b.Digest() + } + for _, l := range i.GetDescriptor().Manifests { + blobinfo := &BlobInfo{ + Type: l.MediaType, + Digest: l.Digest, + Size: l.Size, + } + a, err := i.GetArtifact(l.Digest) + if err != nil { + blobinfo.Error = fmt.Sprintf("cannot get artifact: %s\n", err) + } else { + blobinfo.Info = GetArtifactInfo(a, layerFiles) + } + info.Layers = append(info.Layers, blobinfo) + } + return info +} diff --git a/pkg/contexts/oci/ociutils/print.go b/api/oci/ociutils/print.go similarity index 91% rename from pkg/contexts/oci/ociutils/print.go rename to api/oci/ociutils/print.go index fe5678a4f..c10a6a8a8 100644 --- a/pkg/contexts/oci/ociutils/print.go +++ b/api/oci/ociutils/print.go @@ -8,13 +8,13 @@ import ( "io" "strings" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/common/compression" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/compression" + common "ocm.software/ocm/api/utils/misc" ) func PrintArtifact(pr common.Printer, art cpi.ArtifactAccess, listFiles bool) { diff --git a/api/oci/ref.go b/api/oci/ref.go new file mode 100644 index 000000000..c3fa4e128 --- /dev/null +++ b/api/oci/ref.go @@ -0,0 +1,306 @@ +package oci + +import ( + "fmt" + "strings" + + "github.com/mandelsoft/goutils/errors" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/oci/grammar" +) + +// to find a suitable secret for images on Docker Hub, we need its two domains to do matching. +const ( + dockerHubDomain = "docker.io" + dockerHubLegacyDomain = "index.docker.io" + + KIND_OCI_REFERENCE = "oci reference" + KIND_ARETEFACT_REFERENCE = "artifact reference" +) + +// ParseRepo parses a standard oci repository reference into an internal representation. +func ParseRepo(ref string) (UniformRepositorySpec, error) { + create := false + if strings.HasPrefix(ref, "+") { + create = true + ref = ref[1:] + } + uspec := UniformRepositorySpec{} + match := grammar.AnchoredRegistryRegexp.FindSubmatch([]byte(ref)) + if match == nil { + match = grammar.AnchoredGenericRegistryRegexp.FindSubmatch([]byte(ref)) + if match == nil { + return uspec, errors.ErrInvalid(KIND_OCI_REFERENCE, ref) + } + uspec.SetType(string(match[1])) + uspec.Info = string(match[2]) + uspec.CreateIfMissing = create + return uspec, nil + } + uspec.SetType(string(match[1])) + uspec.Scheme = string(match[2]) + uspec.Host = string(match[3]) + uspec.CreateIfMissing = create + return uspec, nil +} + +// RefSpec is a go internal representation of an oci reference. +type RefSpec struct { + UniformRepositorySpec `json:",inline"` + ArtSpec `json:",inline"` +} + +func pointer(b []byte) *string { + if len(b) == 0 { + return nil + } + s := string(b) + return &s +} + +func dig(b []byte) *digest.Digest { + if len(b) == 0 { + return nil + } + s := digest.Digest(b) + return &s +} + +// ParseRef parses a oci reference into a internal representation. +func ParseRef(ref string) (RefSpec, error) { + create := false + if strings.HasPrefix(ref, "+") { + create = true + ref = ref[1:] + } + + spec := RefSpec{UniformRepositorySpec: UniformRepositorySpec{CreateIfMissing: create}} + match := grammar.AnchoredTypedSchemedHostPortArtifactRegexp.FindSubmatch([]byte(ref)) + if match != nil { + spec.SetType(string(match[1])) + spec.Scheme = string(match[2]) + spec.Host = string(match[3]) + spec.Repository = string(match[4]) + spec.Tag = pointer(match[5]) + spec.Digest = dig(match[6]) + return spec, nil + } + + match = grammar.AnchoredTypedOptSchemedReqHostReqPortArtifactRegexp.FindSubmatch([]byte(ref)) + if match != nil { + spec.SetType(string(match[1])) + spec.Scheme = string(match[2]) + spec.Host = string(match[3]) + spec.Repository = string(match[4]) + spec.Tag = pointer(match[5]) + spec.Digest = dig(match[6]) + return spec, nil + } + match = grammar.FileReferenceRegexp.FindSubmatch([]byte(ref)) + if match != nil { + spec.SetType(string(match[1])) + spec.Info = string(match[2]) + spec.Repository = string(match[3]) + spec.Tag = pointer(match[4]) + spec.Digest = dig(match[5]) + return spec, nil + } + match = grammar.DockerLibraryReferenceRegexp.FindSubmatch([]byte(ref)) + if match != nil { + spec.Host = dockerHubDomain + spec.Repository = "library" + grammar.RepositorySeparator + string(match[1]) + spec.Tag = pointer(match[2]) + spec.Digest = dig(match[3]) + return spec, nil + } + match = grammar.DockerReferenceRegexp.FindSubmatch([]byte(ref)) + if match != nil { + spec.Host = dockerHubDomain + spec.Repository = string(match[1]) + spec.Tag = pointer(match[2]) + spec.Digest = dig(match[3]) + return spec, nil + } + match = grammar.ReferenceRegexp.FindSubmatch([]byte(ref)) + if match != nil { + spec.Scheme = string(match[1]) + spec.Host = string(match[2]) + spec.Repository = string(match[3]) + spec.Tag = pointer(match[4]) + spec.Digest = dig(match[5]) + return spec, nil + } + match = grammar.TypedReferenceRegexp.FindSubmatch([]byte(ref)) + if match != nil { + spec.SetType(string(match[1])) + spec.Scheme = string(match[2]) + spec.Host = string(match[3]) + spec.Repository = string(match[4]) + spec.Tag = pointer(match[5]) + spec.Digest = dig(match[6]) + return spec, nil + } + match = grammar.TypedURIRegexp.FindSubmatch([]byte(ref)) + if match != nil { + spec.SetType(string(match[1])) + spec.Scheme = string(match[2]) + spec.Host = string(match[3]) + spec.Repository = string(match[4]) + spec.Tag = pointer(match[5]) + spec.Digest = dig(match[6]) + return spec, nil + } + match = grammar.TypedGenericReferenceRegexp.FindSubmatch([]byte(ref)) + if match != nil { + spec.SetType(string(match[1])) + spec.Info = string(match[2]) + spec.Repository = string(match[3]) + spec.Tag = pointer(match[4]) + spec.Digest = dig(match[5]) + return spec, nil + } + match = grammar.AnchoredRegistryRegexp.FindSubmatch([]byte(ref)) + if match != nil { + spec.SetType(string(match[1])) + spec.Info = string(match[2]) + spec.Repository = string(match[3]) + spec.Tag = pointer(match[4]) + spec.Digest = dig(match[5]) + return spec, nil + } + + match = grammar.AnchoredGenericRegistryRegexp.FindSubmatch([]byte(ref)) + if match != nil { + spec.SetType(string(match[1])) + spec.Info = string(match[2]) + + match = grammar.ErrorCheckRegexp.FindSubmatch([]byte(ref)) + if match != nil { + if len(match[3]) != 0 || len(match[4]) != 0 { + return RefSpec{}, errors.ErrInvalid(KIND_OCI_REFERENCE, ref) + } + } + return spec, nil + } + return RefSpec{}, errors.ErrInvalid(KIND_OCI_REFERENCE, ref) +} + +func (r *RefSpec) Name() string { + return r.UniformRepositorySpec.ComposeRef(r.Repository) +} + +func (r *RefSpec) String() string { + art := r.Repository + if r.Tag != nil { + art = fmt.Sprintf("%s:%s", art, *r.Tag) + } + if r.Digest != nil { + art = fmt.Sprintf("%s@%s", art, r.Digest.String()) + } + return r.UniformRepositorySpec.ComposeRef(art) +} + +// CredHost fallback to legacy docker domain if applicable +// this is how containerd translates the old domain for DockerHub to the new one, taken from containerd/reference/docker/reference.go:674. +func (r *RefSpec) CredHost() string { + if r.Host == dockerHubDomain { + return dockerHubLegacyDomain + } + return r.Host +} + +func (r RefSpec) DeepCopy() RefSpec { + if r.Tag != nil { + tag := *r.Tag + r.Tag = &tag + } + if r.Digest != nil { + dig := *r.Digest + r.Digest = &dig + } + return r +} + +//////////////////////////////////////////////////////////////////////////////// + +func ParseArt(art string) (ArtSpec, error) { + match := grammar.AnchoredArtifactVersionRegexp.FindSubmatch([]byte(art)) + + if match == nil { + return ArtSpec{}, errors.ErrInvalid(KIND_ARETEFACT_REFERENCE, art) + } + var tag *string + var dig *digest.Digest + + if match[2] != nil { + t := string(match[2]) + tag = &t + } + if match[3] != nil { + t := string(match[3]) + d, err := digest.Parse(t) + if err != nil { + return ArtSpec{}, errors.ErrInvalidWrap(err, KIND_ARETEFACT_REFERENCE, art) + } + dig = &d + } + return ArtSpec{ + Repository: string(match[1]), + Tag: tag, + Digest: dig, + }, nil +} + +// ArtSpec is a go internal representation of a oci reference. +type ArtSpec struct { + // Repository is the part of a reference without its hostname + Repository string `json:"repository"` + // +optional + Tag *string `json:"tag,omitempty"` + // +optional + Digest *digest.Digest `json:"digest,omitempty"` +} + +func (r *ArtSpec) Version() string { + if r.Tag != nil { + return *r.Tag + } + if r.Digest != nil { + return "@" + string(*r.Digest) + } + return "latest" +} + +func (r *ArtSpec) IsRegistry() bool { + return r.Repository == "" +} + +func (r *ArtSpec) IsVersion() bool { + return r.Tag != nil || r.Digest != nil +} + +func (r *ArtSpec) IsTagged() bool { + return r.Tag != nil +} + +func (r *ArtSpec) Reference() string { + if r.Tag != nil { + return *r.Tag + } + if r.Digest != nil { + return "@" + string(*r.Digest) + } + return "latest" +} + +func (r *ArtSpec) String() string { + s := r.Repository + if r.Tag != nil { + s += fmt.Sprintf(":%s", *r.Tag) + } + if r.Digest != nil { + s += fmt.Sprintf("@%s", r.Digest.String()) + } + return s +} diff --git a/api/oci/ref_test.go b/api/oci/ref_test.go new file mode 100644 index 000000000..a62abd3c1 --- /dev/null +++ b/api/oci/ref_test.go @@ -0,0 +1,829 @@ +package oci_test + +import ( + "strings" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + godigest "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/extensions/repositories/ctf" + "ocm.software/ocm/api/oci/extensions/repositories/ocireg" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/runtime" +) + +func Type(t string) string { + if t == "" { + return t + } + return t + "::" +} + +func FileFormat(t, f string) string { + if t == "" { + return f + } + if f == "" { + return t + } + return t + "+" + f +} + +func FileType(t, f string) string { + if t != "" { + return t + } else { + return f + } +} + +func Scheme(s string) string { + if s == "" { + return s + } + return s + "://" +} + +func Sub(t string) string { + if t == "" { + return t + } + return "/" + t +} + +func Vers(t, d string) string { + if t == "" && d == "" { + return "" + } + if t == "" { + return "@" + d + } + if d == "" { + return ":" + t + } + return ":" + t + "@" + d +} + +func Dig(b []byte) *godigest.Digest { + if len(b) == 0 { + return nil + } + s := godigest.Digest(b) + return &s +} + +func Pointer(b []byte) *string { + if len(b) == 0 { + return nil + } + s := string(b) + return &s +} + +func CheckRef(ref string, exp *oci.RefSpec) { + spec, err := oci.ParseRef(ref) + if exp == nil { + ExpectWithOffset(1, err).To(HaveOccurred()) + } else { + ExpectWithOffset(1, err).To(Succeed()) + ExpectWithOffset(1, spec).To(Equal(*exp)) + } +} + +func CheckRepo(ref string, exp *oci.UniformRepositorySpec) { + spec, err := oci.ParseRepo(ref) + if exp == nil { + ExpectWithOffset(1, err).To(HaveOccurred()) + } else { + ExpectWithOffset(1, err).To(Succeed()) + ExpectWithOffset(1, spec).To(Equal(*exp)) + } +} + +var _ = Describe("ref parsing", func() { + digest := godigest.Digest("sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a") + tag := "v1" + + ghcr := oci.UniformRepositorySpec{Host: "ghcr.io"} + docker := oci.UniformRepositorySpec{Host: "docker.io"} + + Context("parse file path refs", func() { + t := "ctf" + p := "file/path" + r := "github.com/mandelsoft/ocm" + v := "v1" + d := "sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a" + + Context("[+][::][./][//[:]", func() { + for _, cm := range []string{"", "+"} { + for _, ut := range []string{"", t} { + for _, uf := range []string{"", "directory", "tar", "tgz"} { + for _, up := range []string{p, "./" + p} { + for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + ".1.2-rc.1"} { + for _, ud := range []string{"", d} { + ref := cm + Type(FileFormat(ut, uf)) + up + "//" + r + Vers(uv, ud) + ut, uf, uv, up, ud := ut, uf, uv, up, ud + + // tests parsing of all permutations of + // [+][::][./][//[:][@] + It("parses ref "+ref, func() { + CheckRef(ref, &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: FileType(ut, uf), + Scheme: "", + Host: "", + Info: up, + CreateIfMissing: ref[0] == '+', + TypeHint: FileFormat(ut, uf), + }, + ArtSpec: oci.ArtSpec{ + Repository: r, + Tag: Pointer([]byte(uv)), + Digest: Dig([]byte(ud)), + }, + }) + }) + } + } + } + } + } + } + }) + }) + + Context("parse domain refs", func() { + t := "oci" + h := "ghcr.io" + r := "github.com/mandelsoft/ocm" + v := "v1" + d := "sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a" + + // Notice that the file formats (directory, tar, tgz) CAN BE PARSED in this notation, BUT for non file based + // implementations like oci, this information is not used. + Context("[+][::][://][:][/]/[:][@]", func() { + for _, cm := range []string{"", "+"} { + for _, ut := range []string{"", t} { + for _, uf := range []string{"", "directory", "tar", "tgz"} { + for _, ush := range []string{"", "http", "https"} { + for _, uh := range []string{h, h + ":3030"} { + for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + ".1.2-rc.1"} { + for _, ud := range []string{"", d} { + for _, sep := range []string{"/", "//"} { + ref := cm + Type(FileFormat(ut, uf)) + Scheme(ush) + uh + sep + r + Vers(uv, ud) + ut, uf, ush, uh, uv, ud := ut, uf, ush, uh, uv, ud + + // tests parsing of all permutations of + // [::][://][:][/]/[:][@] + It("parses ref "+ref, func() { + CheckRef(ref, &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: FileType(ut, uf), + Scheme: ush, + Host: uh, + Info: "", + CreateIfMissing: ref[0] == '+', + TypeHint: FileFormat(ut, uf), + }, + ArtSpec: oci.ArtSpec{ + Repository: r, + Tag: Pointer([]byte(uv)), + Digest: Dig([]byte(ud)), + }, + }) + }) + } + } + } + } + } + } + } + } + }) + + It("repository creation from parsed repo", func() { + ctx := oci.New() + aliasreg := ocireg.NewRepositorySpec("http://ghcr.io") + ctx.SetAlias("myalias", aliasreg) + repo := Must(oci.ParseRef("myalias//repository:1.0.0")) + spec := Must(ctx.MapUniformRepositorySpec(&repo.UniformRepositorySpec)) + Expect(spec).To(Equal(aliasreg)) + }) + }) + + Context("parse host port refs", func() { + t := "oci" + h := "localhost" + r := "github.com/mandelsoft/ocm" + v := "v1" + d := "sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a" + + // localhost (with and without port) (and other host names) are a special case since these are not formally + // valid domains + // the combination of this test and the test below test parsing of all permutations of + // [::][://]:/[:][@] + Context("[+][::][://]:/[:][@]", func() { + for _, cm := range []string{"", "+"} { + for _, ut := range []string{"", t} { + for _, uf := range []string{"", "directory", "tar", "tgz"} { + for _, ush := range []string{"", "http", "https"} { + for _, uh := range []string{h + ":3030"} { + for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + ".1.2-rc.1"} { + for _, ud := range []string{"", d} { + ref := cm + Type(FileFormat(ut, uf)) + Scheme(ush) + uh + "/" + r + Vers(uv, ud) + ut, uf, ush, uh, uv, ud := ut, uf, ush, uh, uv, ud + + // tests parsing of all permutations of + // [::][://]:/[:][@] + It("parses ref "+ref, func() { + CheckRef(ref, &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: FileType(ut, uf), + Scheme: ush, + Host: uh, + Info: "", + CreateIfMissing: ref[0] == '+', + TypeHint: FileFormat(ut, uf), + }, + ArtSpec: oci.ArtSpec{ + Repository: r, + Tag: Pointer([]byte(uv)), + Digest: Dig([]byte(ud)), + }, + }) + }) + } + } + } + } + } + } + } + }) + Context("[+][::][://][:]//[:][@]", func() { + for _, cm := range []string{"", "+"} { + for _, ut := range []string{"", t} { + for _, uf := range []string{"", "directory", "tar", "tgz"} { + for _, ush := range []string{"", "http", "https"} { + for _, uh := range []string{h, h + ":3030"} { + for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + ".1.2-rc.1"} { + for _, ud := range []string{"", d} { + ref := cm + Type(FileFormat(ut, uf)) + Scheme(ush) + uh + "//" + r + Vers(uv, ud) + ut, uf, ush, uh, uv, ud := ut, uf, ush, uh, uv, ud + + // tests parsing of all permutations of + // [::][://][:]//[:][@] + It("parses ref "+ref, func() { + CheckRef(ref, &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: FileType(ut, uf), + Scheme: ush, + Host: uh, + Info: "", + CreateIfMissing: ref[0] == '+', + TypeHint: FileFormat(ut, uf), + }, + ArtSpec: oci.ArtSpec{ + Repository: r, + Tag: Pointer([]byte(uv)), + Digest: Dig([]byte(ud)), + }, + }) + }) + } + } + } + } + } + } + } + }) + }) + + Context("parse json repo spec refs", func() { + t := "oci" + h := "ghcr.io" + r := "github.com/mandelsoft/ocm" + v := "v1" + d := "sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a" + + repospec := ocireg.NewRepositorySpec(h) + jsonrepospec := string(Must(runtime.DefaultJSONEncoding.Marshal(repospec))) + + // Notice that the file formats (directory, tar, tgz) CAN BE PARSED in this notation, BUT for non file based + // implementations like oci, this information is not used. + Context("[+][::][//][:][@]", func() { + for _, cm := range []string{"", "+"} { + for _, ut := range []string{"", t} { + for _, uf := range []string{"", "directory", "tar", "tgz"} { + for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + ".1.2-rc.1"} { + for _, ud := range []string{"", d} { + ref := cm + Type(FileFormat(ut, uf)) + jsonrepospec + "//" + r + Vers(uv, ud) + ut, uf, uv, ud := ut, uf, uv, ud + + // tests parsing of all permutations of + // [::][//][:][@] + It("parses ref "+ref, func() { + CheckRef(ref, &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: FileType(ut, uf), + Scheme: "", + Host: "", + Info: jsonrepospec, + CreateIfMissing: ref[0] == '+', + TypeHint: FileFormat(ut, uf), + }, + ArtSpec: oci.ArtSpec{ + Repository: r, + Tag: Pointer([]byte(uv)), + Digest: Dig([]byte(ud)), + }, + }) + }) + } + } + } + } + } + }) + }) + + Context("parse docker library refs", func() { + // h := "docker.io" + r := "ubuntu" + v := "v1" + d := "sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a" + + Context("[:][@]", func() { + for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + ".1.2-rc.1"} { + for _, ud := range []string{"", d} { + ref := r + Vers(uv, ud) + uv, ud := uv, ud + + // tests parsing of all permutations of + // [:][@] + It("parses ref "+ref, func() { + CheckRef(ref, &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: "", + Scheme: "", + Host: "docker.io", + Info: "", + CreateIfMissing: false, + TypeHint: "", + }, + ArtSpec: oci.ArtSpec{ + Repository: "library/" + r, + Tag: Pointer([]byte(uv)), + Digest: Dig([]byte(ud)), + }, + }) + }) + } + } + }) + }) + + Context("parse docker repository refs", func() { + // h := "docker.io" + r := "docker-repo/ubuntu" + v := "v1" + d := "sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a" + + Context("/[:][@]", func() { + for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + ".1.2-rc.1"} { + for _, ud := range []string{"", d} { + ref := r + Vers(uv, ud) + uv, ud := uv, ud + + // tests parsing of all permutations of + // [:][@] + It("parses ref "+ref, func() { + CheckRef(ref, &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: "", + Scheme: "", + Host: "docker.io", + Info: "", + CreateIfMissing: false, + TypeHint: "", + }, + ArtSpec: oci.ArtSpec{ + Repository: r, + Tag: Pointer([]byte(uv)), + Digest: Dig([]byte(ud)), + }, + }) + }) + } + } + }) + }) + + Context("parse file path repos", func() { + t := "ctf" + p := "file/path" + + Context("[+][::][./][", func() { + for _, cm := range []string{"", "+"} { + for _, ut := range []string{"", t} { + for _, uf := range []string{"", "directory", "tar", "tgz"} { + for _, up := range []string{p, "./" + p} { + ref := cm + Type(FileFormat(ut, uf)) + up + ut, uf, up := ut, uf, up + // tests parsing of all permutations of + // [+][::][./][//[:][@] + It("parses ref "+ref, func() { + CheckRepo(ref, &oci.UniformRepositorySpec{ + Type: FileType(ut, uf), + Scheme: "", + Host: "", + Info: up, + CreateIfMissing: ref[0] == '+', + TypeHint: FileFormat(ut, uf), + }) + }) + } + } + } + } + }) + }) + + Context("parse domain repos", func() { + t := "oci" + h := "ghcr.io" + + Context("[+][::][://][:]", func() { + for _, cm := range []string{"", "+"} { + for _, ut := range []string{"", t} { + for _, uf := range []string{"", "directory", "tar", "tgz"} { + for _, ush := range []string{"", "http", "https"} { + for _, uh := range []string{h, h + ":3030", "localhost", "localhost:3030"} { + ref := cm + Type(FileFormat(ut, uf)) + Scheme(ush) + uh + ut, uf, ush, uh := ut, uf, ush, uh + + // tests parsing of all permutations of + // [+][::][://][:] + It("parses ref "+ref, func() { + // if you are coming from the ocm test and + // wondering why the corresponding tests if + // has an additional condition that the + // type has to be empty - this is because + // the corresponding parse method calls + // an intermediate handler based on the + // type that resolves the localhost in the + // info. + // For oci repositories, such this + // handling is done in the + // MapUniformRepositorySpec logic. + if strings.HasPrefix(uh, "localhost") { + CheckRepo(ref, &oci.UniformRepositorySpec{ + Type: FileType(ut, uf), + Scheme: "", + Host: "", + Info: Scheme(ush) + uh, + CreateIfMissing: ref[0] == '+', + TypeHint: FileFormat(ut, uf), + }) + } else { + CheckRepo(ref, &oci.UniformRepositorySpec{ + Type: FileType(ut, uf), + Scheme: ush, + Host: uh, + Info: "", + CreateIfMissing: ref[0] == '+', + TypeHint: FileFormat(ut, uf), + }) + } + }) + } + } + } + } + } + }) + It("repository creation from parsed repo with localhost", func() { + ctx := oci.New() + repo := Must(oci.ParseRepo("http://localhost")) + spec := Must(ctx.MapUniformRepositorySpec(&repo)) + Expect(spec).To(Equal(ocireg.NewRepositorySpec("http://localhost"))) + }) + It("repository creation from parsed repo with localhost", func() { + ctx := oci.New() + + aliasreg := ocireg.NewRepositorySpec("http://ghcr.io") + ctx.SetAlias("myalias", aliasreg) + repo := Must(oci.ParseRepo("myalias")) + spec := Must(ctx.MapUniformRepositorySpec(&repo)) + Expect(spec).To(Equal(aliasreg)) + }) + }) + + Context("parse json repo spec refs", func() { + t := "oci" + h := "ghcr.io" + + repospec := ocireg.NewRepositorySpec(h) + jsonrepospec := string(Must(runtime.DefaultJSONEncoding.Marshal(repospec))) + + // Notice that the file formats (directory, tar, tgz) CAN BE PARSED in this notation, BUT for non file based + // implementations like oci, this information is not used. + Context("[+][::]", func() { + for _, cm := range []string{"", "+"} { + for _, ut := range []string{"", t} { + for _, uf := range []string{"", "directory", "tar", "tgz"} { + ref := cm + Type(FileFormat(ut, uf)) + jsonrepospec + ut, uf := ut, uf + + // tests parsing of all permutations of + // [::] + It("parses ref "+ref, func() { + CheckRepo(ref, &oci.UniformRepositorySpec{ + Type: FileType(ut, uf), + Scheme: "", + Host: "", + Info: jsonrepospec, + CreateIfMissing: ref[0] == '+', + TypeHint: FileFormat(ut, uf), + }) + }) + } + } + } + }) + }) + + It("succeeds for repository", func() { + CheckRef("::ghcr.io/", &oci.RefSpec{UniformRepositorySpec: ghcr}) + }) + It("succeeds", func() { + CheckRef("ubuntu", &oci.RefSpec{UniformRepositorySpec: docker, ArtSpec: oci.ArtSpec{Repository: "library/ubuntu"}}) + CheckRef("ubuntu:v1", &oci.RefSpec{UniformRepositorySpec: docker, ArtSpec: oci.ArtSpec{Repository: "library/ubuntu", Tag: &tag}}) + CheckRef("test/ubuntu", &oci.RefSpec{UniformRepositorySpec: docker, ArtSpec: oci.ArtSpec{Repository: "test/ubuntu"}}) + CheckRef("test_test/ubuntu", &oci.RefSpec{UniformRepositorySpec: docker, ArtSpec: oci.ArtSpec{Repository: "test_test/ubuntu"}}) + CheckRef("test__test/ubuntu", &oci.RefSpec{UniformRepositorySpec: docker, ArtSpec: oci.ArtSpec{Repository: "test__test/ubuntu"}}) + CheckRef("test-test/ubuntu", &oci.RefSpec{UniformRepositorySpec: docker, ArtSpec: oci.ArtSpec{Repository: "test-test/ubuntu"}}) + CheckRef("test--test/ubuntu", &oci.RefSpec{UniformRepositorySpec: docker, ArtSpec: oci.ArtSpec{Repository: "test--test/ubuntu"}}) + CheckRef("test-----test/ubuntu", &oci.RefSpec{UniformRepositorySpec: docker, ArtSpec: oci.ArtSpec{Repository: "test-----test/ubuntu"}}) + CheckRef("test/ubuntu:v1", &oci.RefSpec{UniformRepositorySpec: docker, ArtSpec: oci.ArtSpec{Repository: "test/ubuntu", Tag: &tag}}) + CheckRef("ghcr.io/test/ubuntu", &oci.RefSpec{UniformRepositorySpec: ghcr, ArtSpec: oci.ArtSpec{Repository: "test/ubuntu"}}) + CheckRef("ghcr.io/test", &oci.RefSpec{UniformRepositorySpec: ghcr, ArtSpec: oci.ArtSpec{Repository: "test"}}) + CheckRef("ghcr.io:8080/test/ubuntu", &oci.RefSpec{UniformRepositorySpec: oci.UniformRepositorySpec{Host: "ghcr.io:8080"}, ArtSpec: oci.ArtSpec{Repository: "test/ubuntu"}}) + CheckRef("ghcr.io/test/ubuntu:v1", &oci.RefSpec{UniformRepositorySpec: ghcr, ArtSpec: oci.ArtSpec{Repository: "test/ubuntu", Tag: &tag}}) + CheckRef("ghcr.io/test/ubuntu@sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a", &oci.RefSpec{UniformRepositorySpec: ghcr, ArtSpec: oci.ArtSpec{Repository: "test/ubuntu", Digest: &digest}}) + CheckRef("ghcr.io/test/ubuntu:v1@sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a", &oci.RefSpec{UniformRepositorySpec: ghcr, ArtSpec: oci.ArtSpec{Repository: "test/ubuntu", Tag: &tag, Digest: &digest}}) + CheckRef("test___test/ubuntu", &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Info: "test___test/ubuntu", + }, + }) + CheckRef("type::https://ghcr.io/repo/repo:v1@"+digest.String(), &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: "type", + Scheme: "https", + Host: "ghcr.io", + Info: "", + TypeHint: "type", + }, + ArtSpec: oci.ArtSpec{ + Repository: "repo/repo", + Tag: &tag, + Digest: &digest, + }, + }) + CheckRef("http://127.0.0.1:443/repo/repo:v1@"+digest.String(), &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: "", + Scheme: "http", + Host: "127.0.0.1:443", + Info: "", + }, + ArtSpec: oci.ArtSpec{ + Repository: "repo/repo", + Tag: &tag, + Digest: &digest, + }, + }) + CheckRef("directory::a/b", &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: "directory", + Scheme: "", + Host: "", + Info: "a/b", + TypeHint: "directory", + }, + ArtSpec: oci.ArtSpec{ + Repository: "", + }, + }) + CheckRef("ctf+directory::a/b", &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: "ctf", + Scheme: "", + Host: "", + Info: "a/b", + TypeHint: "ctf+directory", + }, + ArtSpec: oci.ArtSpec{ + Repository: "", + }, + }) + CheckRef("+ctf+directory::a/b", &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: "ctf", + Scheme: "", + Host: "", + Info: "a/b", + CreateIfMissing: true, + TypeHint: "ctf+directory", + }, + ArtSpec: oci.ArtSpec{ + Repository: "", + }, + }) + + CheckRef("a/b//", &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: "", + Scheme: "", + Host: "", + Info: "a/b", + }, + ArtSpec: oci.ArtSpec{ + Repository: "", + }, + }) + + CheckRef("directory::a/b//c/d", &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: "directory", + Scheme: "", + Host: "", + Info: "a/b", + TypeHint: "directory", + }, + ArtSpec: oci.ArtSpec{ + Repository: "c/d", + }, + }) + + CheckRef("oci::ghcr.io", &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: "oci", + Scheme: "", + Host: "ghcr.io", + Info: "", + TypeHint: "oci", + }, + ArtSpec: oci.ArtSpec{ + Repository: "", + }, + }) + CheckRef("/tmp/ctf//mandelsoft/test:v1", &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: "", + Scheme: "", + Host: "", + Info: "/tmp/ctf", + }, + ArtSpec: oci.ArtSpec{ + Repository: "mandelsoft/test", + Tag: &tag, + }, + }) + CheckRef("/tmp/ctf", &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: "", + Scheme: "", + Host: "", + Info: "/tmp/ctf", + }, + }) + }) + + It("json spec", func() { + ctx := oci.New() + + tag := "1.0.0" + CheckRef("OCIRegistry::{\"baseUrl\": \"test.com\"}//repo:1.0.0", &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: "OCIRegistry", + Scheme: "", + Host: "", + Info: "{\"baseUrl\": \"test.com\"}", + TypeHint: "OCIRegistry", + }, + ArtSpec: oci.ArtSpec{ + Repository: "repo", + Tag: &tag, + }, + }) + ref := Must(oci.ParseRef("OCIRegistry::{\"type\":\"OCIRegistry\", \"baseUrl\": \"test.com\"}//repo:1.0.0")) + spec := Must(ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec)) + repo := Must(spec.Repository(ctx, nil)) + _ = repo + }) + + It("fail for json spec with type mismatch", func() { + ctx := oci.New() + + tag := "1.0.0" + CheckRef("oci::{\"type\":\"OCIRegistry\", \"baseUrl\": \"test.com\"}//repo:1.0.0", &oci.RefSpec{ + UniformRepositorySpec: oci.UniformRepositorySpec{ + Type: "oci", + Scheme: "", + Host: "", + Info: "{\"type\":\"OCIRegistry\", \"baseUrl\": \"test.com\"}", + TypeHint: "oci", + }, + ArtSpec: oci.ArtSpec{ + Repository: "repo", + Tag: &tag, + }, + }) + ref := Must(oci.ParseRef("oci::{\"type\":\"OCIRegistry\", \"baseUrl\": \"test.com\"}//repo:1.0.0")) + spec, err := ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec) + Expect(spec).To(BeNil()) + Expect(err).ToNot(BeNil()) + }) + + It("fails", func() { + CheckRef("https://ubuntu", nil) + CheckRef("ubuntu@4711", nil) + CheckRef("test/ubuntu@4711", nil) + CheckRef("test/ubuntu:v1@4711", nil) + CheckRef("ghcr.io/test/ubuntu:v1@4711", nil) + }) + It("repo", func() { + CheckRepo("ghcr.io", &oci.UniformRepositorySpec{ + Host: "ghcr.io", + }) + CheckRepo("https://ghcr.io", &oci.UniformRepositorySpec{ + Scheme: "https", + Host: "ghcr.io", + }) + CheckRepo("alias", &oci.UniformRepositorySpec{ + Info: "alias", + }) + CheckRepo("tar::a/b.tar", &oci.UniformRepositorySpec{ + Type: "tar", + Info: "a/b.tar", + TypeHint: "tar", + }) + CheckRepo("a/b.tar", &oci.UniformRepositorySpec{ + Info: "a/b.tar", + }) + }) + It("localhost", func() { + ctx := oci.New() + // port is necessary here, otherwise it is ambiguous with dockerhub reference (localhost/test:1.0.0 could be + // an artifact stored on duckerhub) + ref := Must(oci.ParseRef("localhost:80/test:1.0.0")) + spec := Must(ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec)) + Expect(spec).To(Equal(ocireg.NewRepositorySpec("localhost:80"))) + }) + It("localhost with unambiguous separator and without port", func() { + ctx := oci.New() + ref := Must(oci.ParseRef("localhost//test:1.0.0")) + spec := Must(ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec)) + Expect(spec).To(Equal(ocireg.NewRepositorySpec("localhost"))) + }) + It("localhost with unambiguous separator", func() { + ctx := oci.New() + ref := Must(oci.ParseRef("localhost:80//test:1.0.0")) + spec := Must(ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec)) + Expect(spec).To(Equal(ocireg.NewRepositorySpec("localhost:80"))) + }) + It("scheme://localhost:port//repository:version", func() { + ctx := oci.New() + ref := Must(oci.ParseRef("http://localhost:80//test:1.0.0")) + spec := Must(ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec)) + Expect(spec).To(Equal(ocireg.NewRepositorySpec("http://localhost:80"))) + }) + It("scheme://localhost:port/repository:version", func() { + ctx := oci.New() + ref := Must(oci.ParseRef("http://localhost:80/test:1.0.0")) + spec := Must(ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec)) + Expect(spec).To(Equal(ocireg.NewRepositorySpec("http://localhost:80"))) + }) + It("ctf with create", func() { + ctx := oci.New() + ref := Must(oci.ParseRef("+ctf+directory::./file/path//github.com/mandelsoft/ocm")) + spec := Must(ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec)) + Expect(spec).To(Equal(Must(ctf.NewRepositorySpec(accessobj.ACC_CREATE, "./file/path", accessio.FormatDirectory)))) + }) + It("ctf without create", func() { + ctx := oci.New() + + ref := Must(oci.ParseRepo("ctf+directory::./file/path")) + spec := Must(ctx.MapUniformRepositorySpec(&ref)) + Expect(spec).To(Equal(Must(ctf.NewRepositorySpec(accessobj.ACC_WRITABLE, "./file/path")))) + }) +}) diff --git a/api/oci/session.go b/api/oci/session.go new file mode 100644 index 000000000..a0b8a8b5e --- /dev/null +++ b/api/oci/session.go @@ -0,0 +1,180 @@ +package oci + +import ( + "encoding/json" + "reflect" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/datacontext" +) + +type NamespaceContainer interface { + LookupNamespace(name string) (NamespaceAccess, error) +} +type ArtifactContainer interface { + GetArtifact(version string) (ArtifactAccess, error) +} + +type EvaluationResult struct { + Ref RefSpec + Repository Repository + Namespace NamespaceAccess + Artifact ArtifactAccess +} + +type Session interface { + datacontext.Session + + LookupRepository(Context, RepositorySpec) (Repository, error) + LookupNamespace(NamespaceContainer, string) (NamespaceAccess, error) + GetArtifact(ArtifactContainer, string) (ArtifactAccess, error) + EvaluateRef(ctx Context, ref string) (*EvaluationResult, error) + DetermineRepository(ctx Context, ref string) (Repository, UniformRepositorySpec, error) + DetermineRepositoryBySpec(ctx Context, spec *UniformRepositorySpec) (Repository, error) +} + +type session struct { + datacontext.Session + base datacontext.SessionBase + repositories map[datacontext.ObjectKey]Repository + namespaces map[datacontext.ObjectKey]NamespaceAccess + artifacts map[datacontext.ObjectKey]ArtifactAccess +} + +var key = reflect.TypeOf(session{}) + +func NewSession(s datacontext.Session) Session { + return datacontext.GetOrCreateSubSession(s, key, newSession).(Session) +} + +func newSession(s datacontext.SessionBase) datacontext.Session { + return &session{ + Session: s.Session(), + base: s, + repositories: map[datacontext.ObjectKey]Repository{}, + namespaces: map[datacontext.ObjectKey]NamespaceAccess{}, + artifacts: map[datacontext.ObjectKey]ArtifactAccess{}, + } +} + +func (s *session) Close() error { + return s.Session.Close() + // TODO: cleanup cache +} + +func (s *session) LookupRepository(ctx Context, spec RepositorySpec) (Repository, error) { + spec, err := ctx.RepositoryTypes().Convert(spec) + if err != nil { + return nil, err + } + data, err := json.Marshal(spec) + if err != nil { + return nil, err + } + key := datacontext.ObjectKey{ + Object: ctx, + Name: string(data), + } + + s.base.Lock() + defer s.base.Unlock() + if s.base.IsClosed() { + return nil, errors.ErrClosed("session") + } + + if r := s.repositories[key]; r != nil { + return r, nil + } + repo, err := ctx.RepositoryForSpec(spec) + if err != nil { + return nil, err + } + s.repositories[key] = repo + s.base.AddCloser(repo) + return repo, err +} + +func (s *session) LookupNamespace(c NamespaceContainer, name string) (NamespaceAccess, error) { + key := datacontext.ObjectKey{ + Object: c, + Name: name, + } + s.base.Lock() + defer s.base.Unlock() + if s.base.IsClosed() { + return nil, errors.ErrClosed("session") + } + if ns := s.namespaces[key]; ns != nil { + return ns, nil + } + ns, err := c.LookupNamespace(name) + if err != nil { + return nil, err + } + s.namespaces[key] = ns + s.base.AddCloser(ns) + return ns, err +} + +func (s *session) GetArtifact(c ArtifactContainer, version string) (ArtifactAccess, error) { + key := datacontext.ObjectKey{ + Object: c, + Name: version, + } + s.base.Lock() + defer s.base.Unlock() + if s.base.IsClosed() { + return nil, errors.ErrClosed("session") + } + if obj := s.artifacts[key]; obj != nil { + return obj, nil + } + obj, err := c.GetArtifact(version) + if err != nil { + return nil, err + } + s.artifacts[key] = obj + s.base.AddCloser(obj) + return obj, err +} + +func (s *session) EvaluateRef(ctx Context, ref string) (*EvaluationResult, error) { + var err error + result := &EvaluationResult{} + result.Ref, err = ParseRef(ref) + if err != nil { + return nil, err + } + result.Repository, err = s.DetermineRepositoryBySpec(ctx, &result.Ref.UniformRepositorySpec) + if err != nil { + return nil, err + } + if result.Ref.Repository == "" { + return result, nil + } + result.Namespace, err = s.LookupNamespace(result.Repository, result.Ref.Repository) + + if !result.Ref.IsVersion() { + return result, err + } + result.Artifact, err = s.GetArtifact(result.Namespace, result.Ref.Version()) + return result, err +} + +func (s *session) DetermineRepository(ctx Context, ref string) (Repository, UniformRepositorySpec, error) { + spec, err := ParseRepo(ref) + if err != nil { + return nil, spec, err + } + r, err := s.DetermineRepositoryBySpec(ctx, &spec) + return r, spec, err +} + +func (s *session) DetermineRepositoryBySpec(ctx Context, spec *UniformRepositorySpec) (Repository, error) { + rspec, err := ctx.MapUniformRepositorySpec(spec) + if err != nil { + return nil, err + } + return s.LookupRepository(ctx, rspec) +} diff --git a/pkg/contexts/oci/suite_test.go b/api/oci/suite_test.go similarity index 100% rename from pkg/contexts/oci/suite_test.go rename to api/oci/suite_test.go diff --git a/pkg/contexts/oci/testhelper/manifests.go b/api/oci/testhelper/manifests.go similarity index 87% rename from pkg/contexts/oci/testhelper/manifests.go rename to api/oci/testhelper/manifests.go index 579a57784..824ab871f 100644 --- a/pkg/contexts/oci/testhelper/manifests.go +++ b/api/oci/testhelper/manifests.go @@ -3,16 +3,16 @@ package testhelper import ( "github.com/mandelsoft/goutils/testutils" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/digester/digesters/artifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/env/builder" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/signing/hasher/sha256" + "ocm.software/ocm/api/helper/builder" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/digester/digesters/artifact" + "ocm.software/ocm/api/tech/signing/hasher/sha256" + "ocm.software/ocm/api/utils/mime" ) //////////////////////////////////////////////////////////////////////////////// diff --git a/api/oci/testhelper/oci.go b/api/oci/testhelper/oci.go new file mode 100644 index 000000000..baa20e92b --- /dev/null +++ b/api/oci/testhelper/oci.go @@ -0,0 +1,17 @@ +package testhelper + +import ( + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/helper/builder" + "ocm.software/ocm/api/oci/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" +) + +func FakeOCIRepo(env *builder.Builder, path string, host string) string { + spec, err := ctf.NewRepositorySpec(accessobj.ACC_READONLY, path, accessio.PathFileSystem(env.FileSystem())) + ExpectWithOffset(1, err).To(Succeed()) + env.OCIContext().SetAlias(host, spec) + return host + ".alias" +} diff --git a/pkg/contexts/oci/transfer/filters/filter.go b/api/oci/tools/transfer/filters/filter.go similarity index 92% rename from pkg/contexts/oci/transfer/filters/filter.go rename to api/oci/tools/transfer/filters/filter.go index 406dc4405..486b083db 100644 --- a/pkg/contexts/oci/transfer/filters/filter.go +++ b/api/oci/tools/transfer/filters/filter.go @@ -5,9 +5,9 @@ import ( ociv1 "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/utils" ) type Filter interface { diff --git a/pkg/contexts/oci/transfer/suite_test.go b/api/oci/tools/transfer/suite_test.go similarity index 100% rename from pkg/contexts/oci/transfer/suite_test.go rename to api/oci/tools/transfer/suite_test.go diff --git a/api/oci/tools/transfer/transfer.go b/api/oci/tools/transfer/transfer.go new file mode 100644 index 000000000..e3d2f1912 --- /dev/null +++ b/api/oci/tools/transfer/transfer.go @@ -0,0 +1,142 @@ +package transfer + +import ( + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/finalizer" + "github.com/mandelsoft/goutils/generics" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/oci/tools/transfer/filters" + "ocm.software/ocm/api/utils/logging" +) + +func TransferArtifact(art cpi.ArtifactAccess, set cpi.ArtifactSink, tags ...string) error { + _, err := TransferArtifactWithFilter(art, set, nil, tags...) + return err +} + +func TransferArtifactWithFilter(art cpi.ArtifactAccess, set cpi.ArtifactSink, filter filters.Filter, tags ...string) (*digest.Digest, error) { + if art.GetDescriptor().IsIndex() { + return TransferIndexWithFilter(art.IndexAccess(), set, filter, tags...) + } else { + if filter != nil && !filter.Accept(art, nil) { + return nil, errors.ErrNoMatch(cpi.KIND_OCIARTIFACT, art.Digest().String()) + } + return generics.Pointer(art.Digest()), TransferManifest(art.ManifestAccess(), set, tags...) + } +} + +func TransferIndex(art cpi.IndexAccess, set cpi.ArtifactSink, tags ...string) error { + _, err := TransferIndexWithFilter(art, set, nil, tags...) + return err +} + +func TransferIndexWithFilter(art cpi.IndexAccess, set cpi.ArtifactSink, filter filters.Filter, tags ...string) (dig *digest.Digest, err error) { + logging.Logger().Debug("transfer OCI index", "digest", art.Digest()) + defer func() { + logging.Logger().Debug("transfer OCI index done", "error", logging.ErrorMessage(err)) + }() + + var finalize finalizer.Finalizer + defer finalize.FinalizeWithErrorPropagation(&err) + + // provide a local copy of the inbound artifact (index) + // which keeps track of the original serialized form, + // which is used if the manifest is left unchanged after + // filtering. + modified, err := cpi.NewArtifact(art) + if err != nil { + return nil, err + } + + // don't add this (index) object later. + // it implements the same interface, but would + // provide a new serialization, which might differ + // from the original one even if it is unchanged, + // which would change the digest. + index, err := modified.Index() + if err != nil { + return nil, err + } + + ign := 0 + for i, l := range art.GetDescriptor().Manifests { + loop := finalize.Nested() + logging.Logger().Debug("indexed manifest", "digest", l.Digest, "size", l.Size) + art, err := art.GetArtifact(l.Digest) + if err != nil { + return nil, errors.Wrapf(err, "getting indexed artifact %s", l.Digest) + } + loop.Close(art) + if filter == nil || filter.Accept(art, l.Platform) { + err = TransferArtifact(art, set) + if err != nil { + return nil, errors.Wrapf(err, "transferring indexed artifact %s", l.Digest) + } + dig = generics.Pointer(l.Digest) + } else { + index.Manifests = append(index.Manifests[:i-ign], index.Manifests[i-ign+1:]...) + ign++ + } + err = loop.Finalize() + if err != nil { + return nil, err + } + } + + if filter != nil { + switch len(art.GetDescriptor().Manifests) - ign { + case 0: + return nil, errors.ErrNoMatch(cpi.KIND_OCIARTIFACT, art.Digest().String()) + case 1: + if len(tags) > 0 { + err := set.AddTags(*dig, tags...) + if err != nil { + return nil, err + } + } + return dig, nil + } + } + + _, err = set.AddArtifact(modified, tags...) + if err != nil { + return nil, errors.Wrapf(err, "transferring index artifact") + } + return generics.Pointer(modified.Digest()), err +} + +func TransferManifest(art cpi.ManifestAccess, set cpi.ArtifactSink, tags ...string) (err error) { + logging.Logger().Debug("transfer OCI manifest", "digest", art.Digest()) + defer func() { + logging.Logger().Debug("transfer OCI manifest done", "error", logging.ErrorMessage(err)) + }() + + blob, err := art.GetConfigBlob() + if err != nil { + return errors.Wrapf(err, "getting config blob") + } + err = set.AddBlob(blob) + blob.Close() + if err != nil { + return errors.Wrapf(err, "transferring config blob") + } + for i, l := range art.GetDescriptor().Layers { + logging.Logger().Debug("layer", "digest", "digest", l.Digest, "size", l.Size, "index", i) + blob, err = art.GetBlob(l.Digest) + if err != nil { + return errors.Wrapf(err, "getting layer blob %s", l.Digest) + } + err = set.AddBlob(blob) + blob.Close() + if err != nil { + return errors.Wrapf(err, "transferring layer blob %s", l.Digest) + } + } + blob, err = set.AddArtifact(art, tags...) + if err != nil { + return errors.Wrapf(err, "transferring image artifact") + } + return blob.Close() +} diff --git a/api/oci/tools/transfer/transfer_test.go b/api/oci/tools/transfer/transfer_test.go new file mode 100644 index 000000000..897a5f144 --- /dev/null +++ b/api/oci/tools/transfer/transfer_test.go @@ -0,0 +1,185 @@ +package transfer_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + . "ocm.software/ocm/api/oci/testhelper" + + "github.com/mandelsoft/goutils/finalizer" + + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/extensions/repositories/ctf" + "ocm.software/ocm/api/oci/tools/transfer" + "ocm.software/ocm/api/oci/tools/transfer/filters" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" +) + +const ( + OUT = "/tmp/res" + OCIPATH = "/tmp/oci" +) + +var _ = Describe("transfer OCI artifacts", func() { + var env *Builder + var idesc *artdesc.Descriptor + + BeforeEach(func() { + env = NewBuilder() + env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { + idesc = OCIIndex1(env) + }) + }) + + AfterEach(func() { + env.Cleanup() + }) + + It("transfers index", func() { + // index implicitly tests transfer of simple manifest, also + var finalize finalizer.Finalizer + defer Defer(finalize.Finalize) + + src := Must(ctf.Open(env.OCIContext(), accessobj.ACC_READONLY, OCIPATH, 0, env)) + finalize.Close(src, "source") + art := Must(src.LookupArtifact(OCINAMESPACE3, OCIINDEXVERSION)) + finalize.Close(art, "source artifact") + + tgt := Must(ctf.Create(env.OCIContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) + defer Close(tgt, "target") + ns := Must(tgt.LookupNamespace(OCINAMESPACE3)) + defer Close(ns, "target namespace") + + MustBeSuccessful(transfer.TransferArtifact(art, ns, OCIINDEXVERSION)) + + MustBeSuccessful(finalize.Finalize()) + + tart := Must(ns.GetArtifact(idesc.Digest.String())) + defer Close(tart, "target index artifact") + + Expect(tart.IsIndex()) + manifests := tart.IndexAccess().GetDescriptor().Manifests + Expect(len(manifests)).To(Equal(3)) + Expect(manifests[0].Digest.Encoded()).To(Equal(D_OCIMANIFEST1)) + Expect(manifests[1].Digest.Encoded()).To(Equal(D_OCIMANIFEST2)) + Expect(manifests[2].Digest.Encoded()).To(Equal(D_OCIMANIFEST2)) + + nart1 := Must(ns.GetArtifact(manifests[0].Digest.String())) + defer Close(nart1, "nested artifact 1") + Expect(nart1.IsManifest()).To(BeTrue()) + Expect(len(nart1.ManifestAccess().GetDescriptor().Layers)).To(Equal(1)) + blob := Must(nart1.ManifestAccess().GetBlob(nart1.ManifestAccess().GetDescriptor().Layers[0].Digest)) + defer Close(blob, "layer 0 of nested artifact 1") + data := Must(blob.Get()) + Expect(string(data)).To(Equal(OCILAYER)) + Expect(manifests[0].Platform).To(Equal(&artdesc.Platform{OS: "linux", Architecture: "amd64"})) + + nart2 := Must(ns.GetArtifact(manifests[1].Digest.String())) + defer Close(nart2, "nested artifact 2") + Expect(nart2.IsManifest()).To(BeTrue()) + Expect(len(nart2.ManifestAccess().GetDescriptor().Layers)).To(Equal(1)) + blob = Must(nart2.ManifestAccess().GetBlob(nart2.ManifestAccess().GetDescriptor().Layers[0].Digest)) + defer Close(blob, "layer 0 of nested artifact 2") + data = Must(blob.Get()) + Expect(string(data)).To(Equal(OCILAYER2)) + Expect(manifests[1].Platform).To(Equal(&artdesc.Platform{OS: "linux", Architecture: "arm64"})) + + nart3 := Must(ns.GetArtifact(manifests[2].Digest.String())) + defer Close(nart3, "nested artifact 3") + Expect(nart2.IsManifest()).To(BeTrue()) + Expect(len(nart3.ManifestAccess().GetDescriptor().Layers)).To(Equal(1)) + blob = Must(nart3.ManifestAccess().GetBlob(nart2.ManifestAccess().GetDescriptor().Layers[0].Digest)) + defer Close(blob, "layer 0 of nested artifact 3") + data = Must(blob.Get()) + Expect(string(data)).To(Equal(OCILAYER2)) + Expect(manifests[2].Platform).To(Equal(&artdesc.Platform{OS: "darwin", Architecture: "arm64"})) + }) + + Context("with filter", func() { + It("transfers index", func() { + // index implicitly tests transfer of simple manifest, also + var finalize finalizer.Finalizer + defer Defer(finalize.Finalize) + + src := Must(ctf.Open(env.OCIContext(), accessobj.ACC_READONLY, OCIPATH, 0, env)) + finalize.Close(src, "source") + art := Must(src.LookupArtifact(OCINAMESPACE3, OCIINDEXVERSION)) + finalize.Close(art, "source artifact") + + tgt := Must(ctf.Create(env.OCIContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) + defer Close(tgt, "target") + ns := Must(tgt.LookupNamespace(OCINAMESPACE3)) + defer Close(ns, "target namespace") + + filter := filters.Platform("linux", "") + MustBeSuccessful(transfer.TransferArtifactWithFilter(art, ns, filter, OCIINDEXVERSION)) + + MustBeSuccessful(finalize.Finalize()) + + tart := Must(ns.GetArtifact(OCIINDEXVERSION)) + defer Close(tart, "target index artifact") + + Expect(tart.IsIndex()) + manifests := tart.IndexAccess().GetDescriptor().Manifests + Expect(len(manifests)).To(Equal(2)) + Expect(manifests[0].Digest.Encoded()).To(Equal(D_OCIMANIFEST1)) + Expect(manifests[1].Digest.Encoded()).To(Equal(D_OCIMANIFEST2)) + + nart1 := Must(ns.GetArtifact(manifests[0].Digest.String())) + defer Close(nart1, "nested artifact 1") + Expect(nart1.IsManifest()).To(BeTrue()) + Expect(len(nart1.ManifestAccess().GetDescriptor().Layers)).To(Equal(1)) + blob := Must(nart1.ManifestAccess().GetBlob(nart1.ManifestAccess().GetDescriptor().Layers[0].Digest)) + defer Close(blob, "layer 0 of nested artifact 1") + data := Must(blob.Get()) + Expect(string(data)).To(Equal(OCILAYER)) + Expect(manifests[0].Platform).To(Equal(&artdesc.Platform{OS: "linux", Architecture: "amd64"})) + + nart2 := Must(ns.GetArtifact(manifests[1].Digest.String())) + defer Close(nart2, "nested artifact 2") + Expect(nart2.IsManifest()).To(BeTrue()) + Expect(len(nart2.ManifestAccess().GetDescriptor().Layers)).To(Equal(1)) + blob = Must(nart2.ManifestAccess().GetBlob(nart2.ManifestAccess().GetDescriptor().Layers[0].Digest)) + defer Close(blob, "layer 0 of nested artifact 2") + data = Must(blob.Get()) + Expect(string(data)).To(Equal(OCILAYER2)) + Expect(manifests[1].Platform).To(Equal(&artdesc.Platform{OS: "linux", Architecture: "arm64"})) + }) + + It("transfers index to manifest", func() { + // index implicitly tests transfer of simple manifest, also + var finalize finalizer.Finalizer + defer Defer(finalize.Finalize) + + src := Must(ctf.Open(env.OCIContext(), accessobj.ACC_READONLY, OCIPATH, 0, env)) + finalize.Close(src, "source") + art := Must(src.LookupArtifact(OCINAMESPACE3, OCIINDEXVERSION)) + finalize.Close(art, "source artifact") + + tgt := Must(ctf.Create(env.OCIContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) + defer Close(tgt, "target") + ns := Must(tgt.LookupNamespace(OCINAMESPACE3)) + defer Close(ns, "target namespace") + + filter := filters.Platform("linux", "amd64") + MustBeSuccessful(transfer.TransferArtifactWithFilter(art, ns, filter, OCIINDEXVERSION)) + + MustBeSuccessful(finalize.Finalize()) + + tart := Must(ns.GetArtifact(OCIINDEXVERSION)) + defer Close(tart, "target index artifact") + + Expect(tart.IsManifest()) + + nart1 := tart + Expect(nart1.IsManifest()).To(BeTrue()) + Expect(len(nart1.ManifestAccess().GetDescriptor().Layers)).To(Equal(1)) + blob := Must(nart1.ManifestAccess().GetBlob(nart1.ManifestAccess().GetDescriptor().Layers[0].Digest)) + defer Close(blob, "layer 0 of nested artifact 1") + data := Must(blob.Get()) + Expect(string(data)).To(Equal(OCILAYER)) + }) + }) +}) diff --git a/api/oci/types/interface.go b/api/oci/types/interface.go new file mode 100644 index 000000000..2b1ef83cc --- /dev/null +++ b/api/oci/types/interface.go @@ -0,0 +1,87 @@ +package cpi + +// This is the Context Provider Interface for credential providers + +import ( + "github.com/opencontainers/go-digest" + ociv1 "github.com/opencontainers/image-spec/specs-go/v1" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/oci/internal" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +const CONTEXT_TYPE = internal.CONTEXT_TYPE + +const CommonTransportFormat = internal.CommonTransportFormat + +type ( + Context = internal.Context + ContextProvider = internal.ContextProvider + Repository = internal.Repository + RepositorySpecHandlers = internal.RepositorySpecHandlers + RepositorySpecHandler = internal.RepositorySpecHandler + UniformRepositorySpec = internal.UniformRepositorySpec + RepositoryType = internal.RepositoryType + RepositoryTypeProvider = internal.RepositoryTypeProvider + RepositoryTypeScheme = internal.RepositoryTypeScheme + RepositorySpec = internal.RepositorySpec + IntermediateRepositorySpecAspect = internal.IntermediateRepositorySpecAspect + GenericRepositorySpec = internal.GenericRepositorySpec + ArtifactAccess = internal.ArtifactAccess + Artifact = internal.Artifact + ArtifactSource = internal.ArtifactSource + ArtifactSink = internal.ArtifactSink + BlobSource = internal.BlobSource + BlobSink = internal.BlobSink + NamespaceLister = internal.NamespaceLister + NamespaceAccess = internal.NamespaceAccess + ManifestAccess = internal.ManifestAccess + IndexAccess = internal.IndexAccess + RepositorySource = internal.RepositorySource + ConsumerIdentityProvider = internal.ConsumerIdentityProvider +) + +type Descriptor = ociv1.Descriptor + +func DefaultContext() Context { + return internal.DefaultContext +} + +func New(m ...datacontext.BuilderMode) Context { + return internal.Builder{}.New(m...) +} + +func FromProvider(p ContextProvider) Context { + return internal.FromProvider(p) +} + +func RegisterRepositorySpecHandler(handler RepositorySpecHandler, types ...string) { + internal.RegisterRepositorySpecHandler(handler, types...) +} + +func ToGenericRepositorySpec(spec RepositorySpec) (*GenericRepositorySpec, error) { + return internal.ToGenericRepositorySpec(spec) +} + +func UniformRepositorySpecForHostURL(typ string, host string) *UniformRepositorySpec { + return internal.UniformRepositorySpecForHostURL(typ, host) +} + +const ( + KIND_OCIARTIFACT = internal.KIND_OCIARTIFACT + KIND_MEDIATYPE = blobaccess.KIND_MEDIATYPE + KIND_BLOB = blobaccess.KIND_BLOB +) + +func ErrUnknownArtifact(name, version string) error { + return internal.ErrUnknownArtifact(name, version) +} + +func ErrBlobNotFound(digest digest.Digest) error { + return blobaccess.ErrBlobNotFound(digest) +} + +func IsErrBlobNotFound(err error) bool { + return blobaccess.IsErrBlobNotFound(err) +} diff --git a/api/oci/utils.go b/api/oci/utils.go new file mode 100644 index 000000000..86a2425e8 --- /dev/null +++ b/api/oci/utils.go @@ -0,0 +1,45 @@ +package oci + +import ( + "fmt" + + ociidentity "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/grammar" + "ocm.software/ocm/api/utils/runtime" +) + +func AsTags(tag string) []string { + if tag != "" { + return []string{tag} + } + return nil +} + +func StandardOCIRef(host, repository, version string) string { + sep := grammar.TagSeparator + if ok, _ := artdesc.IsDigest(version); ok { + sep = grammar.DigestSeparator + } + return fmt.Sprintf("%s%s%s%s%s", host, grammar.RepositorySeparator, repository, sep, version) +} + +func IsIntermediate(spec RepositorySpec) bool { + if s, ok := spec.(IntermediateRepositorySpecAspect); ok { + return s.IsIntermediate() + } + return false +} + +func IsUnknown(r RepositorySpec) bool { + return runtime.IsUnknown(r) +} + +func GetConsumerIdForRef(ref string) (cpi.ConsumerIdentity, error) { + r, err := ParseRef(ref) + if err != nil { + return nil, err + } + return ociidentity.GetConsumerId(r.Host, r.Repository), nil +} diff --git a/pkg/contexts/ocm/add_test.go b/api/ocm/add_test.go similarity index 94% rename from pkg/contexts/ocm/add_test.go rename to api/ocm/add_test.go index 2d228f158..af008f6e0 100644 --- a/pkg/contexts/ocm/add_test.go +++ b/api/ocm/add_test.go @@ -4,15 +4,15 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/testhelper" - - "github.com/open-component-model/ocm/pkg/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "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/mime" + . "ocm.software/ocm/api/ocm/testhelper" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/repositories/composition" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/mime" ) var _ = Describe("add resources", func() { diff --git a/pkg/contexts/ocm/bind_test.go b/api/ocm/bind_test.go similarity index 85% rename from pkg/contexts/ocm/bind_test.go rename to api/ocm/bind_test.go index 10a88e8c6..2995ddefa 100644 --- a/pkg/contexts/ocm/bind_test.go +++ b/api/ocm/bind_test.go @@ -6,7 +6,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - me "github.com/open-component-model/ocm/pkg/contexts/ocm" + me "ocm.software/ocm/api/ocm" ) var _ = Describe("area test", func() { diff --git a/api/ocm/builder.go b/api/ocm/builder.go new file mode 100644 index 000000000..ab4d9480c --- /dev/null +++ b/api/ocm/builder.go @@ -0,0 +1,50 @@ +package ocm + +import ( + "context" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/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/api/ocm/compdesc/accessors.go b/api/ocm/compdesc/accessors.go new file mode 100644 index 000000000..a691c79f8 --- /dev/null +++ b/api/ocm/compdesc/accessors.go @@ -0,0 +1,84 @@ +package compdesc + +import ( + "ocm.software/ocm/api/ocm/compdesc/equivalent" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/selectors/accessors" +) + +// NameAccessor describes a accessor for a named object. +type NameAccessor interface { + // GetName returns the name of the object. + GetName() string + // SetName sets the name of the object. + SetName(name string) +} + +// VersionAccessor describes a accessor for a versioned object. +type VersionAccessor interface { + // GetVersion returns the version of the object. + GetVersion() string + // SetVersion sets the version of the object. + SetVersion(version string) +} + +// LabelsAccessor describes a accessor for a labeled object. +type LabelsAccessor interface { + // GetLabels returns the labels of the object. + GetLabels() metav1.Labels + // SetLabels sets the labels of the object. + SetLabels(labels []metav1.Label) +} + +// ObjectMetaAccessor describes a accessor for named and versioned object. +type ObjectMetaAccessor interface { + NameAccessor + VersionAccessor + LabelsAccessor +} + +// ElementMetaAccessor provides generic access an elements meta information. +type ElementMetaAccessor interface { + ElementMetaProvider + Equivalent(ElementMetaAccessor) equivalent.EqualState +} + +// ElementAccessor provides generic access to list of elements. +type ElementAccessor interface { + Len() int + Get(i int) ElementMetaAccessor +} + +type ElementMetaProvider interface { + GetMeta() *ElementMeta +} + +// ElementArtifactAccessor provides access to generic artifact information of an element. +type ElementArtifactAccessor interface { + ElementMetaAccessor + GetType() string + GetAccess() AccessSpec + SetAccess(a AccessSpec) +} + +type ElementDigestAccessor interface { + GetDigest() *metav1.DigestSpec + SetDigest(*metav1.DigestSpec) +} + +// ArtifactAccessor provides generic access to list of artifacts. +// There are resources or sources. +type ArtifactAccessor interface { + ElementAccessor + GetArtifact(i int) ElementArtifactAccessor +} + +// AccessSpec is an abstract specification of an access method +// The outbound object is typicall a runtime.UnstructuredTypedObject. +// Inbound any serializable AccessSpec implementation is possible. +type AccessSpec = accessors.AccessSpec + +// AccessProvider provides access to an access specification of elements. +type AccessProvider interface { + GetAccess() AccessSpec +} diff --git a/pkg/contexts/ocm/compdesc/codecs.go b/api/ocm/compdesc/codecs.go similarity index 100% rename from pkg/contexts/ocm/compdesc/codecs.go rename to api/ocm/compdesc/codecs.go diff --git a/pkg/contexts/ocm/compdesc/compdesc_test.go b/api/ocm/compdesc/compdesc_test.go similarity index 96% rename from pkg/contexts/ocm/compdesc/compdesc_test.go rename to api/ocm/compdesc/compdesc_test.go index 4f1ba0946..c0b0d04db 100644 --- a/pkg/contexts/ocm/compdesc/compdesc_test.go +++ b/api/ocm/compdesc/compdesc_test.go @@ -7,8 +7,8 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - compdescv3 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1" + "ocm.software/ocm/api/ocm/compdesc" + compdescv3 "ocm.software/ocm/api/ocm/compdesc/versions/ocm.software/v3alpha1" ) func NormalizeYAML(y string) string { diff --git a/api/ocm/compdesc/componentdescriptor.go b/api/ocm/compdesc/componentdescriptor.go new file mode 100644 index 000000000..f8fe31ea5 --- /dev/null +++ b/api/ocm/compdesc/componentdescriptor.go @@ -0,0 +1,844 @@ +package compdesc + +import ( + "fmt" + "reflect" + "strings" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/ocm/compdesc/equivalent" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/selectors/accessors" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/api/utils/semverutils" +) + +const InternalSchemaVersion = "internal" + +var NotFound = errors.ErrNotFound() + +const KIND_REFERENCE = "component reference" + +const ComponentDescriptorFileName = "component-descriptor.yaml" + +// Metadata defines the configured metadata of the component descriptor. +// It is taken from the original serialization format. It can be set +// to define a default serialization version. +type Metadata struct { + ConfiguredVersion string `json:"configuredSchemaVersion"` +} + +// ComponentDescriptor defines a versioned component with a source and dependencies. +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type ComponentDescriptor struct { + // Metadata specifies the schema version of the component. + Metadata Metadata `json:"meta"` + // Spec contains the specification of the component. + ComponentSpec `json:"component"` + // Signatures contains a list of signatures for the ComponentDescriptor + Signatures metav1.Signatures `json:"signatures,omitempty"` + + // NestedDigets describe calculated resource digests for aggregated + // component versions, which might not be persisted, but incorporated + // into signatures of the actual component version + NestedDigests metav1.NestedDigests `json:"nestedDigests,omitempty"` +} + +func New(name, version string) *ComponentDescriptor { + return DefaultComponent(&ComponentDescriptor{ + Metadata: Metadata{ + ConfiguredVersion: "v2", + }, + ComponentSpec: ComponentSpec{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Version: version, + Provider: metav1.Provider{ + Name: "acme", + }, + }, + }, + }) +} + +// SchemaVersion returns the scheme version configured in the representation. +func (cd *ComponentDescriptor) SchemaVersion() string { + return cd.Metadata.ConfiguredVersion +} + +func (cd *ComponentDescriptor) Copy() *ComponentDescriptor { + out := &ComponentDescriptor{ + Metadata: cd.Metadata, + ComponentSpec: ComponentSpec{ + ObjectMeta: *cd.ObjectMeta.Copy(), + RepositoryContexts: cd.RepositoryContexts.Copy(), + Sources: cd.Sources.Copy(), + References: cd.References.Copy(), + Resources: cd.Resources.Copy(), + }, + Signatures: cd.Signatures.Copy(), + NestedDigests: cd.NestedDigests.Copy(), + } + return out +} + +func (cd *ComponentDescriptor) Reset() { + cd.Provider.Name = "" + cd.Provider.Labels = nil + cd.Resources = nil + cd.Sources = nil + cd.References = nil + cd.RepositoryContexts = nil + cd.Signatures = nil + cd.Labels = nil + DefaultComponent(cd) +} + +// ComponentSpec defines a virtual component with +// a repository context, source and dependencies. +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type ComponentSpec struct { + metav1.ObjectMeta `json:",inline"` + // RepositoryContexts defines the previous repositories of the component + RepositoryContexts runtime.UnstructuredTypedObjectList `json:"repositoryContexts"` + // Sources defines sources that produced the component + Sources Sources `json:"sources"` + // References references component dependencies that can be resolved in the current context. + References References `json:"componentReferences"` + // Resources defines all resources that are created by the component and by a third party. + Resources Resources `json:"resources"` +} + +const ( + SystemIdentityName = metav1.SystemIdentityName + SystemIdentityVersion = metav1.SystemIdentityVersion +) + +type ElementMetaAccess interface { + GetName() string + GetVersion() string + GetIdentity(accessor ElementAccessor) metav1.Identity + GetLabels() metav1.Labels +} + +type ArtifactMetaAccess interface { + ElementMetaAccess + GetType() string + SetType(string) +} + +// ArtifactMetaPointer is a pointer to an artifact meta object. +type ArtifactMetaPointer[P any] interface { + ArtifactMetaAccess + *P +} + +// ElementMeta defines a object that is uniquely identified by its identity. +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type ElementMeta struct { + // Name is the context unique name of the object. + Name string `json:"name"` + // Version is the semver version of the object. + Version string `json:"version"` + // ExtraIdentity is the identity of an object. + // An additional label with key "name" ist not allowed + ExtraIdentity metav1.Identity `json:"extraIdentity,omitempty"` + // Labels defines an optional set of additional labels + // describing the object. + // +optional + Labels metav1.Labels `json:"labels,omitempty"` +} + +// GetName returns the name of the object. +func (o *ElementMeta) GetName() string { + return o.Name +} + +// GetMeta returns the element meta. +func (r *ElementMeta) GetMeta() *ElementMeta { + return r +} + +// GetExtraIdentity returns the extra identity of the object. +func (o *ElementMeta) GetExtraIdentity() metav1.Identity { + if o.ExtraIdentity == nil { + return metav1.Identity{} + } + return o.ExtraIdentity.Copy() +} + +// SetName sets the name of the object. +func (o *ElementMeta) SetName(name string) { + o.Name = name +} + +// GetVersion returns the version of the object. +func (o *ElementMeta) GetVersion() string { + return o.Version +} + +// SetVersion sets the version of the object. +func (o *ElementMeta) SetVersion(version string) { + o.Version = version +} + +// GetLabels returns the label of the object. +func (o *ElementMeta) GetLabels() metav1.Labels { + return o.Labels +} + +// SetLabels sets the labels of the object. +func (o *ElementMeta) SetLabels(labels []metav1.Label) { + o.Labels = labels +} + +// SetLabel sets a single label to an effective value. +// If the value is no byte slice, it is marshaled. +func (o *ElementMeta) SetLabel(name string, value interface{}, opts ...metav1.LabelOption) error { + return o.Labels.Set(name, value, opts...) +} + +// RemoveLabel removes a single label. +func (o *ElementMeta) RemoveLabel(name string) bool { + return o.Labels.Remove(name) +} + +// SetExtraIdentity sets the identity of the object. +func (o *ElementMeta) SetExtraIdentity(identity metav1.Identity) { + o.ExtraIdentity = identity +} + +func (o *ElementMeta) AddExtraIdentity(identity metav1.Identity) { + if o.ExtraIdentity == nil { + o.ExtraIdentity = identity + } else { + o.ExtraIdentity = o.ExtraIdentity.Copy() + for k, v := range identity { + o.ExtraIdentity[k] = v + } + } +} + +// GetIdentity returns the identity of the object. +func (o *ElementMeta) GetIdentity(accessor ElementAccessor) metav1.Identity { + identity := o.ExtraIdentity.Copy() + if identity == nil { + identity = metav1.Identity{} + } + identity[SystemIdentityName] = o.Name + if accessor != nil { + found := false + l := accessor.Len() + for i := 0; i < l; i++ { + m := accessor.Get(i).GetMeta() + if m.Name == o.Name && m.ExtraIdentity.Equals(o.ExtraIdentity) { + if found { + identity[SystemIdentityVersion] = o.Version + + break + } + found = true + } + } + } + return identity +} + +// GetIdentityForContext returns the identity of the object. +func (o *ElementMeta) GetIdentityForContext(accessor accessors.ElementListAccessor) metav1.Identity { + identity := o.ExtraIdentity.Copy() + if identity == nil { + identity = metav1.Identity{} + } + identity[SystemIdentityName] = o.Name + if accessor != nil { + found := false + l := accessor.Len() + for i := 0; i < l; i++ { + m := accessor.Get(i).GetMeta() + if m.GetName() == o.GetName() && m.GetExtraIdentity().Equals(o.ExtraIdentity) { + if found { + identity[SystemIdentityVersion] = o.Version + + break + } + found = true + } + } + } + return identity +} + +// GetRawIdentity returns the identity plus version. +func (o *ElementMeta) GetRawIdentity() metav1.Identity { + identity := o.ExtraIdentity.Copy() + if identity == nil { + identity = metav1.Identity{} + } + identity[SystemIdentityName] = o.Name + if o.Version != "" { + identity[SystemIdentityVersion] = o.Version + } + return identity +} + +// GetMatchBaseIdentity returns all possible identity attributes for resource matching. +func (o *ElementMeta) GetMatchBaseIdentity() metav1.Identity { + identity := o.ExtraIdentity.Copy() + if identity == nil { + identity = metav1.Identity{} + } + identity[SystemIdentityName] = o.Name + identity[SystemIdentityVersion] = o.Version + + return identity +} + +// GetIdentityDigest returns the digest of the object's identity. +func (o *ElementMeta) GetIdentityDigest(accessor ElementAccessor) []byte { + return o.GetIdentity(accessor).Digest() +} + +func (o *ElementMeta) Copy() *ElementMeta { + if o == nil { + return nil + } + return &ElementMeta{ + Name: o.Name, + Version: o.Version, + ExtraIdentity: o.ExtraIdentity.Copy(), + Labels: o.Labels.Copy(), + } +} + +func (o *ElementMeta) Equivalent(a *ElementMeta) equivalent.EqualState { + if o == a { + return equivalent.StateEquivalent() + } + if o == nil { + o, a = a, o + } + if a == nil { + return o.Labels.Equivalent(nil) + } + + state := equivalent.StateLocalHashEqual(a.Name == o.Name && a.Version == o.Version && reflect.DeepEqual(a.ExtraIdentity, o.ExtraIdentity)) + return state.Apply(o.Labels.Equivalent(a.Labels)) +} + +func GetByIdentity(a ElementAccessor, id metav1.Identity) ElementMetaAccessor { + l := a.Len() + for i := 0; i < l; i++ { + e := a.Get(i) + if e.GetMeta().GetIdentity(a).Equals(id) { + return e + } + } + return nil +} + +func GetIndexByIdentity(a ElementAccessor, id metav1.Identity) int { + l := a.Len() + for i := 0; i < l; i++ { + e := a.Get(i) + if e.GetMeta().GetIdentity(a).Equals(id) { + return i + } + } + return -1 +} + +// ArtifactAccess provides access to a dedicated kind of artifact set +// in the component descriptor (resources or sources). +type ArtifactAccess func(cd *ComponentDescriptor) ArtifactAccessor + +// GenericAccessSpec returns a generic AccessSpec implementation for an unstructured object. +// It can always be used instead of a dedicated access spec implementation. The core +// methods will map these spec into effective ones before an access is returned to the caller. +func GenericAccessSpec(un *runtime.UnstructuredTypedObject) AccessSpec { + return &runtime.UnstructuredVersionedTypedObject{ + *un.DeepCopy(), + } +} + +// Sources describes a set of source specifications. +type Sources []Source + +var _ ElementAccessor = Sources{} + +func SourceArtifacts(cd *ComponentDescriptor) ArtifactAccessor { + return cd.Sources +} + +func (r Sources) Equivalent(o Sources) equivalent.EqualState { + return EquivalentElems(r, o) +} + +func (s Sources) Len() int { + return len(s) +} + +func (s Sources) Get(i int) ElementMetaAccessor { + return &s[i] +} + +func (s Sources) GetArtifact(i int) ElementArtifactAccessor { + return &s[i] +} + +func (s Sources) Copy() Sources { + if s == nil { + return nil + } + out := make(Sources, len(s)) + for i, v := range s { + out[i] = *v.Copy() + } + return out +} + +// Source is the definition of a component's source. +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type Source struct { + SourceMeta `json:",inline"` + Access AccessSpec `json:"access"` +} + +func (s *Source) GetAccess() AccessSpec { + return s.Access +} + +func (r *Source) SetAccess(a AccessSpec) { + r.Access = a +} + +func (r *Source) Equivalent(e ElementMetaAccessor) equivalent.EqualState { + if o, ok := e.(*Source); !ok { + return equivalent.StateNotLocalHashEqual() + } else { + state := equivalent.StateLocalHashEqual(r.Type == o.Type) + return state.Apply( + r.ElementMeta.Equivalent(&o.ElementMeta), + ) + } +} + +func (s *Source) Copy() *Source { + return &Source{ + SourceMeta: *s.SourceMeta.Copy(), + Access: s.Access, + } +} + +// SourceMeta is the definition of the meta data of a source. +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type SourceMeta struct { + ElementMeta + // Type describes the type of the object. + Type string `json:"type"` +} + +// GetType returns the type of the object. +func (o *SourceMeta) GetType() string { + return o.Type +} + +// SetType sets the type of the object. +func (o *SourceMeta) SetType(ttype string) { + o.Type = ttype +} + +// Copy copies a source meta. +func (o *SourceMeta) Copy() *SourceMeta { + if o == nil { + return nil + } + return &SourceMeta{ + ElementMeta: *o.ElementMeta.Copy(), + Type: o.Type, + } +} + +func (o *SourceMeta) WithVersion(v string) *SourceMeta { + r := *o + r.Version = v + return &r +} + +func (o *SourceMeta) WithExtraIdentity(extras ...string) *SourceMeta { + r := *o + r.AddExtraIdentity(NewExtraIdentity(extras...)) + return &r +} + +func (o *SourceMeta) WithLabel(l *Label) *SourceMeta { + r := *o + r.Labels.SetDef(l.Name, l) + return &r +} + +func NewSourceMeta(name, typ string) *SourceMeta { + return &SourceMeta{ + ElementMeta: ElementMeta{Name: name}, + Type: typ, + } +} + +// SourceRef defines a reference to a source +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type SourceRef struct { + // IdentitySelector defines the identity that is used to match a source. + IdentitySelector metav1.StringMap `json:"identitySelector,omitempty"` + // Labels defines an optional set of additional labels + // describing the object. + // +optional + Labels metav1.Labels `json:"labels,omitempty"` +} + +// Copy copy a source ref. +func (r *SourceRef) Copy() *SourceRef { + if r == nil { + return nil + } + return &SourceRef{ + IdentitySelector: r.IdentitySelector.Copy(), + Labels: r.Labels.Copy(), + } +} + +type SourceRefs []SourceRef + +// Copy copies a list of source refs. +func (r SourceRefs) Copy() SourceRefs { + if r == nil { + return nil + } + + result := make(SourceRefs, len(r)) + for i, v := range r { + result[i] = *v.Copy() + } + return result +} + +// Resources describes a set of resource specifications. +type Resources []Resource + +var _ ElementAccessor = Resources{} + +func ResourceArtifacts(cd *ComponentDescriptor) ArtifactAccessor { + return cd.Resources +} + +func (r Resources) Equivalent(o Resources) equivalent.EqualState { + return EquivalentElems(r, o) +} + +func (r Resources) Len() int { + return len(r) +} + +func (r Resources) Get(i int) ElementMetaAccessor { + return &r[i] +} + +func (r Resources) GetArtifact(i int) ElementArtifactAccessor { + return &r[i] +} + +func (r Resources) Copy() Resources { + if r == nil { + return nil + } + out := make(Resources, len(r)) + for i, v := range r { + out[i] = *v.Copy() + } + return out +} + +func (r Resources) HaveDigests() bool { + for _, e := range r { + if e.Digest == nil { + return false + } + } + return true +} + +// Resource describes a resource dependency of a component. +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type Resource struct { + ResourceMeta `json:",inline"` + // Access describes the type specific method to + // access the defined resource. + Access AccessSpec `json:"access"` +} + +func (r *Resource) GetAccess() AccessSpec { + return r.Access +} + +func (r *Resource) SetAccess(a AccessSpec) { + r.Access = a +} + +func (r *Resource) GetDigest() *metav1.DigestSpec { + return r.Digest +} + +func (r *Resource) SetDigest(d *metav1.DigestSpec) { + r.Digest = d +} + +func (r *Resource) GetRelation() metav1.ResourceRelation { + return r.Relation +} + +func (r *Resource) Equivalent(e ElementMetaAccessor) equivalent.EqualState { + if o, ok := e.(*Resource); !ok { + state := equivalent.StateNotLocalHashEqual() + if r.Digest.IsExcluded() || IsNoneAccess(r.Access) { + return state + } else { + state = state.Apply(equivalent.StateNotArtifactEqual(r.Digest != nil)) + } + return state + } else { + // not delegated to ResourceMeta, because the significance of digests can only be determined at the Resource level. + state := equivalent.StateLocalHashEqual(r.Type == o.Type && r.Relation == o.Relation && reflect.DeepEqual(r.SourceRefs, o.SourceRefs)) + + if !IsNoneAccess(r.Access) || !IsNoneAccess(o.Access) { + state = state.Apply(r.Digest.Equivalent(o.Digest)) + } + return state.Apply(r.ElementMeta.Equivalent(&o.ElementMeta)) + } +} + +func (r *Resource) Copy() *Resource { + return &Resource{ + ResourceMeta: *r.ResourceMeta.Copy(), + Access: r.Access, + } +} + +// ResourceMeta describes the meta data of a resource. +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type ResourceMeta struct { + ElementMeta `json:",inline"` + + // Type describes the type of the object. + Type string `json:"type"` + + // Relation describes the relation of the resource to the component. + // Can be a local or external resource + Relation metav1.ResourceRelation `json:"relation,omitempty"` + + // SourceRefs defines a list of source names. + // These entries reference the sources defined in the + // component.sources. + SourceRefs SourceRefs `json:"srcRefs,omitempty"` + + // Digest is the optional digest of the referenced resource. + // +optional + Digest *metav1.DigestSpec `json:"digest,omitempty"` +} + +// Fresh returns a digest-free copy. +func (o *ResourceMeta) Fresh() *ResourceMeta { + n := o.Copy() + n.Digest = nil + return n +} + +// GetType returns the type of the object. +func (o *ResourceMeta) GetType() string { + return o.Type +} + +// SetType sets the type of the object. +func (o *ResourceMeta) SetType(ttype string) { + o.Type = ttype +} + +// SetDigest sets the digest of the object. +func (o *ResourceMeta) SetDigest(d *metav1.DigestSpec) { + o.Digest = d +} + +// Copy copies a resource meta. +func (o *ResourceMeta) Copy() *ResourceMeta { + if o == nil { + return nil + } + r := &ResourceMeta{ + ElementMeta: *o.ElementMeta.Copy(), + Type: o.Type, + Relation: o.Relation, + SourceRefs: o.SourceRefs.Copy(), + Digest: o.Digest.Copy(), + } + return r +} + +func (o *ResourceMeta) WithVersion(v string) *ResourceMeta { + r := *o + r.Version = v + return &r +} + +func (o *ResourceMeta) WithExtraIdentity(extras ...string) *ResourceMeta { + r := *o + r.AddExtraIdentity(NewExtraIdentity(extras...)) + return &r +} + +func (o *ResourceMeta) WithLabel(l *Label) *ResourceMeta { + r := *o + r.Labels.SetDef(l.Name, l) + return &r +} + +func NewResourceMeta(name string, typ string, relation metav1.ResourceRelation) *ResourceMeta { + return &ResourceMeta{ + ElementMeta: ElementMeta{Name: name}, + Type: typ, + Relation: relation, + } +} + +type References []ComponentReference + +func (r References) Equivalent(o References) equivalent.EqualState { + return EquivalentElems(r, o) +} + +func (r References) Len() int { + return len(r) +} + +func (r References) Get(i int) ElementMetaAccessor { + return &r[i] +} + +func (r References) Swap(i, j int) { + r[i], r[j] = r[j], r[i] +} + +func (r References) Less(i, j int) bool { + c := strings.Compare(r[i].Name, r[j].Name) + if c != 0 { + return c < 0 + } + return semverutils.Compare(r[i].Version, r[j].Version) < 0 +} + +func (r References) Copy() References { + if r == nil { + return nil + } + out := make(References, len(r)) + for i, v := range r { + out[i] = *v.Copy() + } + return out +} + +// ComponentReference describes the reference to another component in the registry. +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type ComponentReference struct { + ElementMeta `json:",inline"` + // ComponentName describes the remote name of the referenced object + ComponentName string `json:"componentName"` + // Digest is the optional digest of the referenced component. + // +optional + Digest *metav1.DigestSpec `json:"digest,omitempty"` +} + +func NewComponentReference(name, componentName, version string, extraIdentity metav1.Identity) *ComponentReference { + return &ComponentReference{ + ElementMeta: ElementMeta{ + Name: name, + Version: version, + ExtraIdentity: extraIdentity, + }, + ComponentName: componentName, + } +} + +func (r ComponentReference) String() string { + return fmt.Sprintf("%s[%s:%s]", r.Name, r.ComponentName, r.Version) +} + +// WithVersion returns a new reference with a dedicated version. +func (o *ComponentReference) WithVersion(v string) *ComponentReference { + n := o.Copy() + n.Version = v + return n +} + +// WithExtraIdentity returns a new reference with a dedicated version. +func (o *ComponentReference) WithExtraIdentity(extras ...string) *ComponentReference { + n := o.Copy() + n.AddExtraIdentity(NewExtraIdentity(extras...)) + return n +} + +// Fresh returns a digest-free copy. +func (o *ComponentReference) Fresh() *ComponentReference { + n := o.Copy() + n.Digest = nil + return n +} + +func (r *ComponentReference) GetDigest() *metav1.DigestSpec { + return r.Digest +} + +func (r *ComponentReference) SetDigest(d *metav1.DigestSpec) { + r.Digest = d +} + +func (r *ComponentReference) Equivalent(e ElementMetaAccessor) equivalent.EqualState { + if o, ok := e.(*ComponentReference); !ok { + state := equivalent.StateNotLocalHashEqual() + if r.Digest != nil { + state = state.Apply(equivalent.StateNotArtifactEqual(true)) + } + return state + } else { + state := equivalent.StateLocalHashEqual(r.Name == o.Name && r.Version == o.Version && r.ComponentName == o.ComponentName) + // TODO: how to handle digests + if r.Digest != nil && o.Digest != nil { // hmm, digest described more than the local component, should we use it at all? + state = state.Apply(r.Digest.Equivalent(o.Digest)) + } else if r.Digest != o.Digest { // not both are nil + state = state.NotEquivalent() + } + + return state.Apply( + r.ElementMeta.Equivalent(&o.ElementMeta), + ) + } +} + +func (r *ComponentReference) GetComponentName() string { + return r.ComponentName +} + +func (r *ComponentReference) Copy() *ComponentReference { + return &ComponentReference{ + ElementMeta: *r.ElementMeta.Copy(), + ComponentName: r.ComponentName, + Digest: r.Digest.Copy(), + } +} diff --git a/pkg/contexts/ocm/compdesc/copy_test.go b/api/ocm/compdesc/copy_test.go similarity index 87% rename from pkg/contexts/ocm/compdesc/copy_test.go rename to api/ocm/compdesc/copy_test.go index e2d8f006b..1cb1c69f2 100644 --- a/pkg/contexts/ocm/compdesc/copy_test.go +++ b/api/ocm/compdesc/copy_test.go @@ -7,11 +7,11 @@ import ( "github.com/go-test/deep" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/handlers/defaultmerge" - "github.com/open-component-model/ocm/pkg/runtime" + "ocm.software/ocm/api/ocm/compdesc" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + "ocm.software/ocm/api/ocm/valuemergehandler/handlers/defaultmerge" + "ocm.software/ocm/api/utils/runtime" ) var _ = Describe("Component Descripor Copy Test Suitet", func() { diff --git a/api/ocm/compdesc/default.go b/api/ocm/compdesc/default.go new file mode 100644 index 000000000..bb9879a8e --- /dev/null +++ b/api/ocm/compdesc/default.go @@ -0,0 +1,52 @@ +package compdesc + +import ( + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/utils/runtime" +) + +// DefaultComponent applies defaults to a component. +func DefaultComponent(component *ComponentDescriptor) *ComponentDescriptor { + if component.RepositoryContexts == nil { + component.RepositoryContexts = make([]*runtime.UnstructuredTypedObject, 0) + } + if component.Sources == nil { + component.Sources = make([]Source, 0) + } + if component.References == nil { + component.References = make([]ComponentReference, 0) + } + if component.Resources == nil { + component.Resources = make([]Resource, 0) + } + + if component.Metadata.ConfiguredVersion == "" { + component.Metadata.ConfiguredVersion = DefaultSchemeVersion + } + // DefaultResources(component) + return component +} + +// DefaultResources defaults a list of resources. +// The version of the component is defaulted for local resources that do not contain a version. +// adds the version as identity if the resource identity would clash otherwise. +func DefaultResources(component *ComponentDescriptor) { + for i, res := range component.Resources { + if res.Relation == v1.LocalRelation && len(res.Version) == 0 { + component.Resources[i].Version = component.GetVersion() + } + + id := res.GetIdentity(component.Resources) + if v, ok := id[SystemIdentityVersion]; ok { + if res.ExtraIdentity == nil { + res.ExtraIdentity = v1.Identity{ + SystemIdentityVersion: v, + } + } else { + if _, ok := res.ExtraIdentity[SystemIdentityVersion]; !ok { + res.ExtraIdentity[SystemIdentityVersion] = v + } + } + } + } +} diff --git a/api/ocm/compdesc/deprecated.go b/api/ocm/compdesc/deprecated.go new file mode 100644 index 000000000..9b10b32d5 --- /dev/null +++ b/api/ocm/compdesc/deprecated.go @@ -0,0 +1,287 @@ +package compdesc + +import ( + "bytes" + "fmt" + + "github.com/mandelsoft/goutils/sliceutils" + + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/utils/selector" +) + +// GetResourceAccessByIdentity returns a pointer to the resource that matches the given identity. +// +// Deprecated: use GetResourceByIdentity. +func (cd *ComponentDescriptor) GetResourceAccessByIdentity(id v1.Identity) *Resource { + dig := id.Digest() + for i, res := range cd.Resources { + if bytes.Equal(res.GetIdentityDigest(cd.Resources), dig) { + return &cd.Resources[i] + } + } + return nil +} + +// GetResourceByRegexSelector returns resources that match the given selectors. +// +// Deprecated: use GetResources with appropriate selectors. +func (cd *ComponentDescriptor) GetResourceByRegexSelector(sel interface{}) (Resources, error) { + identitySelector, err := selector.ParseRegexSelector(sel) + if err != nil { + return nil, fmt.Errorf("unable to parse selector: %w", err) + } + return cd.GetResourcesByIdentitySelectors(identitySelector) +} + +// GetResourcesByIdentitySelectors returns resources that match the given identity selectors. +// Deprecated: use GetResources with appropriate selectors. +func (cd *ComponentDescriptor) GetResourcesByIdentitySelectors(selectors ...IdentitySelector) (Resources, error) { + return cd.GetResourcesBySelectors(selectors, nil) +} + +// GetResourcesByResourceSelectors returns resources that match the given resource selectors. +// +// Deprecated: use GetResources with appropriate selectors. +func (cd *ComponentDescriptor) GetResourcesByResourceSelectors(selectors ...ResourceSelector) (Resources, error) { + return cd.GetResourcesBySelectors(nil, selectors) +} + +// GetResourcesBySelectors returns resources that match the given selector. +// +// Deprecated: use GetResources with appropriate selectors. +func (cd *ComponentDescriptor) GetResourcesBySelectors(selectors []IdentitySelector, resourceSelectors []ResourceSelector) (Resources, error) { + resources := make(Resources, 0) + for i := range cd.Resources { + selctx := NewResourceSelectionContext(i, cd.Resources) + if len(selectors) > 0 { + ok, err := selector.MatchSelectors(selctx.Identity(), selectors...) + if err != nil { + return nil, fmt.Errorf("unable to match selector for resource %s: %w", selctx.Name, err) + } + if !ok { + continue + } + } + ok, err := MatchResourceByResourceSelector(selctx, resourceSelectors...) + if err != nil { + return nil, fmt.Errorf("unable to match selector for resource %s: %w", selctx.Name, err) + } + if !ok { + continue + } + resources = append(resources, *selctx.Resource) + } + if len(resources) == 0 { + return resources, NotFound + } + return resources, nil +} + +// GetExternalResources returns external resource with the given type, name and version. +// +// Deprecated: use GetResources with appropriate selectors. +func (cd *ComponentDescriptor) GetExternalResources(rtype, name, version string) (Resources, error) { + return cd.GetResourcesBySelectors( + []selector.Interface{ + ByName(name), + ByVersion(version), + }, + []ResourceSelector{ + ByResourceType(rtype), + ByRelation(v1.ExternalRelation), + }) +} + +// GetExternalResource returns external resource with the given type, name and version. +// +// If multiple resources match, the first one is returned. +// Deprecated: use GetResources with appropriate selectors. +func (cd *ComponentDescriptor) GetExternalResource(rtype, name, version string) (Resource, error) { + resources, err := cd.GetExternalResources(rtype, name, version) + if err != nil { + return Resource{}, err + } + // at least one resource must be defined, otherwise the getResourceBySelectors functions returns a NotFound err. + return resources[0], nil +} + +// GetLocalResources returns all local resources with the given type, name and version. +func (cd *ComponentDescriptor) GetLocalResources(rtype, name, version string) (Resources, error) { + return cd.GetResourcesBySelectors( + []selector.Interface{ + ByName(name), + ByVersion(version), + }, + []ResourceSelector{ + ByResourceType(rtype), + ByRelation(v1.LocalRelation), + }) +} + +// GetLocalResource returns a local resource with the given type, name and version. +// +// If multiple resources match, the first one is returned. +// Deprecated: use GetResources with appropriate selectors. +func (cd *ComponentDescriptor) GetLocalResource(rtype, name, version string) (Resource, error) { + resources, err := cd.GetLocalResources(rtype, name, version) + if err != nil { + return Resource{}, err + } + // at least one resource must be defined, otherwise the getResourceBySelectors functions returns a NotFound err. + return resources[0], nil +} + +// GetResourcesByType returns all resources that match the given type and selectors. +// +// Deprecated: use GetResources with appropriate selectors. +func (cd *ComponentDescriptor) GetResourcesByType(rtype string, selectors ...IdentitySelector) (Resources, error) { + return cd.GetResourcesBySelectors( + selectors, + []ResourceSelector{ + ByResourceType(rtype), + }) +} + +// GetResourcesByName returns all local and external resources with a name. +// +// Deprecated: use GetResources with appropriate selectors. +func (cd *ComponentDescriptor) GetResourcesByName(name string, selectors ...IdentitySelector) (Resources, error) { + return cd.GetResourcesBySelectors( + sliceutils.CopyAppend[IdentitySelector](selectors, ByName(name)), + nil) +} + +//////////////////////////////////////////////////////////////////////////////// + +// GetSourceAccessByIdentity returns a pointer to the source that matches the given identity. +// +// Deprecated: use GetSourceByIdentity. +func (cd *ComponentDescriptor) GetSourceAccessByIdentity(id v1.Identity) *Source { + dig := id.Digest() + for i, res := range cd.Sources { + if bytes.Equal(res.GetIdentityDigest(cd.Sources), dig) { + return &cd.Sources[i] + } + } + return nil +} + +// GetSourcesByIdentitySelectors returns references that match the given selector. +// +// Deprecated: use GetSources with appropriate selectors. +func (cd *ComponentDescriptor) GetSourcesByIdentitySelectors(selectors ...IdentitySelector) (Sources, error) { + srcs := make(Sources, 0) + for _, src := range cd.Sources { + ok, err := selector.MatchSelectors(src.GetIdentity(cd.Sources), selectors...) + if err != nil { + return nil, fmt.Errorf("unable to match selector for source %s: %w", src.Name, err) + } + if ok { + srcs = append(srcs, src) + } + } + if len(srcs) == 0 { + return srcs, NotFound + } + return srcs, nil +} + +// GetSourcesByName returns all sources with a name. +// +// Deprecated: use GetSources with appropriate selectors. +func (cd *ComponentDescriptor) GetSourcesByName(name string, selectors ...IdentitySelector) (Sources, error) { + return cd.GetSourcesByIdentitySelectors( + sliceutils.CopyAppend[IdentitySelector](selectors, ByName(name))...) +} + +//////////////////////////////////////////////////////////////////////////////// + +// GetComponentReferences returns all component references that matches the given selectors. +// +// Deprectated: use GetReferences with appropriate selectors. +func (cd *ComponentDescriptor) GetComponentReferences(selectors ...IdentitySelector) ([]ComponentReference, error) { + refs := make([]ComponentReference, 0) + for _, ref := range cd.References { + ok, err := selector.MatchSelectors(ref.GetIdentity(cd.References), selectors...) + if err != nil { + return nil, fmt.Errorf("unable to match selector for resource %s: %w", ref.Name, err) + } + if ok { + refs = append(refs, ref) + } + } + if len(refs) == 0 { + return refs, NotFound + } + return refs, nil +} + +// GetComponentReferenceIndex returns the index of a given component reference. +// If the index is not found -1 is returned. +// Deprecated: use GetReferenceIndex. +func (cd *ComponentDescriptor) GetComponentReferenceIndex(ref ComponentReference) int { + return cd.GetReferenceIndex(ref.GetMeta()) +} + +// GetReferenceAccessByIdentity returns a pointer to the reference that matches the given identity. +// Deprectated: use GetReferenceByIdentity. +func (cd *ComponentDescriptor) GetReferenceAccessByIdentity(id v1.Identity) *ComponentReference { + dig := id.Digest() + for i, ref := range cd.References { + if bytes.Equal(ref.GetIdentityDigest(cd.Resources), dig) { + return &cd.References[i] + } + } + return nil +} + +// GetReferencesByIdentitySelectors returns resources that match the given identity selectors. +// Deprectated: use GetReferences with appropriate selectors. +func (cd *ComponentDescriptor) GetReferencesByIdentitySelectors(selectors ...IdentitySelector) (References, error) { + return cd.GetReferencesBySelectors(selectors, nil) +} + +// GetReferencesByReferenceSelectors returns resources that match the given resource selectors. +// Deprectated: use GetReferences with appropriate selectors. +func (cd *ComponentDescriptor) GetReferencesByReferenceSelectors(selectors ...ReferenceSelector) (References, error) { + return cd.GetReferencesBySelectors(nil, selectors) +} + +// GetReferencesBySelectors returns resources that match the given selector. +// Deprectated: use GetReferences with appropriate selectors. +func (cd *ComponentDescriptor) GetReferencesBySelectors(selectors []IdentitySelector, referenceSelectors []ReferenceSelector) (References, error) { + references := make(References, 0) + for i := range cd.References { + selctx := NewReferenceSelectionContext(i, cd.References) + if len(selectors) > 0 { + ok, err := selector.MatchSelectors(selctx.Identity(), selectors...) + if err != nil { + return nil, fmt.Errorf("unable to match selector for resource %s: %w", selctx.Name, err) + } + if !ok { + continue + } + } + ok, err := MatchReferencesByReferenceSelector(selctx, referenceSelectors...) + if err != nil { + return nil, fmt.Errorf("unable to match selector for resource %s: %w", selctx.Name, err) + } + if !ok { + continue + } + references = append(references, *selctx.ComponentReference) + } + if len(references) == 0 { + return references, NotFound + } + return references, nil +} + +// GetReferencesByName returns references that match the given name. +// Deprectated: use GetReferences with appropriate selectors. +func (cd *ComponentDescriptor) GetReferencesByName(name string, selectors ...IdentitySelector) (References, error) { + return cd.GetReferencesBySelectors( + sliceutils.CopyAppend[IdentitySelector](selectors, ByName(name)), + nil) +} diff --git a/pkg/contexts/ocm/compdesc/equal.go b/api/ocm/compdesc/equal.go similarity index 95% rename from pkg/contexts/ocm/compdesc/equal.go rename to api/ocm/compdesc/equal.go index ace1a2b16..9d6fb8f25 100644 --- a/pkg/contexts/ocm/compdesc/equal.go +++ b/api/ocm/compdesc/equal.go @@ -3,7 +3,7 @@ package compdesc import ( "reflect" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/equivalent" + "ocm.software/ocm/api/ocm/compdesc/equivalent" ) func (cd *ComponentDescriptor) Equal(obj interface{}) bool { diff --git a/pkg/contexts/ocm/compdesc/equal_test.go b/api/ocm/compdesc/equal_test.go similarity index 96% rename from pkg/contexts/ocm/compdesc/equal_test.go rename to api/ocm/compdesc/equal_test.go index be9b9f9f9..9007cb91e 100644 --- a/pkg/contexts/ocm/compdesc/equal_test.go +++ b/api/ocm/compdesc/equal_test.go @@ -3,14 +3,14 @@ package compdesc_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/equivalent/testhelper" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/none" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/equivalent" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + . "ocm.software/ocm/api/ocm/compdesc/equivalent/testhelper" + + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/compdesc/equivalent" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/none" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" ) var _ = Describe("equivalence", func() { diff --git a/pkg/contexts/ocm/compdesc/equivalent/equal.go b/api/ocm/compdesc/equivalent/equal.go similarity index 100% rename from pkg/contexts/ocm/compdesc/equivalent/equal.go rename to api/ocm/compdesc/equivalent/equal.go diff --git a/api/ocm/compdesc/equivalent/testhelper/helper.go b/api/ocm/compdesc/equivalent/testhelper/helper.go new file mode 100644 index 000000000..f680aba47 --- /dev/null +++ b/api/ocm/compdesc/equivalent/testhelper/helper.go @@ -0,0 +1,57 @@ +package testhelper + +import ( + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/ocm/compdesc/equivalent" +) + +func CheckEquivalent(eq equivalent.EqualState) { + ExpectWithOffset(1, eq).To(Equal(equivalent.StateEquivalent())) + + Expect(eq.IsEquivalent()).To(BeTrue()) + Expect(eq.IsHashEqual()).To(BeTrue()) + Expect(eq.IsLocalHashEqual()).To(BeTrue()) + Expect(eq.IsArtifactEqual()).To(BeTrue()) + Expect(eq.IsArtifactDetectable()).To(BeTrue()) +} + +func CheckNotEquivalent(eq equivalent.EqualState) { + ExpectWithOffset(1, eq).To(Equal(equivalent.StateNotEquivalent())) + + Expect(eq.IsEquivalent()).To(BeFalse()) + Expect(eq.IsHashEqual()).To(BeTrue()) + Expect(eq.IsLocalHashEqual()).To(BeTrue()) + Expect(eq.IsArtifactEqual()).To(BeTrue()) + Expect(eq.IsArtifactDetectable()).To(BeTrue()) +} + +func CheckNotLocalHashEqual(eq equivalent.EqualState) { + ExpectWithOffset(1, eq).To(Equal(equivalent.StateNotLocalHashEqual())) + + Expect(eq.IsEquivalent()).To(BeFalse()) + Expect(eq.IsHashEqual()).To(BeFalse()) + Expect(eq.IsLocalHashEqual()).To(BeFalse()) + Expect(eq.IsArtifactEqual()).To(BeTrue()) + Expect(eq.IsArtifactDetectable()).To(BeTrue()) +} + +func CheckNotDetectable(eq equivalent.EqualState) { + ExpectWithOffset(1, eq).To(Equal(equivalent.StateNotArtifactEqual(false))) + + Expect(eq.IsEquivalent()).To(BeFalse()) + Expect(eq.IsHashEqual()).To(BeFalse()) + Expect(eq.IsLocalHashEqual()).To(BeTrue()) + Expect(eq.IsArtifactEqual()).To(BeFalse()) + Expect(eq.IsArtifactDetectable()).To(BeFalse()) +} + +func CheckNotArtifactEqual(eq equivalent.EqualState) { + ExpectWithOffset(1, eq).To(Equal(equivalent.StateNotArtifactEqual(true))) + + Expect(eq.IsEquivalent()).To(BeFalse()) + Expect(eq.IsHashEqual()).To(BeFalse()) + Expect(eq.IsLocalHashEqual()).To(BeTrue()) + Expect(eq.IsArtifactEqual()).To(BeFalse()) + Expect(eq.IsArtifactDetectable()).To(BeTrue()) +} diff --git a/api/ocm/compdesc/helper.go b/api/ocm/compdesc/helper.go new file mode 100644 index 000000000..e6cc28fd5 --- /dev/null +++ b/api/ocm/compdesc/helper.go @@ -0,0 +1,239 @@ +package compdesc + +import ( + "bytes" + "fmt" + + "github.com/mandelsoft/goutils/errors" + + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/selectors" + "ocm.software/ocm/api/ocm/selectors/refsel" + "ocm.software/ocm/api/ocm/selectors/rscsel" + "ocm.software/ocm/api/ocm/selectors/srcsel" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/api/utils/selector" +) + +// GetEffectiveRepositoryContext returns the currently active repository context. +func (cd *ComponentDescriptor) GetEffectiveRepositoryContext() *runtime.UnstructuredTypedObject { + if len(cd.RepositoryContexts) == 0 { + return nil + } + return cd.RepositoryContexts[len(cd.RepositoryContexts)-1] +} + +// AddRepositoryContext appends the given repository context to components descriptor repository history. +// The context is not appended if the effective repository context already matches the current context. +func (cd *ComponentDescriptor) AddRepositoryContext(repoCtx runtime.TypedObject) error { + effective, err := runtime.ToUnstructuredTypedObject(cd.GetEffectiveRepositoryContext()) + if err != nil { + return err + } + uRepoCtx, err := runtime.ToUnstructuredTypedObject(repoCtx) + if err != nil { + return err + } + if !runtime.UnstructuredTypesEqual(effective, uRepoCtx) { + cd.RepositoryContexts = append(cd.RepositoryContexts, uRepoCtx) + } + return nil +} + +func (cd *ComponentDescriptor) SelectResources(sel ...rscsel.Selector) ([]Resource, error) { + err := selectors.ValidateSelectors(sel...) + if err != nil { + return nil, err + } + + list := MapToSelectorElementList(cd.Resources) + result := []Resource{} + for _, r := range cd.Resources { + if len(sel) > 0 { + mr := MapToSelectorResource(&r) + for _, s := range sel { + if !s.MatchResource(list, mr) { + continue + } + } + } + result = append(result, r) + } + return result, nil +} + +func (cd *ComponentDescriptor) GetResources() []Resource { + result := []Resource{} + for _, r := range cd.Resources { + result = append(result, r) + } + return result +} + +// GetResourceByIdentity returns resource that matches the given identity. +func (cd *ComponentDescriptor) GetResourceByIdentity(id v1.Identity) (Resource, error) { + dig := id.Digest() + for _, res := range cd.Resources { + if bytes.Equal(res.GetIdentityDigest(cd.Resources), dig) { + return res, nil + } + } + return Resource{}, NotFound +} + +// GetResourceIndexByIdentity returns the index of the resource that matches the given identity. +func (cd *ComponentDescriptor) GetResourceIndexByIdentity(id v1.Identity) int { + dig := id.Digest() + for i, res := range cd.Resources { + if bytes.Equal(res.GetIdentityDigest(cd.Resources), dig) { + return i + } + } + return -1 +} + +// GetResourceByJSONScheme returns resources that match the given selectors. +func (cd *ComponentDescriptor) GetResourceByJSONScheme(src interface{}) (Resources, error) { + sel, err := selector.NewJSONSchemaSelectorFromGoStruct(src) + if err != nil { + return nil, err + } + return cd.GetResourcesByIdentitySelectors(sel) +} + +// GetResourceByDefaultSelector returns resources that match the given selectors. +func (cd *ComponentDescriptor) GetResourceByDefaultSelector(sel interface{}) (Resources, error) { + identitySelector, err := selector.ParseDefaultSelector(sel) + if err != nil { + return nil, fmt.Errorf("unable to parse selector: %w", err) + } + return cd.GetResourcesByIdentitySelectors(identitySelector) +} + +// GetResourceIndex returns the index of a given resource. +// If the index is not found -1 is returned. +func (cd *ComponentDescriptor) GetResourceIndex(res *ResourceMeta) int { + return ElementIndex(cd.Resources, res) +} + +func (cd *ComponentDescriptor) SelectSources(sel ...srcsel.Selector) ([]Source, error) { + err := selectors.ValidateSelectors(sel...) + if err != nil { + return nil, err + } + + list := MapToSelectorElementList(cd.Sources) + result := []Source{} + for _, r := range cd.Sources { + if len(sel) > 0 { + mr := MapToSelectorSource(&r) + for _, s := range sel { + if !s.MatchSource(list, mr) { + continue + } + } + } + result = append(result, r) + } + return result, nil +} + +func (cd *ComponentDescriptor) GetSources() []Source { + result := []Source{} + for _, r := range cd.Sources { + result = append(result, r) + } + return result +} + +// GetSourceByIdentity returns source that match the given identity. +func (cd *ComponentDescriptor) GetSourceByIdentity(id v1.Identity) (Source, error) { + dig := id.Digest() + for _, res := range cd.Sources { + if bytes.Equal(res.GetIdentityDigest(cd.Sources), dig) { + return res, nil + } + } + return Source{}, NotFound +} + +// GetSourceIndexByIdentity returns the index of the source that matches the given identity. +func (cd *ComponentDescriptor) GetSourceIndexByIdentity(id v1.Identity) int { + dig := id.Digest() + for i, res := range cd.Sources { + if bytes.Equal(res.GetIdentityDigest(cd.Sources), dig) { + return i + } + } + return -1 +} + +// GetSourceIndex returns the index of a given source. +// If the index is not found -1 is returned. +func (cd *ComponentDescriptor) GetSourceIndex(src *SourceMeta) int { + return ElementIndex(cd.Sources, src) +} + +// GetReferenceByIdentity returns reference that matches the given identity. +func (cd *ComponentDescriptor) GetReferenceByIdentity(id v1.Identity) (ComponentReference, error) { + dig := id.Digest() + for _, ref := range cd.References { + if bytes.Equal(ref.GetIdentityDigest(cd.Resources), dig) { + return ref, nil + } + } + return ComponentReference{}, errors.ErrNotFound(KIND_REFERENCE, id.String()) +} + +func (cd *ComponentDescriptor) SelectReferences(sel ...refsel.Selector) ([]ComponentReference, error) { + err := selectors.ValidateSelectors(sel...) + if err != nil { + return nil, err + } + + list := MapToSelectorElementList(cd.References) + result := []ComponentReference{} + for _, r := range cd.References { + if len(sel) > 0 { + mr := MapToSelectorReference(&r) + for _, s := range sel { + if !s.MatchReference(list, mr) { + continue + } + } + } + result = append(result, r) + } + return result, nil +} + +func (cd *ComponentDescriptor) GetReferences() []ComponentReference { + result := []ComponentReference{} + for _, r := range cd.References { + result = append(result, r) + } + return result +} + +// GetReferenceIndexByIdentity returns the index of the reference that matches the given identity. +func (cd *ComponentDescriptor) GetReferenceIndexByIdentity(id v1.Identity) int { + dig := id.Digest() + for i, ref := range cd.References { + if bytes.Equal(ref.GetIdentityDigest(cd.Resources), dig) { + return i + } + } + return -1 +} + +// GetReferenceIndex returns the index of a given source. +// If the index is not found -1 is returned. +func (cd *ComponentDescriptor) GetReferenceIndex(src ElementMetaProvider) int { + return ElementIndex(cd.References, src) +} + +// GetSignatureIndex returns the index of the signature with the given name +// If the index is not found -1 is returned. +func (cd *ComponentDescriptor) GetSignatureIndex(name string) int { + return cd.Signatures.GetIndex(name) +} diff --git a/pkg/contexts/ocm/compdesc/helper_test.go b/api/ocm/compdesc/helper_test.go similarity index 98% rename from pkg/contexts/ocm/compdesc/helper_test.go rename to api/ocm/compdesc/helper_test.go index fecfa419b..e4385d42a 100644 --- a/pkg/contexts/ocm/compdesc/helper_test.go +++ b/api/ocm/compdesc/helper_test.go @@ -5,9 +5,9 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ocireg" + "ocm.software/ocm/api/ocm/compdesc" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" ) var _ = Describe("helper", func() { diff --git a/api/ocm/compdesc/init.go b/api/ocm/compdesc/init.go new file mode 100644 index 000000000..10ff40ebe --- /dev/null +++ b/api/ocm/compdesc/init.go @@ -0,0 +1,6 @@ +package compdesc + +import ( + _ "ocm.software/ocm/api/tech/signing/handlers" + _ "ocm.software/ocm/api/tech/signing/hasher" +) diff --git a/api/ocm/compdesc/logging.go b/api/ocm/compdesc/logging.go new file mode 100644 index 000000000..745cb40e0 --- /dev/null +++ b/api/ocm/compdesc/logging.go @@ -0,0 +1,10 @@ +package compdesc + +import ( + "ocm.software/ocm/api/utils/logging" +) + +var ( + REALM = logging.DefineSubRealm("component descriptor handling", "compdesc") + Logger = logging.DynamicLogger(REALM) +) diff --git a/pkg/contexts/ocm/compdesc/logging_test.go b/api/ocm/compdesc/logging_test.go similarity index 94% rename from pkg/contexts/ocm/compdesc/logging_test.go rename to api/ocm/compdesc/logging_test.go index 41f4ed214..068f2d6fd 100644 --- a/pkg/contexts/ocm/compdesc/logging_test.go +++ b/api/ocm/compdesc/logging_test.go @@ -11,8 +11,8 @@ import ( "github.com/mandelsoft/logging" "github.com/tonglil/buflogr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - ocmlog "github.com/open-component-model/ocm/pkg/logging" + "ocm.software/ocm/api/ocm/compdesc" + ocmlog "ocm.software/ocm/api/utils/logging" ) var _ = Describe("logging", func() { diff --git a/pkg/contexts/ocm/compdesc/meta.go b/api/ocm/compdesc/meta.go similarity index 94% rename from pkg/contexts/ocm/compdesc/meta.go rename to api/ocm/compdesc/meta.go index e98f6961d..387bccdc0 100644 --- a/pkg/contexts/ocm/compdesc/meta.go +++ b/api/ocm/compdesc/meta.go @@ -3,7 +3,7 @@ package compdesc import ( "time" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" ) type ( diff --git a/api/ocm/compdesc/meta/v1/identity.go b/api/ocm/compdesc/meta/v1/identity.go new file mode 100644 index 000000000..80ab79d00 --- /dev/null +++ b/api/ocm/compdesc/meta/v1/identity.go @@ -0,0 +1,164 @@ +package v1 + +import ( + "encoding/json" + "fmt" + "sort" + + "k8s.io/apimachinery/pkg/util/validation/field" + + "ocm.software/ocm/api/ocm/compdesc/equivalent" + "ocm.software/ocm/api/utils/logging" +) + +// Identity describes the identity of an object. +// Only ascii characters are allowed +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type Identity map[string]string + +func NewExtraIdentity(extras ...string) Identity { + if len(extras) == 0 { + return nil + } + id := Identity{} + i := 0 + for i < len(extras) { + if i+1 < len(extras) { + id[extras[i]] = extras[i+1] + } else { + id[extras[i]] = "" + } + i += 2 + } + return id +} + +// NewIdentity return a simple name identity. +func NewIdentity(name string, extras ...string) Identity { + id := NewExtraIdentity(extras...) + if id == nil { + return Identity{SystemIdentityName: name} + } + id[SystemIdentityName] = name + return id +} + +// Digest returns the object digest of an identity. +func (i Identity) Digest() []byte { + data, err := json.Marshal(i) + if err != nil { + logging.Logger().LogError(err, "corrupted digest") + } + + return data +} + +// Equals compares two identities. +func (i Identity) Equals(o Identity) bool { + if len(i) != len(o) { + return false + } + + for k, v := range i { + if v2, ok := o[k]; !ok || v != v2 { + return false + } + } + return true +} + +func (i Identity) Equivalent(o Identity) equivalent.EqualState { + if len(i) != len(o) { + return equivalent.StateNotLocalHashEqual() + } + + for k, v := range i { + if v2, ok := o[k]; !ok || v != v2 { + return equivalent.StateNotLocalHashEqual() + } + } + return equivalent.StateEquivalent() +} + +func (i *Identity) Set(name, value string) { + if *i == nil { + *i = Identity{name: value} + } else { + (*i)[name] = value + } +} + +func (i Identity) Get(name string) string { + if i != nil { + return i[name] + } + return "" +} + +func (i Identity) Remove(name string) bool { + if i != nil { + delete(i, name) + } + return false +} + +func (i Identity) String() string { + if i == nil { + return "" + } + + var keys []string + for k := range i { + keys = append(keys, k) + } + sort.Strings(keys) + s := "" + sep := "" + for _, k := range keys { + s = fmt.Sprintf("%s%s%q=%q", s, sep, k, i[k]) + sep = "," + } + return s +} + +// Match implements the selector interface. +func (i Identity) Match(obj map[string]string) (bool, error) { + for k, v := range i { + if obj[k] != v { + return false, nil + } + } + return true, nil +} + +// Copy copies identity. +func (i Identity) Copy() Identity { + if i == nil { + return nil + } + n := Identity{} + for k, v := range i { + n[k] = v + } + return n +} + +// ValidateIdentity validates the identity of object. +func ValidateIdentity(fldPath *field.Path, id Identity) field.ErrorList { + allErrs := field.ErrorList{} + + for key := range id { + if key == SystemIdentityName { + allErrs = append(allErrs, field.Forbidden(fldPath.Key(SystemIdentityName), "name is a reserved system identity label")) + } + + if !IsASCII(key) { + allErrs = append(allErrs, field.Forbidden(fldPath.Key(key), "key contains non-ascii characters")) + } + if !IsIdentity(key) { + allErrs = append(allErrs, field.Invalid(fldPath.Key(key), key, IdentityKeyValidationErrMsg)) + } + } + return allErrs +} diff --git a/api/ocm/compdesc/meta/v1/identity_test.go b/api/ocm/compdesc/meta/v1/identity_test.go new file mode 100644 index 000000000..b3ce4ebdb --- /dev/null +++ b/api/ocm/compdesc/meta/v1/identity_test.go @@ -0,0 +1,45 @@ +package v1_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/ocm/compdesc/equivalent/testhelper" + + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" +) + +var _ = Describe("identity", func() { + Context("equivalence", func() { + var a v1.Identity + var b v1.Identity + + BeforeEach(func() { + a = v1.NewIdentity("name", "extra", "extra") + b = a.Copy() + }) + + It("detects equal", func() { + CheckEquivalent(a.Equivalent(b)) + }) + + It("detects different value", func() { + b["name"] = "X" + CheckNotLocalHashEqual(a.Equivalent(b)) + CheckNotLocalHashEqual(b.Equivalent(a)) + }) + + It("detects additional attr", func() { + b["X"] = "X" + CheckNotLocalHashEqual(a.Equivalent(b)) + CheckNotLocalHashEqual(b.Equivalent(a)) + }) + + It("detects replaced attr", func() { + b["X"] = "extra" + delete(b, "extra") + Expect(len(a)).To(Equal(len(b))) + CheckNotLocalHashEqual(a.Equivalent(b)) + CheckNotLocalHashEqual(b.Equivalent(a)) + }) + }) +}) diff --git a/api/ocm/compdesc/meta/v1/labels.go b/api/ocm/compdesc/meta/v1/labels.go new file mode 100644 index 000000000..5aa6c74fb --- /dev/null +++ b/api/ocm/compdesc/meta/v1/labels.go @@ -0,0 +1,392 @@ +package v1 + +import ( + "encoding/json" + "reflect" + "regexp" + + "github.com/mandelsoft/goutils/errors" + "k8s.io/apimachinery/pkg/util/validation/field" + + "ocm.software/ocm/api/ocm/compdesc/equivalent" + "ocm.software/ocm/api/utils/listformat" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + KIND_LABEL = "label" + KIND_VALUE_MERGE_ALGORITHM = "label merge algorithm" +) + +type MergeAlgorithmSpecification struct { + // Algorithm optionally described the Merge algorithm used to + // merge the label value during a transfer. + Algorithm string `json:"algorithm"` + // eConfig contains optional config for the merge algorithm. + Config json.RawMessage `json:"config,omitempty"` +} + +var _ listformat.DirectDescriptionSource = (*MergeAlgorithmSpecification)(nil) + +func (s *MergeAlgorithmSpecification) Description() string { + return s.Algorithm +} + +func NewMergeAlgorithmSpecification(algo string, spec interface{}) (*MergeAlgorithmSpecification, error) { + m, err := runtime.AsRawMessage(spec) + if err != nil { + return nil, err + } + return &MergeAlgorithmSpecification{ + Algorithm: algo, + Config: m, + }, nil +} + +//////////////////////////////////////////////////////////////////////////////// + +// Label is a label that can be set on objects. +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type Label struct { + // Name is the unique name of the label. + Name string `json:"name"` + // Value is the json/yaml data of the label + // +kubebuilder:pruning:PreserveUnknownFields + // +kubebuilder:validation:Schemaless + Value json.RawMessage `json:"value"` + + // Version is the optional specification version of the attribute value + Version string `json:"version,omitempty"` + // Signing describes whether the label should be included into the signature + Signing bool `json:"signing,omitempty"` + + // MergeAlgorithm optionally describes the desired merge handling used to + // merge the label value during a transfer. + Merge *MergeAlgorithmSpecification `json:"merge,omitempty"` +} + +// DeepCopyInto copies labels. +func (in *Label) DeepCopyInto(out *Label) { + *out = *in + out.Value = append(out.Value[:0:0], in.Value...) +} + +// GetValue returns the label value with the given name as parsed object. +func (in *Label) GetValue(dest interface{}) error { + return json.Unmarshal(in.Value, dest) +} + +// SetValue sets the label value by marshalling the given object. +// A passed byte slice is validated to be valid json. +func (in *Label) SetValue(value interface{}) error { + var v runtime.RawValue + err := v.SetValue(value) + if err != nil { + return err + } + in.Value = v.RawMessage + return nil +} + +var versionRegex = regexp.MustCompile("^v[0-9]+$") + +func NewLabel(name string, value interface{}, opts ...LabelOption) (*Label, error) { + l := Label{Name: name} + err := l.SetValue(value) + if err != nil { + return nil, err + } + + for _, o := range opts { + if err := o.ApplyToLabel(&l); err != nil { + return nil, errors.Wrapf(err, "label %q", name) + } + } + return &l, nil +} + +// Labels describe a list of labels +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type Labels []Label + +// GetIndex returns the index of the given label or -1 if not found. +func (l Labels) GetIndex(name string) int { + for i, label := range l { + if label.Name == name { + return i + } + } + return -1 +} + +// GetDef returns the label definition of the given label. +func (l Labels) GetDef(name string) *Label { + for i, label := range l { + if label.Name == name { + return &l[i] + } + } + return nil +} + +// SetDef sets a label definition. +func (l *Labels) SetDef(name string, value *Label) { + n := *value + n.Name = name + for i, label := range *l { + if label.Name == name { + (*l)[i] = n + return + } + } + *l = append(*l, n) +} + +// Get returns the label value with the given name as json string. +func (l Labels) Get(name string) ([]byte, bool) { + for _, label := range l { + if label.Name == name { + return label.Value, true + } + } + return nil, false +} + +// GetValue returns the label value with the given name as parsed object. +func (l Labels) GetValue(name string, dest interface{}) (bool, error) { + for _, label := range l { + if label.Name == name { + return true, label.GetValue(dest) + } + } + return false, nil +} + +// Set sets or modifies a label including its meta data. +func (l *Labels) Set(name string, value interface{}, opts ...LabelOption) error { + newLabel, err := NewLabel(name, value, opts...) + if err != nil { + return err + } + for i, label := range *l { + if label.Name == name { + (*l)[i] = *newLabel + return nil + } + } + *l = append(*l, *newLabel) + return nil +} + +// Set sets or modifies the label meta data. +func (l *Labels) SetOptions(name string, opts ...LabelOption) error { + newLabel, err := NewLabel(name, nil, opts...) + if err != nil { + return err + } + for i, label := range *l { + if label.Name == name { + newLabel.Value = label.Value + (*l)[i] = *newLabel + return nil + } + } + return errors.ErrNotFound(KIND_LABEL, name) +} + +// SetValue sets or modifies the value of a label, the label metadata +// is not touched. +func (l *Labels) SetValue(name string, value interface{}) error { + newLabel, err := NewLabel(name, value) + if err != nil { + return err + } + for i, label := range *l { + if label.Name == name { + (*l)[i].Value = newLabel.Value + return nil + } + } + *l = append(*l, *newLabel) + return nil +} + +func (l *Labels) Remove(name string) bool { + for i, label := range *l { + if label.Name == name { + *l = append((*l)[:i], (*l)[i+1:]...) + return true + } + } + return false +} + +func (l *Labels) Clear() { + *l = nil +} + +func (l Labels) Equivalent(o Labels) equivalent.EqualState { + state := equivalent.StateEquivalent() + + for _, ol := range o { + ll := l.GetDef(ol.Name) + if ol.Signing { + if ll == nil || !reflect.DeepEqual(&ol, ll) { + state = state.NotLocalHashEqual() + } + } else { + if ll != nil { + if ll.Signing { + state = state.NotLocalHashEqual() + } + if !reflect.DeepEqual(&ol, ll) { + state = state.NotEquivalent() + } + } else { + state = state.NotEquivalent() + } + } + } + for _, ll := range l { + ol := o.GetDef(ll.Name) + if ol == nil { + if ll.Signing { + state = state.NotLocalHashEqual() + } + state = state.NotEquivalent() + } + } + return state +} + +// AsMap return an unmarshalled map representation. +func (l *Labels) AsMap() map[string]interface{} { + labels := map[string]interface{}{} + if l != nil { + for _, label := range *l { + var m interface{} + json.Unmarshal(label.Value, &m) + labels[label.Name] = m + } + } + return labels +} + +// Copy copies labels. +func (l Labels) Copy() Labels { + if l == nil { + return nil + } + n := make(Labels, len(l)) + copy(n, l) + return n +} + +// ValidateLabels validates a list of labels. +func ValidateLabels(fldPath *field.Path, labels Labels) field.ErrorList { + allErrs := field.ErrorList{} + labelNames := make(map[string]struct{}) + for i, label := range labels { + labelPath := fldPath.Index(i) + if len(label.Name) == 0 { + allErrs = append(allErrs, field.Required(labelPath.Child("name"), "must specify a name")) + continue + } + + if _, ok := labelNames[label.Name]; ok { + allErrs = append(allErrs, field.Duplicate(labelPath, "duplicate label name")) + continue + } + labelNames[label.Name] = struct{}{} + } + return allErrs +} + +type LabelOption interface { + ApplyToLabel(l *Label) error +} + +type labelOptVersion struct { + version string +} + +var _ LabelOption = (*labelOptVersion)(nil) + +func WithVersion(v string) LabelOption { + return &labelOptVersion{v} +} + +func CheckLabelVersion(v string) bool { + return versionRegex.MatchString(v) +} + +func (o labelOptVersion) ApplyToLabel(l *Label) error { + if !CheckLabelVersion(o.version) { + return errors.ErrInvalid("version", o.version) + } + l.Version = o.version + return nil +} + +//////////////////////////////////////////////////////////////////////////////// + +type labelOptSigning struct { + sign bool +} + +var _ LabelOption = (*labelOptSigning)(nil) + +func WithSigning(b ...bool) LabelOption { + s := true + for _, o := range b { + s = o + } + return &labelOptSigning{s} +} + +func (o *labelOptSigning) ApplyToLabel(l *Label) error { + l.Signing = o.sign + return nil +} + +//////////////////////////////////////////////////////////////////////////////// + +// LabelMergeHandlerConfig must be label merge handler config. but cannot be checked +// because of cyclic package dependencies. +type LabelMergeHandlerConfig interface{} + +type labelOptMerge struct { + cfg json.RawMessage + algo string +} + +var _ LabelOption = (*labelOptMerge)(nil) + +func WithMerging(algo string, cfg LabelMergeHandlerConfig) LabelOption { + var data []byte + if cfg != nil { + var err error + data, err = json.Marshal(cfg) + if err != nil { + return nil + } + } + return &labelOptMerge{algo: algo, cfg: data} +} + +func (o *labelOptMerge) ApplyToLabel(l *Label) error { + if o.algo != "" || len(o.cfg) > 0 { + l.Merge = &MergeAlgorithmSpecification{} + if o.algo != "" { + l.Merge.Algorithm = o.algo + } + if len(o.cfg) > 0 { + l.Merge.Config = o.cfg + } + } else { + l.Merge = nil + } + return nil +} diff --git a/pkg/contexts/ocm/compdesc/meta/v1/labels_test.go b/api/ocm/compdesc/meta/v1/labels_test.go similarity index 96% rename from pkg/contexts/ocm/compdesc/meta/v1/labels_test.go rename to api/ocm/compdesc/meta/v1/labels_test.go index 966d14ac6..5f59a48fc 100644 --- a/pkg/contexts/ocm/compdesc/meta/v1/labels_test.go +++ b/api/ocm/compdesc/meta/v1/labels_test.go @@ -4,9 +4,9 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/equivalent/testhelper" + . "ocm.software/ocm/api/ocm/compdesc/equivalent/testhelper" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" ) var _ = Describe("labels", func() { diff --git a/pkg/contexts/ocm/compdesc/meta/v1/resourceref.go b/api/ocm/compdesc/meta/v1/resourceref.go similarity index 100% rename from pkg/contexts/ocm/compdesc/meta/v1/resourceref.go rename to api/ocm/compdesc/meta/v1/resourceref.go diff --git a/pkg/contexts/ocm/compdesc/meta/v1/signature.go b/api/ocm/compdesc/meta/v1/signature.go similarity index 98% rename from pkg/contexts/ocm/compdesc/meta/v1/signature.go rename to api/ocm/compdesc/meta/v1/signature.go index 734a9ba38..a2b2a7a03 100644 --- a/pkg/contexts/ocm/compdesc/meta/v1/signature.go +++ b/api/ocm/compdesc/meta/v1/signature.go @@ -5,8 +5,8 @@ import ( "reflect" "strings" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/equivalent" - "github.com/open-component-model/ocm/pkg/signing" + "ocm.software/ocm/api/ocm/compdesc/equivalent" + "ocm.software/ocm/api/tech/signing" ) const ( diff --git a/pkg/contexts/ocm/compdesc/meta/v1/stringmap.go b/api/ocm/compdesc/meta/v1/stringmap.go similarity index 100% rename from pkg/contexts/ocm/compdesc/meta/v1/stringmap.go rename to api/ocm/compdesc/meta/v1/stringmap.go diff --git a/pkg/contexts/ocm/compdesc/meta/v1/suite_test.go b/api/ocm/compdesc/meta/v1/suite_test.go similarity index 100% rename from pkg/contexts/ocm/compdesc/meta/v1/suite_test.go rename to api/ocm/compdesc/meta/v1/suite_test.go diff --git a/api/ocm/compdesc/meta/v1/types.go b/api/ocm/compdesc/meta/v1/types.go new file mode 100644 index 000000000..8b4fbde27 --- /dev/null +++ b/api/ocm/compdesc/meta/v1/types.go @@ -0,0 +1,276 @@ +package v1 + +import ( + "reflect" + "time" + + "github.com/mandelsoft/goutils/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + + "ocm.software/ocm/api/ocm/compdesc/equivalent" +) + +// These constants describe identity attributes predefined by the +// model used to identify elements (resources, sources and references) +// in a component version. +const ( + // SystemIdentityName is the name attribute of an element in + // a component version. It is always present. + SystemIdentityName = "name" + // SystemIdentityVersion is the version attribute optionally + // added to the identity of an element in a component version. + // It is required, if the name and the other explicitly defined + // extra identity attributes are not unique for a dedicated + // kind of element in the context of a component version. + SystemIdentityVersion = "version" +) + +// Metadata defines the metadata of the component descriptor. +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type Metadata struct { + // Version is the schema version of the component descriptor. + Version string `json:"schemaVersion"` +} + +// ProviderName describes the provider type of component in the origin's context. +// Defines whether the component is created by a third party or internally. +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type ProviderName string + +// ResourceRelation describes the type of a resource. +// Defines whether the component is created by a third party or internally. +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type ResourceRelation string + +const ( + // LocalRelation defines a internal relation + // which describes a internally maintained resource in the origin's context. + LocalRelation ResourceRelation = "local" + // ExternalRelation defines a external relation + // which describes a resource maintained by a third party vendor in the origin's context. + ExternalRelation ResourceRelation = "external" +) + +func ValidateRelation(fldPath *field.Path, relation ResourceRelation) *field.Error { + if len(relation) == 0 { + return field.Required(fldPath, "relation must be set") + } + if relation != LocalRelation && relation != ExternalRelation { + return field.NotSupported(fldPath, relation, []string{string(LocalRelation), string(ExternalRelation)}) + } + return nil +} + +const ( + GROUP = "ocm.software" + KIND = "ComponentVersion" +) + +// TypeMeta describes the schema of a descriptor. +type TypeMeta struct { + APIVersion string `json:"apiVersion"` + Kind string `json:"kind"` +} + +// ObjectMeta defines the metadata of the component descriptor. +type ObjectMeta struct { + // Name is the name of the component. + Name string `json:"name"` + // Version is the version of the component. + Version string `json:"version"` + // Labels describe additional properties of the component version + Labels Labels `json:"labels,omitempty"` + // Provider described the component provider + Provider Provider `json:"provider"` + // CreationTime is the creation time of component version + // +optional + CreationTime *Timestamp `json:"creationTime,omitempty"` +} + +func (o *ObjectMeta) Equal(obj interface{}) bool { + if e, ok := obj.(*ObjectMeta); ok { + if o.Name == e.Name && + o.Version == e.Version && + reflect.DeepEqual(o.Provider, e.Provider) && + reflect.DeepEqual(o.Labels, e.Labels) { + return true + } + // check Creation time ? + } + return false +} + +func (o ObjectMeta) Equivalent(a ObjectMeta) equivalent.EqualState { + state := equivalent.StateLocalHashEqual(o.Name == a.Name && o.Version == a.Version) + return state.Apply( + o.Provider.Equivalent(a.Provider), + o.Labels.Equivalent(a.Labels), + ) +} + +// GetName returns the name of the object. +func (o *ObjectMeta) GetName() string { + return o.Name +} + +// SetName sets the name of the object. +func (o *ObjectMeta) SetName(name string) { + o.Name = name +} + +// GetVersion returns the version of the object. +func (o ObjectMeta) GetVersion() string { + return o.Version +} + +// SetVersion sets the version of the object. +func (o *ObjectMeta) SetVersion(version string) { + o.Version = version +} + +// GetLabels returns the label of the object. +func (o ObjectMeta) GetLabels() Labels { + return o.Labels +} + +// SetLabels sets the labels of the object. +func (o *ObjectMeta) SetLabels(labels []Label) { + o.Labels = labels +} + +// GetName returns the name of the object. +func (o *ObjectMeta) Copy() *ObjectMeta { + return &ObjectMeta{ + Name: o.Name, + Version: o.Version, + Labels: o.Labels.Copy(), + Provider: *o.Provider.Copy(), + CreationTime: o.CreationTime.DeepCopy(), + } +} + +//////////////////////////////////////////////////////////////////////////////// + +// Provider describes the provider information of a component version. +type Provider struct { + Name ProviderName `json:"name"` + // Labels describe additional properties of provider + Labels Labels `json:"labels,omitempty"` +} + +// GetName returns the name of the provider. +func (o Provider) GetName() ProviderName { + return o.Name +} + +// SetName sets the name of the provider. +func (o *Provider) SetName(name ProviderName) { + o.Name = name +} + +// GetLabels returns the label of the provider. +func (o Provider) GetLabels() Labels { + return o.Labels +} + +// SetLabels sets the labels of the provider. +func (o *Provider) SetLabels(labels []Label) { + o.Labels = labels +} + +// Copy copies the provider info. +func (o *Provider) Copy() *Provider { + return &Provider{ + Name: o.Name, + Labels: o.Labels.Copy(), + } +} + +func (o Provider) Equivalent(a Provider) equivalent.EqualState { + state := equivalent.StateLocalHashEqual(o.Name == a.Name) + return state.Apply(o.Labels.Equivalent(a.Labels)) +} + +//////////////////////////////////////////////////////////////////////////////// + +type _time = v1.Time + +// Timestamp is time rounded to seconds. +// +k8s:deepcopy-gen=true +type Timestamp struct { + _time `json:",inline"` +} + +func NewTimestamp() Timestamp { + return Timestamp{ + _time: v1.NewTime(time.Now().UTC().Round(time.Second)), + } +} + +func NewTimestampP() *Timestamp { + return &Timestamp{ + _time: v1.NewTime(time.Now().UTC().Round(time.Second)), + } +} + +func NewTimestampFor(t time.Time) Timestamp { + return Timestamp{ + _time: v1.NewTime(t.UTC().Round(time.Second)), + } +} + +func NewTimestampPFor(t time.Time) *Timestamp { + return &Timestamp{ + _time: v1.NewTime(t.UTC().Round(time.Second)), + } +} + +// MarshalJSON implements the json.Marshaler interface. +// The time is a quoted string in RFC 3339 format, with sub-second precision added if present. +func (t Timestamp) MarshalJSON() ([]byte, error) { + if y := t.Year(); y < 0 || y >= 10000 { + // RFC 3339 is clear that years are 4 digits exactly. + // See golang.org/issue/4556#c15 for more discussion. + return nil, errors.New("Time.MarshalJSON: year outside of range [0,9999]") + } + + b := make([]byte, 0, len(time.RFC3339)+2) + b = append(b, '"') + b = t.AppendFormat(b, time.RFC3339) + b = append(b, '"') + return b, nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +// The time is expected to be a quoted string in RFC 3339 format. +func (t *Timestamp) UnmarshalJSON(data []byte) error { + // Ignore null, like in the main JSON package. + if string(data) == "null" { + return nil + } + + // Fractional seconds are handled implicitly by Parse. + tt, err := time.Parse(`"`+time.RFC3339+`"`, string(data)) + *t = NewTimestampFor(tt) + return err +} + +func (t Timestamp) String() string { + return t.Format(time.RFC3339) +} + +func (t *Timestamp) Time() time.Time { + return t._time.Time +} + +func (t *Timestamp) Equal(o Timestamp) bool { + return t._time.Equal(&o._time) +} + +func (t *Timestamp) Add(d time.Duration) Timestamp { + return NewTimestampFor(t._time.Add(d)) +} diff --git a/pkg/contexts/ocm/compdesc/meta/v1/types_test.go b/api/ocm/compdesc/meta/v1/types_test.go similarity index 94% rename from pkg/contexts/ocm/compdesc/meta/v1/types_test.go rename to api/ocm/compdesc/meta/v1/types_test.go index 1693b79c0..0523385e0 100644 --- a/pkg/contexts/ocm/compdesc/meta/v1/types_test.go +++ b/api/ocm/compdesc/meta/v1/types_test.go @@ -4,9 +4,9 @@ import ( "time" . "github.com/onsi/ginkgo/v2" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/equivalent/testhelper" + . "ocm.software/ocm/api/ocm/compdesc/equivalent/testhelper" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" ) var _ = Describe("types", func() { diff --git a/pkg/contexts/ocm/compdesc/meta/v1/utils.go b/api/ocm/compdesc/meta/v1/utils.go similarity index 100% rename from pkg/contexts/ocm/compdesc/meta/v1/utils.go rename to api/ocm/compdesc/meta/v1/utils.go diff --git a/pkg/contexts/ocm/compdesc/meta/v1/zz_generated.deepcopy.go b/api/ocm/compdesc/meta/v1/zz_generated.deepcopy.go similarity index 100% rename from pkg/contexts/ocm/compdesc/meta/v1/zz_generated.deepcopy.go rename to api/ocm/compdesc/meta/v1/zz_generated.deepcopy.go diff --git a/pkg/contexts/ocm/compdesc/none.go b/api/ocm/compdesc/none.go similarity index 100% rename from pkg/contexts/ocm/compdesc/none.go rename to api/ocm/compdesc/none.go diff --git a/pkg/contexts/ocm/compdesc/norm_test.go b/api/ocm/compdesc/norm_test.go similarity index 96% rename from pkg/contexts/ocm/compdesc/norm_test.go rename to api/ocm/compdesc/norm_test.go index a595d5bb8..8d3991f07 100644 --- a/pkg/contexts/ocm/compdesc/norm_test.go +++ b/api/ocm/compdesc/norm_test.go @@ -1,17 +1,17 @@ package compdesc_test import ( - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/normalizations" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/versions" + _ "ocm.software/ocm/api/ocm/compdesc/normalizations" + _ "ocm.software/ocm/api/ocm/compdesc/versions" . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/none" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1" + "ocm.software/ocm/api/ocm/compdesc" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/compdesc/versions/ocm.software/v3alpha1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/none" ) var CD1 = ` diff --git a/pkg/contexts/ocm/compdesc/normalization.go b/api/ocm/compdesc/normalization.go similarity index 100% rename from pkg/contexts/ocm/compdesc/normalization.go rename to api/ocm/compdesc/normalization.go diff --git a/api/ocm/compdesc/normalizations/init.go b/api/ocm/compdesc/normalizations/init.go new file mode 100644 index 000000000..e433dca7c --- /dev/null +++ b/api/ocm/compdesc/normalizations/init.go @@ -0,0 +1,6 @@ +package versions + +import ( + _ "ocm.software/ocm/api/ocm/compdesc/normalizations/jsonv1" + _ "ocm.software/ocm/api/ocm/compdesc/normalizations/jsonv2" +) diff --git a/api/ocm/compdesc/normalizations/jsonv1/norm.go b/api/ocm/compdesc/normalizations/jsonv1/norm.go new file mode 100644 index 000000000..5a6f9bf14 --- /dev/null +++ b/api/ocm/compdesc/normalizations/jsonv1/norm.go @@ -0,0 +1,34 @@ +// Package jsonv1 provides a normalization which uses schema specific +// normalizations. +// It creates the requested schema for the component descriptor +// and just forwards the normalization to this version. +package jsonv1 + +import ( + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/utils/errkind" +) + +const Algorithm = compdesc.JsonNormalisationV1 + +func init() { + compdesc.Normalizations.Register(Algorithm, normalization{}) +} + +type normalization struct{} + +func (m normalization) Normalize(cd *compdesc.ComponentDescriptor) ([]byte, error) { + cv := compdesc.DefaultSchemes[cd.SchemaVersion()] + if cv == nil { + if cv == nil { + return nil, errors.ErrNotSupported(errkind.KIND_SCHEMAVERSION, cd.SchemaVersion()) + } + } + v, err := cv.ConvertFrom(cd) + if err != nil { + return nil, err + } + return v.Normalize(Algorithm) +} diff --git a/api/ocm/compdesc/normalizations/jsonv2/norm.go b/api/ocm/compdesc/normalizations/jsonv2/norm.go new file mode 100644 index 000000000..1fcb3a98e --- /dev/null +++ b/api/ocm/compdesc/normalizations/jsonv2/norm.go @@ -0,0 +1,64 @@ +// Package jsonv2 provides a normalization which is completely based on the +// abstract (internal) version of the component descriptor and is therefore +// agnostic of the final serialization format. Signatures using this algorithm +// can be transferred among different schema versions, as long as is able to +// handle the complete information using for the normalization. +// Older format might omit some info, therefore the signatures cannot be +// validated for such representations, if the original component descriptor +// has used such parts. +package jsonv2 + +import ( + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/compdesc/normalizations/rules" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/norm/jcs" +) + +const Algorithm = compdesc.JsonNormalisationV2 + +func init() { + compdesc.Normalizations.Register(Algorithm, normalization{}) +} + +type normalization struct{} + +func (m normalization) Normalize(cd *compdesc.ComponentDescriptor) ([]byte, error) { + data, err := signing.Normalize(jcs.Type, cd, CDExcludes) + return data, err +} + +// CDExcludes describes the fields relevant for Signing +// ATTENTION: if changed, please adapt the Equivalent Functions +// in the generic part, accordingly. +var CDExcludes = signing.MapExcludes{ + "meta": nil, + "component": signing.MapExcludes{ + "repositoryContexts": nil, + "provider": signing.MapExcludes{ + "labels": rules.LabelExcludes, + }, + "labels": rules.LabelExcludes, + "resources": signing.DynamicArrayExcludes{ + ValueMapper: rules.MapResourcesWithNoneAccess, + Continue: signing.MapExcludes{ + "access": nil, + "srcRefs": nil, + "labels": rules.LabelExcludes, + }, + }, + "sources": signing.ArrayExcludes{ + Continue: signing.MapExcludes{ + "access": nil, + "labels": rules.LabelExcludes, + }, + }, + "references": signing.ArrayExcludes{ + signing.MapExcludes{ + "labels": rules.LabelExcludes, + }, + }, + }, + "signatures": nil, + "nestedDigests": nil, +} diff --git a/pkg/contexts/ocm/compdesc/normalizations/rules/compdescrules.go b/api/ocm/compdesc/normalizations/rules/compdescrules.go similarity index 94% rename from pkg/contexts/ocm/compdesc/normalizations/rules/compdescrules.go rename to api/ocm/compdesc/normalizations/rules/compdescrules.go index 75253e0da..eac5b29f7 100644 --- a/pkg/contexts/ocm/compdesc/normalizations/rules/compdescrules.go +++ b/api/ocm/compdesc/normalizations/rules/compdescrules.go @@ -1,8 +1,8 @@ package rules import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - "github.com/open-component-model/ocm/pkg/signing" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/tech/signing" ) func IgnoreLabelsWithoutSignature(v interface{}) bool { diff --git a/pkg/contexts/ocm/compdesc/op.go b/api/ocm/compdesc/op.go similarity index 100% rename from pkg/contexts/ocm/compdesc/op.go rename to api/ocm/compdesc/op.go diff --git a/pkg/contexts/ocm/compdesc/schemes.go b/api/ocm/compdesc/schemes.go similarity index 97% rename from pkg/contexts/ocm/compdesc/schemes.go rename to api/ocm/compdesc/schemes.go index 10f3865ee..5596080f7 100644 --- a/pkg/contexts/ocm/compdesc/schemes.go +++ b/api/ocm/compdesc/schemes.go @@ -5,8 +5,8 @@ import ( "github.com/mandelsoft/goutils/errors" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/errkind" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/utils/errkind" ) const DefaultSchemeVersion = "v2" diff --git a/pkg/contexts/ocm/compdesc/selector.go b/api/ocm/compdesc/selector.go similarity index 94% rename from pkg/contexts/ocm/compdesc/selector.go rename to api/ocm/compdesc/selector.go index 578ccde40..568f67bbb 100644 --- a/pkg/contexts/ocm/compdesc/selector.go +++ b/api/ocm/compdesc/selector.go @@ -3,7 +3,7 @@ package compdesc import ( "github.com/mandelsoft/goutils/generics" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/accessors" + "ocm.software/ocm/api/ocm/selectors/accessors" ) type elemList struct { diff --git a/pkg/contexts/ocm/compdesc/selectors.go b/api/ocm/compdesc/selectors.go similarity index 98% rename from pkg/contexts/ocm/compdesc/selectors.go rename to api/ocm/compdesc/selectors.go index c3a96438f..ce3ec3776 100644 --- a/pkg/contexts/ocm/compdesc/selectors.go +++ b/api/ocm/compdesc/selectors.go @@ -5,11 +5,11 @@ import ( "reflect" "runtime" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/extraid" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/utils/selector" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extraid" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/selector" ) // Deprecated: use package selectors and its sub packages. diff --git a/api/ocm/compdesc/signing.go b/api/ocm/compdesc/signing.go new file mode 100644 index 000000000..3afdad5c9 --- /dev/null +++ b/api/ocm/compdesc/signing.go @@ -0,0 +1,246 @@ +package compdesc + +import ( + "encoding/hex" + "fmt" + "hash" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/general" + + "ocm.software/ocm/api/credentials" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/signutils" +) + +const ( + KIND_HASH_ALGORITHM = signutils.KIND_HASH_ALGORITHM + KIND_SIGN_ALGORITHM = signutils.KIND_SIGN_ALGORITHM + KIND_NORM_ALGORITHM = signutils.KIND_NORM_ALGORITHM + KIND_VERIFY_ALGORITHM = signutils.KIND_VERIFY_ALGORITHM + KIND_PUBLIC_KEY = signutils.KIND_PUBLIC_KEY + KIND_PRIVATE_KEY = signutils.KIND_PRIVATE_KEY + KIND_SIGNATURE = signutils.KIND_SIGNATURE + KIND_DIGEST = signutils.KIND_DIGEST +) + +// IsNormalizeable checks if componentReferences and resources contain digest. +// Resources are allowed to omit the digest, if res.access.type == None or res.access == nil. +// Does NOT verify if the digests are correct. +func (cd *ComponentDescriptor) IsNormalizeable() error { + // check for digests on component references + for _, reference := range cd.References { + if reference.Digest == nil || reference.Digest.HashAlgorithm == "" || reference.Digest.NormalisationAlgorithm == "" || reference.Digest.Value == "" { + return fmt.Errorf("missing digest in componentReference for %s:%s", reference.Name, reference.Version) + } + } + for _, res := range cd.Resources { + if (res.Access != nil && res.Access.GetType() != "None") && res.Digest == nil { + return fmt.Errorf("missing digest in resource for %s:%s", res.Name, res.Version) + } + if (res.Access == nil || res.Access.GetType() == "None") && res.Digest != nil { + return fmt.Errorf("digest for resource with empty (None) access not allowed %s:%s", res.Name, res.Version) + } + } + return nil +} + +// Hash return the hash for the component-descriptor, if it is normalizeable +// (= componentReferences and resources contain digest field). +func Hash(cd *ComponentDescriptor, normAlgo string, hash hash.Hash) (string, error) { + if hash == nil { + return metav1.NoDigest, nil + } + + normalized, err := Normalize(cd, normAlgo) + if err != nil { + return "", fmt.Errorf("failed normalising component descriptor %w", err) + } + // fmt.Printf("NORM %s:%s: %s\n", cd.Name, cd.Version, string(normalized)) + hash.Reset() + if _, err = hash.Write(normalized); err != nil { + return "", fmt.Errorf("failed hashing the normalisedComponentDescriptorJson: %w", err) + } + + return hex.EncodeToString(hash.Sum(nil)), nil +} + +type CompDescDigest struct { + normAlgo string + hashAlgo string + normalized []byte + digest string +} + +type CompDescDigests struct { + cd *ComponentDescriptor + digests []*CompDescDigest +} + +func NewCompDescDigests(cd *ComponentDescriptor) *CompDescDigests { + return &CompDescDigests{ + cd: cd, + } +} + +func (d *CompDescDigests) Descriptor() *ComponentDescriptor { + return d.cd +} + +func (d *CompDescDigests) Get(normAlgo string, hasher signing.Hasher) ([]byte, string, error) { + var normalized []byte + + for _, e := range d.digests { + if e.normAlgo == normAlgo { + normalized = e.normalized + if e.hashAlgo == hasher.Algorithm() { + return e.normalized, e.digest, nil + } + } + } + + var err error + if normalized == nil { + normalized, err = Normalize(d.cd, normAlgo) + if err != nil { + return nil, "", fmt.Errorf("failed normalising component descriptor %w", err) + } + } + hash := hasher.Create() + if _, err = hash.Write(normalized); err != nil { + return nil, "", fmt.Errorf("failed hashing the normalisedComponentDescriptorJson: %w", err) + } + + e := &CompDescDigest{ + normAlgo: normAlgo, + hashAlgo: hasher.Algorithm(), + normalized: normalized, + digest: hex.EncodeToString(hash.Sum(nil)), + } + d.digests = append(d.digests, e) + return normalized, e.digest, nil +} + +func NormHash(cd *ComponentDescriptor, normAlgo string, hash hash.Hash) ([]byte, string, error) { + if hash == nil { + return nil, metav1.NoDigest, nil + } + + normalized, err := Normalize(cd, normAlgo) + if err != nil { + return nil, "", fmt.Errorf("failed normalising component descriptor %w", err) + } + hash.Reset() + if _, err = hash.Write(normalized); err != nil { + return nil, "", fmt.Errorf("failed hashing the normalisedComponentDescriptorJson: %w", err) + } + + return normalized, hex.EncodeToString(hash.Sum(nil)), nil +} + +// Sign signs the given component-descriptor with the signer. +// The component-descriptor has to contain digests for componentReferences and resources. +func Sign(cctx credentials.Context, cd *ComponentDescriptor, privateKey signutils.GenericPrivateKey, signer signing.Signer, hasher signing.Hasher, signatureName, issuer string) error { + digest, err := Hash(cd, JsonNormalisationV1, hasher.Create()) + if err != nil { + return fmt.Errorf("failed getting hash for cd: %w", err) + } + + iss, err := signutils.ParseDN(issuer) + if err != nil { + return err + } + + sctx := &signing.DefaultSigningContext{ + Hash: hasher.Crypto(), + PrivateKey: privateKey, + PublicKey: nil, + RootCerts: nil, + Issuer: iss, + } + + signature, err := signer.Sign(cctx, digest, sctx) + if err != nil { + return fmt.Errorf("failed signing hash of normalised component descriptor, %w", err) + } + signature.Issuer = issuer + cd.Signatures = append(cd.Signatures, metav1.Signature{ + Name: signatureName, + Digest: metav1.DigestSpec{ + HashAlgorithm: hasher.Algorithm(), + NormalisationAlgorithm: JsonNormalisationV1, + Value: digest, + }, + Signature: metav1.SignatureSpec{ + Algorithm: signature.Algorithm, + Value: signature.Value, + MediaType: signature.MediaType, + Issuer: signature.Issuer, + }, + }) + return nil +} + +// Verify verifies the signature (selected by signatureName) and hash of the component-descriptor (as specified in the signature). +// Does NOT resolve resources or referenced component-descriptors. +// Returns error if verification fails. +func Verify(cd *ComponentDescriptor, registry signing.Registry, signatureName string, rootCA ...signutils.GenericCertificatePool) error { + // find matching signature + matchingSignature := cd.SelectSignatureByName(signatureName) + if matchingSignature == nil { + return errors.ErrNotFound(KIND_SIGNATURE, signatureName) + } + verifier := registry.GetVerifier(matchingSignature.Signature.Algorithm) + if verifier == nil { + return errors.ErrUnknown(KIND_SIGN_ALGORITHM, matchingSignature.Signature.Algorithm) + } + publicKey := registry.GetPublicKey(signatureName) + if publicKey == nil { + return errors.ErrNotFound(KIND_PUBLIC_KEY, signatureName) + } + hasher := registry.GetHasher(matchingSignature.Digest.HashAlgorithm) + if hasher == nil { + return errors.ErrUnknown(KIND_HASH_ALGORITHM, matchingSignature.Digest.HashAlgorithm) + } + // Verify author of signature + sctx := &signing.DefaultSigningContext{ + Hash: hasher.Crypto(), + PublicKey: publicKey, + RootCerts: general.Optional(rootCA), + Issuer: registry.GetIssuer(signatureName), + } + err := verifier.Verify(matchingSignature.Digest.Value, matchingSignature.ConvertToSigning(), sctx) + if err != nil { + return fmt.Errorf("failed verifying: %w", err) + } + + hash := hasher.Create() + // Verify normalised cd to given (and verified) hash + calculatedDigest, err := Hash(cd, matchingSignature.Digest.NormalisationAlgorithm, hash) + if err != nil { + return fmt.Errorf("failed hashing cd %s:%s: %w", cd.Name, cd.Version, err) + } + + if calculatedDigest != matchingSignature.Digest.Value { + return fmt.Errorf("normalised component-descriptor does not match hash from signature") + } + + return nil +} + +// SelectSignatureByName returns the Signature (Digest and SigantureSpec) matching the given name. +func (cd *ComponentDescriptor) SelectSignatureByName(signatureName string) *metav1.Signature { + for _, signature := range cd.Signatures { + if signature.Name == signatureName { + return &signature + } + } + return nil +} + +//////////////////////////////////////////////////////////////////////////////// + +func (cd *ComponentDescriptor) HasResourceDigests() bool { + return cd.Resources.HaveDigests() +} diff --git a/pkg/contexts/ocm/compdesc/suite_test.go b/api/ocm/compdesc/suite_test.go similarity index 100% rename from pkg/contexts/ocm/compdesc/suite_test.go rename to api/ocm/compdesc/suite_test.go diff --git a/pkg/contexts/ocm/compdesc/testutils/compnametest.go b/api/ocm/compdesc/testutils/compnametest.go similarity index 96% rename from pkg/contexts/ocm/compdesc/testutils/compnametest.go rename to api/ocm/compdesc/testutils/compnametest.go index 0c6fdff42..4cabc119f 100644 --- a/pkg/contexts/ocm/compdesc/testutils/compnametest.go +++ b/api/ocm/compdesc/testutils/compnametest.go @@ -9,7 +9,7 @@ import ( "github.com/sirupsen/logrus" - "github.com/open-component-model/ocm/pkg/runtime" + "ocm.software/ocm/api/utils/runtime" ) func TestCompName(dataBytes []byte, err error) { diff --git a/pkg/contexts/ocm/compdesc/util.go b/api/ocm/compdesc/util.go similarity index 100% rename from pkg/contexts/ocm/compdesc/util.go rename to api/ocm/compdesc/util.go diff --git a/api/ocm/compdesc/versions/init.go b/api/ocm/compdesc/versions/init.go new file mode 100644 index 000000000..86c915e6c --- /dev/null +++ b/api/ocm/compdesc/versions/init.go @@ -0,0 +1,6 @@ +package versions + +import ( + _ "ocm.software/ocm/api/ocm/compdesc/versions/ocm.software/v3alpha1" + _ "ocm.software/ocm/api/ocm/compdesc/versions/v2" +) diff --git a/api/ocm/compdesc/versions/ocm.software/v3alpha1/componentdescriptor.go b/api/ocm/compdesc/versions/ocm.software/v3alpha1/componentdescriptor.go new file mode 100644 index 000000000..c584fe5b7 --- /dev/null +++ b/api/ocm/compdesc/versions/ocm.software/v3alpha1/componentdescriptor.go @@ -0,0 +1,283 @@ +package v3alpha1 + +import ( + "errors" + + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/utils/runtime" +) + +var ErrNotFound = errors.New("NotFound") + +// ComponentDescriptor defines a versioned component with a source and dependencies. +type ComponentDescriptor struct { + // TypeMeta specifies the schema version of the component. + metav1.TypeMeta `json:",inline"` + // Spec contains the specification of the component. + metav1.ObjectMeta `json:"metadata"` + + // RepositoryContexts defines the previous repositories of the component + RepositoryContexts runtime.UnstructuredTypedObjectList `json:"repositoryContexts"` + + Spec ComponentVersionSpec `json:"spec"` + // Signatures contains a list of signatures for the ComponentDescriptor + Signatures metav1.Signatures `json:"signatures,omitempty"` + // NestedDigests described digest information of resources in aggregated + // omponent versions. + NestedDigests metav1.NestedDigests `json:"nestedDigests,omitempty"` +} + +var _ compdesc.ComponentDescriptorVersion = (*ComponentDescriptor)(nil) + +// SchemeVersion returns the actual scheme version of this component descriptor +// representation. +func (cd *ComponentDescriptor) SchemaVersion() string { + if cd.APIVersion == "" { + return SchemaVersion + } + return cd.APIVersion +} + +func (cd *ComponentDescriptor) GetName() string { + return cd.Name +} + +// ComponentVersionSpec defines a virtual component with +// a repository context, source and dependencies. +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type ComponentVersionSpec struct { + // Sources defines sources that produced the component + Sources Sources `json:"sources,omitempty"` + // References references component version dependencies that can be resolved in the current context. + References References `json:"references,omitempty"` + // Resources defines all resources that are created by the component and by a third party. + Resources Resources `json:"resources,omitempty"` +} + +const ( + SystemIdentityName = metav1.SystemIdentityName + SystemIdentityVersion = metav1.SystemIdentityVersion +) + +// ElementMetaAccessor provides generic access an elements meta information. +type ElementMetaAccessor interface { + GetMeta() *ElementMeta +} + +// ElementAccessor provides generic access to list of elements. +type ElementAccessor interface { + Len() int + Get(i int) ElementMetaAccessor +} + +// ElementMeta defines a object that is uniquely identified by its identity. +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type ElementMeta struct { + // Name is the context unique name of the object. + Name string `json:"name"` + // Version is the semver version of the object. + Version string `json:"version"` + // ExtraIdentity is the identity of an object. + // An additional label with key "name" ist not allowed + ExtraIdentity metav1.Identity `json:"extraIdentity,omitempty"` + // Labels defines an optional set of additional labels + // describing the object. + // +optional + Labels metav1.Labels `json:"labels,omitempty"` +} + +// GetName returns the name of the object. +func (o *ElementMeta) GetName() string { + return o.Name +} + +// GetMeta returns the element meta. +func (o *ElementMeta) GetMeta() *ElementMeta { + return o +} + +// SetName sets the name of the object. +func (o *ElementMeta) SetName(name string) { + o.Name = name +} + +// GetVersion returns the version of the object. +func (o *ElementMeta) GetVersion() string { + return o.Version +} + +// SetVersion sets the version of the object. +func (o *ElementMeta) SetVersion(version string) { + o.Version = version +} + +// GetLabels returns the label of the object. +func (o *ElementMeta) GetLabels() metav1.Labels { + return o.Labels +} + +// SetLabels sets the labels of the object. +func (o *ElementMeta) SetLabels(labels []metav1.Label) { + o.Labels = labels +} + +// SetExtraIdentity sets the identity of the object. +func (o *ElementMeta) SetExtraIdentity(identity metav1.Identity) { + o.ExtraIdentity = identity +} + +// GetIdentity returns the identity of the object. +func (o *ElementMeta) GetIdentity(accessor ElementAccessor) metav1.Identity { + identity := o.ExtraIdentity.Copy() + if identity == nil { + identity = metav1.Identity{} + } + identity[SystemIdentityName] = o.Name + if accessor != nil { + found := false + l := accessor.Len() + for i := 0; i < l; i++ { + m := accessor.Get(i).GetMeta() + if m.Name == o.Name && m.ExtraIdentity.Equals(o.ExtraIdentity) { + if found { + identity[SystemIdentityVersion] = o.Version + break + } + found = true + } + } + } + return identity +} + +// GetIdentityDigest returns the digest of the object's identity. +func (o *ElementMeta) GetIdentityDigest(accessor ElementAccessor) []byte { + return o.GetIdentity(accessor).Digest() +} + +// Sources describes a set of source specifications. +type Sources []Source + +func (r Sources) Len() int { + return len(r) +} + +func (r Sources) Get(i int) ElementMetaAccessor { + return &r[i] +} + +// Source is the definition of a component's source. +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type Source struct { + SourceMeta `json:",inline"` + + Access *runtime.UnstructuredTypedObject `json:"access"` +} + +// SourceMeta is the definition of the metadata of a source. +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type SourceMeta struct { + ElementMeta `json:",inline"` + // Type describes the type of the object. + Type string `json:"type"` +} + +// GetType returns the type of the object. +func (o SourceMeta) GetType() string { + return o.Type +} + +// SetType sets the type of the object. +func (o *SourceMeta) SetType(ttype string) { + o.Type = ttype +} + +// SourceRef defines a reference to a source +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type SourceRef struct { + // IdentitySelector defines the identity that is used to match a source. + IdentitySelector metav1.StringMap `json:"identitySelector,omitempty"` + // Labels defines an optional set of additional labels + // describing the object. + // +optional + Labels metav1.Labels `json:"labels,omitempty"` +} + +// Resources describes a set of resource specifications. +type Resources []Resource + +func (r Resources) Len() int { + return len(r) +} + +func (r Resources) Get(i int) ElementMetaAccessor { + return &r[i] +} + +// Resource describes a resource dependency of a component. +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type Resource struct { + ElementMeta `json:",inline"` + + // Type describes the type of the object. + Type string `json:"type"` + + // Relation describes the relation of the resource to the component. + // Can be a local or external resource + Relation metav1.ResourceRelation `json:"relation,omitempty"` + + // SourceRefs defines a list of source names. + // These entries reference the sources defined in the + // component.sources. + SourceRefs []SourceRef `json:"srcRefs,omitempty"` + // SourceRef is for deserialization compatibility, only. + // The usage of this field in external formats is deprecated. + SourceRef []SourceRef `json:"srcRef,omitempty"` + + // Access describes the type specific method to + // access the defined resource. + Access *runtime.UnstructuredTypedObject `json:"access"` + + // Digest is the optional digest of the referenced resource. + // +optional + Digest *metav1.DigestSpec `json:"digest,omitempty"` +} + +// GetType returns the type of the object. +func (r Resource) GetType() string { + return r.Type +} + +// SetType sets the type of the object. +func (r *Resource) SetType(ttype string) { + r.Type = ttype +} + +type References []Reference + +func (r References) Len() int { + return len(r) +} + +func (r References) Get(i int) ElementMetaAccessor { + return &r[i] +} + +// Reference describes the reference to another component in the registry. +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type Reference struct { + ElementMeta `json:",inline"` + // ComponentName describes the remote name of the referenced object + ComponentName string `json:"componentName"` + // Digest is the optional digest of the referenced component. + // +optional + Digest *metav1.DigestSpec `json:"digest,omitempty"` +} diff --git a/api/ocm/compdesc/versions/ocm.software/v3alpha1/default.go b/api/ocm/compdesc/versions/ocm.software/v3alpha1/default.go new file mode 100644 index 000000000..0a03ce49a --- /dev/null +++ b/api/ocm/compdesc/versions/ocm.software/v3alpha1/default.go @@ -0,0 +1,49 @@ +package v3alpha1 + +import ( + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/utils/runtime" +) + +// Default applies defaults to a component. +func (cd *ComponentDescriptor) Default() error { + if cd.RepositoryContexts == nil { + cd.RepositoryContexts = make([]*runtime.UnstructuredTypedObject, 0) + } + if cd.Spec.Sources == nil { + cd.Spec.Sources = make([]Source, 0) + } + if cd.Spec.References == nil { + cd.Spec.References = make([]Reference, 0) + } + if cd.Spec.Resources == nil { + cd.Spec.Resources = make([]Resource, 0) + } + + DefaultResources(cd) + return nil +} + +// DefaultResources defaults a list of resources. +// The version of the component is defaulted for local resources that do not contain a version. +// adds the version as identity if the resource identity would clash otherwise. +func DefaultResources(component *ComponentDescriptor) { + for i, res := range component.Spec.Resources { + if res.Relation == v1.LocalRelation && len(res.Version) == 0 { + component.Spec.Resources[i].Version = component.GetVersion() + } + + id := res.GetIdentity(component.Spec.Resources) + if v, ok := id[SystemIdentityVersion]; ok { + if res.ExtraIdentity == nil { + res.ExtraIdentity = v1.Identity{ + SystemIdentityVersion: v, + } + } else { + if _, ok := res.ExtraIdentity[SystemIdentityVersion]; !ok { + res.ExtraIdentity[SystemIdentityVersion] = v + } + } + } + } +} diff --git a/api/ocm/compdesc/versions/ocm.software/v3alpha1/jsonscheme/bindata.go b/api/ocm/compdesc/versions/ocm.software/v3alpha1/jsonscheme/bindata.go new file mode 100644 index 000000000..a48550800 --- /dev/null +++ b/api/ocm/compdesc/versions/ocm.software/v3alpha1/jsonscheme/bindata.go @@ -0,0 +1,263 @@ +// Code generated by go-bindata. (@generated) DO NOT EDIT. + +//Package jsonscheme generated by go-bindata.// sources: +// ../../../../../../../resources/component-descriptor-ocm-v3-schema.yaml +package jsonscheme + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +// Name return file name +func (fi bindataFileInfo) Name() string { + return fi.name +} + +// Size return file size +func (fi bindataFileInfo) Size() int64 { + return fi.size +} + +// Mode return file mode +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} + +// ModTime return file modify time +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} + +// IsDir return file whether a directory +func (fi bindataFileInfo) IsDir() bool { + return fi.mode&os.ModeDir != 0 +} + +// Sys return file is sys mode +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _ResourcesComponentDescriptorOcmV3SchemaYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xe4\x19\x5f\x6f\xdb\xb6\xf3\xdd\x9f\xe2\x80\x04\x90\xdd\x44\x76\xe2\xa2\x0f\xd5\x4b\x50\xb4\x2f\x3f\xfc\xb6\x75\x58\x8b\x3d\x2c\xf5\x02\x46\x3a\xd9\x4c\x25\x52\x23\x69\x27\x5e\x9b\xef\x3e\x90\x14\x25\x4a\x96\x1c\xcb\x49\x1f\x86\xbd\x24\xe2\xf1\xee\x78\xbc\xff\x47\x9f\xd2\x24\x82\x60\xa5\x54\x21\xa3\xd9\x6c\x49\x44\x82\x0c\xc5\x34\xce\xf8\x3a\x99\xc9\x78\x85\x39\x91\xb3\x98\xe7\x05\x67\xc8\x54\x98\xa0\x8c\x05\x2d\x14\x17\x21\x8f\xf3\x70\xf3\x9a\x64\xc5\x8a\x5c\x06\xa3\x53\x8b\xeb\xf1\xba\x93\x9c\x85\x16\x3a\xe5\x62\x39\x4b\x04\x49\xd5\x6c\x7e\x31\xbf\x08\x2f\xe7\x25\xeb\x60\xe4\x18\x52\xce\x22\x08\x3e\xbe\xff\x19\xde\xbb\xc3\xe0\x43\x75\x18\x6c\x5e\x83\xa3\x38\x4d\x30\x95\xd1\x08\x20\x47\x45\xf4\x7f\x00\xb5\x2d\x30\x82\x80\xdf\xde\x61\xac\x02\x03\x6a\xf2\xad\x2e\x00\x1b\x14\x92\x72\x66\x88\x13\xa2\x88\xc5\x16\xf8\xd7\x9a\x0a\x4c\x2c\x3b\x80\x10\x02\x46\x72\x0c\xea\x65\x49\x67\x21\x24\x49\xa8\xe6\x4c\xb2\x5f\x05\x2f\x50\x28\x8a\x32\x82\x94\x64\x12\xcd\x7e\x51\x43\x4b\x0e\x9a\x9b\xfb\x06\x38\x15\x98\x46\x10\x9c\xcc\xcc\x5d\x6a\xf5\xfe\xe2\x9d\x59\x1e\xd8\x4b\x24\x30\x23\x0f\x98\x7c\xc2\x7c\x83\xc2\x11\x65\xe4\x16\x33\xd9\x4b\x63\xb7\x1d\x72\x21\xf8\x86\x26\x28\x7a\xd1\x1d\x82\x23\x88\x05\x12\x7d\xed\xcf\xd4\xbf\x8c\x55\xbe\x54\x82\xb2\x65\x05\x4c\xb9\xc8\x89\x8a\x20\x21\x0a\x43\x45\x73\x1c\x19\x83\x89\x25\xf6\x5a\x6c\x57\x69\x24\x5b\x72\x41\xd5\x2a\xaf\x0f\x2b\x88\x52\x28\xb4\x49\xff\xbc\x26\xe1\xdf\x0b\xfd\xe7\x22\x7c\x3b\xbb\x09\x17\x67\xa7\x95\x9c\x9c\xa5\x74\x19\xc1\x37\x78\x3c\xc0\x5c\xbe\xce\x4a\xb1\x88\x10\x64\x6b\xb9\x51\x85\x79\x25\x50\x97\x3a\x03\xc7\xa2\xf7\x62\x07\x38\x17\xc9\xd6\xd8\xa7\x85\xa6\xeb\x74\x68\xdb\x50\x47\xf0\xed\xb1\xcf\x73\x3c\xa5\x6d\xae\x2f\xc2\xb7\x9e\xaa\x24\x5d\x32\xca\x96\x6d\xfe\xc1\x2d\xe7\x19\x12\xe6\xd0\x3c\xcb\x75\xe8\xc1\xec\x3e\x1d\x19\x23\x6d\x19\xcf\xd3\x1b\x0a\xb3\x37\xb2\x4c\x72\xf2\xf0\x13\xb2\xa5\x5a\x45\x30\x7f\xf3\x66\xd4\x69\xf7\xd0\x1a\x7e\xf1\x6a\x7c\x3d\x5d\xb4\x40\x93\x57\x0e\xf6\x6d\x7e\xfe\x38\x9e\x35\xb6\x6f\x3a\x48\x6e\x34\xcd\x44\x6b\x65\x04\x40\x13\x64\x8a\xaa\xed\x3b\xa5\x04\xbd\x5d\x2b\xfc\x3f\x6e\xad\xa8\x39\x65\x95\x5c\x5d\x52\xe9\xc3\xc7\xd7\xe1\xcd\x99\x13\xc4\x01\x27\x57\x96\x75\x23\x66\x2d\xcf\x13\x50\xe4\x2b\x32\x48\x05\xcf\x41\x9a\x0d\x9d\x2d\x81\xb0\x04\x48\x72\xb7\x96\x0a\x13\x50\x1c\x48\x96\xf1\x7b\x20\x0c\x78\x61\xf5\x0b\x19\x92\x84\xb2\x25\x04\x9b\xe0\x1c\x72\x72\xa7\x53\x32\xcb\xb6\xe7\x86\xd4\xac\xa7\x39\x65\x25\xd4\x9d\xb5\xa2\x12\x72\x24\x4c\x82\x5a\x21\xa4\x5c\x73\xd5\x4c\xac\xfa\x25\x10\x81\xfa\x28\xed\x53\x34\x69\xca\x2b\x9d\xc0\x97\xd3\xf9\xf4\xb5\xff\x1d\xa6\x9c\x9f\xdd\x12\x51\xc2\x36\x3e\xc2\xa6\x0b\xe3\x72\x3a\x77\x5f\x15\x9a\x87\x5f\x7d\x36\xc8\x7c\x65\x6f\x16\x57\xe3\x8b\xef\xd7\x97\xe1\xdb\xc5\x97\xe4\xd5\x64\x7c\x15\x7d\x99\xfa\x80\xc9\x55\x37\x28\x1c\x8f\xaf\xa2\x1a\xf8\xfd\x4b\x62\x6c\xf4\x2e\xfc\x23\x5c\xe8\xc8\x70\xdf\x8e\xe5\x81\xc8\x13\x77\xe2\xd9\xd8\xdf\x38\x33\x4c\x1a\x10\x83\x59\x46\x5f\xcb\xf3\xbb\x5c\xef\xa9\x64\xb9\xd5\x71\x24\x75\xa6\x6b\x85\x64\x97\x13\x07\xf0\x68\x9d\xb0\xe0\x92\x2a\x2e\xb6\xef\x39\x53\xf8\xa0\x86\x24\x2e\x8d\xd5\x97\xa8\x0c\x87\x76\x22\xf1\x6e\x47\xe2\x18\xa5\x3c\xb0\x62\xdf\x12\x89\x06\x4b\x97\x92\x92\x14\x25\x8c\xf5\x0a\x1f\x14\x32\x9d\xe2\xe4\xe4\x09\x41\x47\x00\x92\xaf\x45\x8c\x1f\x30\xa5\xcc\x64\xa6\x01\xb7\xd5\x99\xb7\x5a\x94\x59\xb5\x5a\x6b\x0e\xd5\xc2\xca\x37\x20\x81\x37\xf2\x5d\x47\x4a\xed\xb4\x5f\x89\x8c\x0f\x4a\x90\xff\x95\x08\xbd\x49\x79\x87\xc3\xb3\x1a\x8b\xbd\xb6\xb5\xc0\x41\xbd\x87\xef\x0b\x1d\xc8\x76\xdb\xd8\x2f\xa1\x4b\x94\xea\x53\x81\xf1\x00\xcb\xad\x88\x5c\xbd\x73\xdd\x43\x6d\x4f\xdd\x94\x64\x54\x9a\x26\x66\x77\xdb\xd4\xd1\x23\xfb\xbb\xc6\x81\x7b\xab\x75\xb7\x10\x07\x14\xf8\x6e\x8c\x11\x80\x6e\xaf\xa4\x22\x79\xd1\x56\x92\xd5\x51\x8f\xc4\xfb\x98\x96\xa0\x23\xdb\x3c\xdd\x53\x10\xb5\x16\x38\xd0\x68\x64\x8f\x45\xf4\x2a\xc7\x84\x92\xcf\x2e\xec\x86\xdb\xa8\xa3\x9d\x1c\xa8\x6c\x0b\xaa\xe4\xa8\xb1\x9a\xb9\xeb\xf3\x0a\x2d\x92\x4d\x60\x3c\x35\xc5\xb6\x52\x0b\x78\xed\x5e\x97\x3d\x2b\xc4\x63\x53\x95\x0d\x99\x6a\x59\xf1\x7b\x91\xd9\xa5\x43\x21\xf6\xbc\xde\x60\xae\x23\xd8\xef\x3a\xbd\x1b\x76\xd0\x34\x7c\x28\xf0\x1c\xd2\x38\x7a\x2f\x59\x23\x14\x4c\xfa\x60\xa8\x3b\xa8\x0f\xc7\x24\x91\x3e\x9d\x1e\xd3\xa2\xb7\x73\x6e\x07\xce\x33\xd3\xfa\x00\x23\x54\x6a\xa9\x46\x6d\xab\x9f\xfe\xda\x3c\xb0\x36\xb6\x1c\x50\x60\x59\x84\xed\x29\x2f\xe2\x86\x3f\x6c\x84\x1e\xec\xcc\xad\xdb\xed\x54\x49\x6f\x98\x84\xf6\x40\xd9\x71\x40\xdb\x61\x6d\x13\x23\xe2\xdf\x30\x3d\xb0\x75\x22\x20\x30\x45\x81\x2c\x46\x33\x39\xc0\xb8\x7e\xbf\xc9\x78\x4c\xb2\x49\xd9\x14\x1d\xfb\x98\xe1\x7c\xf0\x13\x66\x18\x2b\xde\xff\x80\xd0\xeb\xac\x07\xf6\x0a\xa6\x5b\x2d\xaf\x72\xec\xe5\xab\xbb\x1f\x3a\x8c\x77\xba\xd2\xf3\x5f\x80\x3a\x46\xdf\x43\xfd\x78\x5f\x03\x09\x27\x40\x62\xb5\x26\x59\xb6\x8d\xea\x33\x42\x53\x78\xee\x67\x20\x0b\x8c\x29\xc9\xb4\x93\x2a\x41\x63\x2d\xb2\xfc\xb7\xf4\x9c\x83\x1a\xca\x76\xd8\x72\x86\x1f\x53\x3f\xcc\x42\xa7\x38\xb6\xce\xb2\xa0\xb1\xb1\x3f\x55\x56\xf1\xfd\xf4\x08\xb1\x6f\x84\x71\x6c\xe4\xd0\x17\x47\x38\x31\xf4\x26\x72\x6b\x2e\xe7\xe5\x2c\xbf\x96\x0a\x72\xa2\xe2\x95\xe7\xe8\x72\x27\x21\x7b\x73\x9b\x59\x6a\x7d\xab\xca\x99\x0d\xc8\xb5\xdc\xdd\xde\xfb\x1f\x99\x61\x6c\x9e\x7d\x76\x0e\xb7\x6c\xea\x02\x61\x95\xfd\xa4\xfe\x90\xad\xf3\x08\xae\x03\x63\xea\xe0\x1c\x02\x3d\xe8\x0a\x46\xb2\x60\x71\x4c\x48\x1c\x38\x63\xfd\xe8\xf8\x69\x3e\x33\x0f\x7d\x1f\x7d\x99\x76\xf5\xb8\x51\x55\x17\xde\x7d\x2d\x63\x33\xdc\x4d\xaa\x4d\x69\x6c\x6c\xed\x9a\xfe\x98\x33\x85\x4c\xe9\xa5\x57\x8a\x9c\xff\xaa\x63\xef\x58\x26\x81\x67\xfb\x69\x2b\xb1\xd5\x1e\x5b\x96\xd0\x67\x9f\x50\x71\x6a\x77\x4b\x2f\xc0\x79\x57\xfa\x51\xcb\x48\xbe\x67\xe9\x14\x57\xd0\xdf\xeb\x22\x1e\x42\xf0\x95\xb2\xa4\xfc\xf4\x7f\x0f\x0a\xad\x31\x83\x51\x53\xf1\x35\x79\xef\xcb\x75\x19\xc1\x10\xf0\x38\x9f\xb6\x7e\x4c\xab\x7e\x2b\x3b\xb7\xdb\x92\xa7\xea\x9e\x08\xac\x37\x40\x87\xb9\x96\xa9\x97\x7f\xcc\x99\x54\x11\x04\x55\xe3\xee\xdd\xc7\xdd\xc0\x12\xef\x3c\xd1\xdb\xab\xed\x3c\xfe\x1d\xf5\xc3\xc7\x0e\x17\x5d\xa4\xe2\xb5\x10\xc8\x54\xb6\x3d\x87\x7b\x04\xce\xb2\x6d\xf9\x68\x6d\x0a\x15\x67\xd8\x08\xa7\xb6\x27\x96\x0d\x75\x35\xf7\x1d\x25\x57\x45\x1d\xb4\x26\xbf\xa3\xb8\x75\xcf\x48\xc1\x3f\x01\x00\x00\xff\xff\x5a\x12\x6d\xad\x32\x1d\x00\x00") + +func ResourcesComponentDescriptorOcmV3SchemaYamlBytes() ([]byte, error) { + return bindataRead( + _ResourcesComponentDescriptorOcmV3SchemaYaml, + "../../../../../../../resources/component-descriptor-ocm-v3-schema.yaml", + ) +} + +func ResourcesComponentDescriptorOcmV3SchemaYaml() (*asset, error) { + bytes, err := ResourcesComponentDescriptorOcmV3SchemaYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "../../../../../../../resources/component-descriptor-ocm-v3-schema.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "../../../../../../../resources/component-descriptor-ocm-v3-schema.yaml": ResourcesComponentDescriptorOcmV3SchemaYaml, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// +// data/ +// foo.txt +// img/ +// a.png +// b.png +// +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} + +var _bintree = &bintree{nil, map[string]*bintree{ + "..": {nil, map[string]*bintree{ + "..": {nil, map[string]*bintree{ + "..": {nil, map[string]*bintree{ + "..": {nil, map[string]*bintree{ + "..": {nil, map[string]*bintree{ + "..": {nil, map[string]*bintree{ + "..": {nil, map[string]*bintree{ + "resources": {nil, map[string]*bintree{ + "component-descriptor-ocm-v3-schema.yaml": {ResourcesComponentDescriptorOcmV3SchemaYaml, map[string]*bintree{}}, + }}, + }}, + }}, + }}, + }}, + }}, + }}, + }}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) +} diff --git a/api/ocm/compdesc/versions/ocm.software/v3alpha1/jsonscheme/jsonscheme.go b/api/ocm/compdesc/versions/ocm.software/v3alpha1/jsonscheme/jsonscheme.go new file mode 100644 index 000000000..b2053fd74 --- /dev/null +++ b/api/ocm/compdesc/versions/ocm.software/v3alpha1/jsonscheme/jsonscheme.go @@ -0,0 +1,55 @@ +//go:generate go-bindata -nometadata -pkg jsonscheme ../../../../../../../resources/component-descriptor-ocm-v3-schema.yaml +//go:generate gofmt -s -w bindata.go + +package jsonscheme + +import ( + "errors" + "fmt" + + "github.com/ghodss/yaml" + "github.com/xeipuuv/gojsonschema" +) + +var Schema *gojsonschema.Schema + +func init() { + dataBytes, err := ResourcesComponentDescriptorOcmV3SchemaYamlBytes() + if err != nil { + panic(err) + } + + data, err := yaml.YAMLToJSON(dataBytes) + if err != nil { + panic(err) + } + + Schema, err = gojsonschema.NewSchema(gojsonschema.NewBytesLoader(data)) + if err != nil { + panic(err) + } +} + +// Validate validates the given data against the component descriptor v2 jsonscheme. +func Validate(src []byte) error { + data, err := yaml.YAMLToJSON(src) + if err != nil { + return err + } + documentLoader := gojsonschema.NewBytesLoader(data) + res, err := Schema.Validate(documentLoader) + if err != nil { + return err + } + + if !res.Valid() { + errs := res.Errors() + errMsg := errs[0].String() + for i := 1; i < len(errs); i++ { + errMsg = fmt.Sprintf("%s;%s", errMsg, errs[i].String()) + } + return errors.New(errMsg) + } + + return nil +} diff --git a/api/ocm/compdesc/versions/ocm.software/v3alpha1/signing.go b/api/ocm/compdesc/versions/ocm.software/v3alpha1/signing.go new file mode 100644 index 000000000..aca008e67 --- /dev/null +++ b/api/ocm/compdesc/versions/ocm.software/v3alpha1/signing.go @@ -0,0 +1,54 @@ +package v3alpha1 + +import ( + "fmt" + + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/compdesc/normalizations/rules" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/norm/entry" +) + +// CDExcludes describes the fields relevant for Signing +// ATTENTION: if changed, please adapt the HashEqual Functions +// in the generic part, accordingly. +var CDExcludes = signing.MapExcludes{ + "repositoryContexts": nil, + "metadata": signing.MapExcludes{ + "labels": rules.LabelExcludes, + }, + "spec": signing.MapExcludes{ + "provider": signing.MapExcludes{ + "labels": rules.LabelExcludes, + }, + "resources": signing.DynamicArrayExcludes{ + ValueMapper: rules.MapResourcesWithNoneAccess, + Continue: signing.MapExcludes{ + "access": nil, + "srcRefs": nil, + "labels": rules.LabelExcludes, + }, + }, + "sources": signing.ArrayExcludes{ + Continue: signing.MapExcludes{ + "access": nil, + "labels": rules.LabelExcludes, + }, + }, + "references": signing.ArrayExcludes{ + signing.MapExcludes{ + "labels": rules.LabelExcludes, + }, + }, + }, + "signatures": nil, + "nestedDigests": nil, +} + +func (cd *ComponentDescriptor) Normalize(normAlgo string) ([]byte, error) { + if normAlgo != compdesc.JsonNormalisationV1 { + return nil, fmt.Errorf("unsupported cd normalization %q", normAlgo) + } + data, err := signing.Normalize(entry.Type, cd, CDExcludes) + return data, err +} diff --git a/api/ocm/compdesc/versions/ocm.software/v3alpha1/validation.go b/api/ocm/compdesc/versions/ocm.software/v3alpha1/validation.go new file mode 100644 index 000000000..b1c21d828 --- /dev/null +++ b/api/ocm/compdesc/versions/ocm.software/v3alpha1/validation.go @@ -0,0 +1,202 @@ +package v3alpha1 + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + "k8s.io/apimachinery/pkg/util/validation/field" + + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" +) + +// Validate validates a parsed v2 component descriptor. +func (cd *ComponentDescriptor) Validate() error { + if err := Validate(nil, cd); err != nil { + return errors.Wrapf(err.ToAggregate(), "%s:%s", cd.Name, cd.Version) + } + return nil +} + +func Validate(fldPath *field.Path, component *ComponentDescriptor) field.ErrorList { + if component == nil { + return nil + } + allErrs := field.ErrorList{} + + if len(component.APIVersion) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("apiVersion"), "must specify a version")) + } + if len(component.Kind) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("kind"), "must specify kind "+Kind)) + } + if component.Kind != Kind { + allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), component.Kind, "must be "+Kind)) + } + + metaPath := fldPath.Child("metadata") + if err := validateProvider(metaPath.Child("provider"), component.Provider); err != nil { + allErrs = append(allErrs, err) + } + + allErrs = append(allErrs, ValidateObjectMeta(metaPath, component)...) + + specPath := fldPath.Child("spec") + srcPath := specPath.Child("sources") + allErrs = append(allErrs, ValidateSources(srcPath, component.Spec.Sources)...) + + refPath := specPath.Child("references") + allErrs = append(allErrs, ValidateComponentReferences(refPath, component.Spec.References)...) + + resourcePath := specPath.Child("resources") + allErrs = append(allErrs, ValidateResources(resourcePath, component.Spec.Resources, component.GetVersion())...) + + return allErrs +} + +// ValidateObjectMeta Validate the metadata of an object. +func ValidateObjectMeta(fldPath *field.Path, om compdesc.ObjectMetaAccessor) field.ErrorList { + allErrs := field.ErrorList{} + if len(om.GetName()) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("name"), "must specify a name")) + } + if len(om.GetVersion()) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("version"), "must specify a version")) + } + if len(om.GetLabels()) != 0 { + allErrs = append(allErrs, metav1.ValidateLabels(fldPath.Child("labels"), om.GetLabels())...) + } + return allErrs +} + +// ValidateSources validates a list of sources. +// It makes sure that no duplicate sources are present. +func ValidateSources(fldPath *field.Path, sources Sources) field.ErrorList { + allErrs := field.ErrorList{} + sourceIDs := make(map[string]struct{}) + for i, src := range sources { + srcPath := fldPath.Index(i) + allErrs = append(allErrs, ValidateSource(srcPath, src, false)...) + + id := src.GetIdentity(sources) + dig := string(id.Digest()) + if _, ok := sourceIDs[dig]; ok { + allErrs = append(allErrs, field.Duplicate(srcPath, fmt.Sprintf("duplicate source %s", id))) + continue + } + sourceIDs[dig] = struct{}{} + } + return allErrs +} + +// ValidateSource validates the a component's source object. +func ValidateSource(fldPath *field.Path, src Source, access bool) field.ErrorList { + allErrs := field.ErrorList{} + if len(src.GetName()) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("name"), "must specify a name")) + } + if len(src.GetType()) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must specify a type")) + } + if src.Access == nil && access { + allErrs = append(allErrs, field.Required(fldPath.Child("access"), "must specify a access")) + } + allErrs = append(allErrs, metav1.ValidateIdentity(fldPath.Child("extraIdentity"), src.ExtraIdentity)...) + return allErrs +} + +// ValidateResource validates a components resource. +func ValidateResource(fldPath *field.Path, res Resource, access bool) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, ValidateObjectMeta(fldPath, &res)...) + + if err := metav1.ValidateRelation(fldPath.Child("relation"), res.Relation); err != nil { + allErrs = append(allErrs, err) + } + + if !metav1.IsIdentity(res.Name) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), res.Name, metav1.IdentityKeyValidationErrMsg)) + } + + if len(res.GetType()) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must specify a type")) + } + + if res.Access == nil && access { + allErrs = append(allErrs, field.Required(fldPath.Child("access"), "must specify a access")) + } + allErrs = append(allErrs, metav1.ValidateIdentity(fldPath.Child("extraIdentity"), res.ExtraIdentity)...) + return allErrs +} + +func validateProvider(fldPath *field.Path, provider metav1.Provider) *field.Error { + if len(provider.Name) == 0 { + return field.Required(fldPath.Child("name"), "provider name must be set") + } + return nil +} + +// ValidateComponentReference validates a component version reference. +func ValidateComponentReference(fldPath *field.Path, cr Reference) field.ErrorList { + allErrs := field.ErrorList{} + if len(cr.ComponentName) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("componentName"), "must specify a component name")) + } + allErrs = append(allErrs, ValidateObjectMeta(fldPath, &cr)...) + return allErrs +} + +// ValidateComponentReferences validates a list of component version references. +// It makes sure that no duplicate sources are present. +func ValidateComponentReferences(fldPath *field.Path, refs References) field.ErrorList { + allErrs := field.ErrorList{} + refIDs := make(map[string]struct{}) + for i, ref := range refs { + refPath := fldPath.Index(i) + allErrs = append(allErrs, ValidateComponentReference(refPath, ref)...) + + id := ref.GetIdentity(refs) + dig := string(id.Digest()) + if _, ok := refIDs[dig]; ok { + allErrs = append(allErrs, field.Duplicate(refPath, fmt.Sprintf("duplicate component reference %s", id))) + continue + } + refIDs[dig] = struct{}{} + } + return allErrs +} + +// ValidateResources validates a list of resources. +// It makes sure that no duplicate sources are present. +func ValidateResources(fldPath *field.Path, resources Resources, componentVersion string) field.ErrorList { + allErrs := field.ErrorList{} + resourceIDs := make(map[string]struct{}) + for i, res := range resources { + localPath := fldPath.Index(i) + allErrs = append(allErrs, ValidateResource(localPath, res, true)...) + + if err := ValidateSourceRefs(localPath.Child("sourceRef"), res.SourceRefs); err != nil { + allErrs = append(allErrs, err...) + } + + id := res.GetIdentity(resources) + dig := string(id.Digest()) + if _, ok := resourceIDs[dig]; ok { + allErrs = append(allErrs, field.Duplicate(localPath, fmt.Sprintf("duplicate resource %s", id))) + continue + } + resourceIDs[dig] = struct{}{} + } + return allErrs +} + +func ValidateSourceRefs(fldPath *field.Path, srcs []SourceRef) field.ErrorList { + allErrs := field.ErrorList{} + for i, src := range srcs { + localPath := fldPath.Index(i) + if err := metav1.ValidateLabels(localPath.Child("labels"), src.Labels); err != nil { + allErrs = append(allErrs, err...) + } + } + return allErrs +} diff --git a/api/ocm/compdesc/versions/ocm.software/v3alpha1/validation_test.go b/api/ocm/compdesc/versions/ocm.software/v3alpha1/validation_test.go new file mode 100644 index 000000000..6dfa91596 --- /dev/null +++ b/api/ocm/compdesc/versions/ocm.software/v3alpha1/validation_test.go @@ -0,0 +1,438 @@ +package v3alpha1_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gstruct" + . "ocm.software/ocm/api/ocm/compdesc/versions/ocm.software/v3alpha1" + + "k8s.io/apimachinery/pkg/util/validation/field" + + meta "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/compdesc/testutils" + "ocm.software/ocm/api/ocm/compdesc/versions/ocm.software/v3alpha1/jsonscheme" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + "ocm.software/ocm/api/utils/runtime" +) + +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "V2 Test Suite") +} + +var _ = Describe("Validation", func() { + testutils.TestCompName(jsonscheme.ResourcesComponentDescriptorOcmV3SchemaYamlBytes()) + + Context("validator", func() { + var ( + comp *ComponentDescriptor + + ociImage1 *Resource + ociRegistry1 *ociartifact.AccessSpec + ociImage2 *Resource + ociRegistry2 *ociartifact.AccessSpec + ) + + BeforeEach(func() { + ociRegistry1 = ociartifact.New("docker/image1:1.2.3") + + unstrucOCIRegistry1, err := runtime.ToUnstructuredTypedObject(ociRegistry1) + Expect(err).ToNot(HaveOccurred()) + + ociImage1 = &Resource{ + ElementMeta: ElementMeta{ + Name: "image1", + Version: "1.2.3", + }, + Relation: meta.ExternalRelation, + Access: unstrucOCIRegistry1, + } + ociRegistry2 = ociartifact.New("docker/image1:1.2.3") + unstrucOCIRegistry2, err := runtime.ToUnstructuredTypedObject(ociRegistry2) + Expect(err).ToNot(HaveOccurred()) + ociImage2 = &Resource{ + ElementMeta: ElementMeta{ + Name: "image2", + Version: "1.2.3", + }, + Relation: meta.ExternalRelation, + Access: unstrucOCIRegistry2, + } + + comp = &ComponentDescriptor{ + TypeMeta: meta.TypeMeta{ + APIVersion: SchemaVersion, + Kind: Kind, + }, + ObjectMeta: meta.ObjectMeta{ + Name: "my-comp", + Version: "1.2.3", + Provider: meta.Provider{ + Name: "external", + }, + }, + RepositoryContexts: nil, + Spec: ComponentVersionSpec{ + Sources: nil, + References: nil, + Resources: []Resource{*ociImage1, *ociImage2}, + }, + } + }) + + Context("#Metadata", func() { + It("should forbid if the component schemaVersion is missing", func() { + comp := ComponentDescriptor{} + + errList := Validate(nil, &comp) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeRequired), + "Field": Equal("apiVersion"), + })))) + }) + + It("should pass if the component schemaVersion is defined", func() { + errList := Validate(nil, comp) + Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeRequired), + "Field": Equal("apiVersion"), + })))) + }) + }) + + Context("#ObjectMeta", func() { + It("should forbid if the component's version is missing", func() { + comp := ComponentDescriptor{} + errList := Validate(nil, &comp) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeRequired), + "Field": Equal("metadata.name"), + })))) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeRequired), + "Field": Equal("metadata.version"), + })))) + }) + + It("should forbid if the component's name is missing", func() { + comp := ComponentDescriptor{} + errList := Validate(nil, &comp) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeRequired), + "Field": Equal("metadata.name"), + })))) + }) + }) + + Context("#Sources", func() { + It("should forbid if a duplicated component's source is defined", func() { + comp.Spec.Sources = []Source{ + { + SourceMeta: SourceMeta{ + ElementMeta: ElementMeta{ + Name: "a", + }, + }, + Access: runtime.NewEmptyUnstructured("custom"), + }, + { + SourceMeta: SourceMeta{ + ElementMeta: ElementMeta{ + Name: "a", + }, + }, + Access: runtime.NewEmptyUnstructured("custom"), + }, + } + errList := Validate(nil, comp) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeDuplicate), + "Field": Equal("spec.sources[1]"), + })))) + }) + }) + + Context("#ComponentReferences", func() { + It("should pass if a reference is set", func() { + comp.Spec.References = []Reference{ + { + ElementMeta: ElementMeta{ + Name: "test", + Version: "1.2.3", + }, + ComponentName: "test", + }, + } + errList := Validate(nil, comp) + Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeRequired), + "Field": Equal("spec.references[0].name"), + })))) + Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeRequired), + "Field": Equal("spec.references[0].version"), + })))) + }) + + It("should forbid if a reference's name is missing", func() { + comp.Spec.References = []Reference{ + { + ElementMeta: ElementMeta{ + Version: "1.2.3", + }, + ComponentName: "test", + }, + } + errList := Validate(nil, comp) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeRequired), + "Field": Equal("spec.references[0].name"), + })))) + }) + + It("should forbid if a reference's component name is missing", func() { + comp.Spec.References = []Reference{ + { + ElementMeta: ElementMeta{ + Name: "test", + Version: "1.2.3", + }, + }, + } + errList := Validate(nil, comp) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeRequired), + "Field": Equal("spec.references[0].componentName"), + })))) + }) + + It("should forbid if a reference's version is missing", func() { + comp.Spec.References = []Reference{ + { + ElementMeta: ElementMeta{ + Name: "test", + }, + ComponentName: "test", + }, + } + errList := Validate(nil, comp) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeRequired), + "Field": Equal("spec.references[0].version"), + })))) + }) + + It("should forbid if a duplicated component reference is defined", func() { + comp.Spec.References = []Reference{ + { + ElementMeta: ElementMeta{ + Name: "test", + }, + }, + { + ElementMeta: ElementMeta{ + Name: "test", + }, + }, + } + errList := Validate(nil, comp) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeDuplicate), + "Field": Equal("spec.references[1]"), + })))) + }) + }) + + Context("#Resources", func() { + It("should forbid if a resource name contains invalid characters", func() { + comp.Spec.Resources = []Resource{ + { + ElementMeta: ElementMeta{ + Name: "test$", + }, + }, + { + ElementMeta: ElementMeta{ + Name: "testšŸ™…", + }, + }, + } + errList := Validate(nil, comp) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeInvalid), + "Field": Equal("spec.resources[0].name"), + })))) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeInvalid), + "Field": Equal("spec.resources[1].name"), + })))) + }) + + It("should forbid if a duplicated local resource is defined", func() { + comp.Spec.Resources = []Resource{ + { + ElementMeta: ElementMeta{ + Name: "test", + }, + }, + { + ElementMeta: ElementMeta{ + Name: "test", + }, + }, + } + errList := Validate(nil, comp) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeDuplicate), + "Field": Equal("spec.resources[1]"), + })))) + }) + + It("should forbid if a duplicated resource with additional identity labels is defined", func() { + comp.Spec.Resources = []Resource{ + { + ElementMeta: ElementMeta{ + Name: "test", + ExtraIdentity: meta.Identity{ + "my-id": "some-id", + }, + }, + }, + { + ElementMeta: ElementMeta{ + Name: "test", + ExtraIdentity: meta.Identity{ + "my-id": "some-id", + }, + }, + }, + } + errList := Validate(nil, comp) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeDuplicate), + "Field": Equal("spec.resources[1]"), + })))) + }) + + It("should pass if a duplicated resource has the same name but with different additional identity labels", func() { + comp.Spec.Resources = []Resource{ + { + ElementMeta: ElementMeta{ + Name: "test", + ExtraIdentity: meta.Identity{ + "my-id": "some-id", + }, + }, + }, + { + ElementMeta: ElementMeta{ + Name: "test", + }, + }, + } + errList := Validate(nil, comp) + Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeDuplicate), + "Field": Equal("spec.resources[1]"), + })))) + Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeDuplicate), + "Field": Equal("spec.resources[0]"), + })))) + }) + }) + + Context("#labels", func() { + It("should forbid if labels are defined multiple times in the same context", func() { + comp.Spec.References = []Reference{ + { + ElementMeta: ElementMeta{ + Name: "test", + Version: "1.2.3", + Labels: []meta.Label{ + { + Name: "l1", + Value: []byte{}, + }, + { + Name: "l1", + Value: []byte{}, + }, + }, + }, + ComponentName: "test", + }, + } + + errList := Validate(nil, comp) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeDuplicate), + "Field": Equal("spec.references[0].labels[1]"), + })))) + }) + + It("should pass if labels are defined multiple times in the same context with differnet names", func() { + comp.Spec.References = []Reference{ + { + ElementMeta: ElementMeta{ + Name: "test", + Version: "1.2.3", + Labels: []meta.Label{ + { + Name: "l1", + Value: []byte{}, + }, + { + Name: "l2", + Value: []byte{}, + }, + }, + }, + ComponentName: "test", + }, + } + + errList := Validate(nil, comp) + Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeDuplicate), + "Field": Equal("spec.references[0].labels[1]"), + })))) + }) + }) + + Context("#Identity", func() { + It("should pass valid identity labels", func() { + identity := meta.Identity{ + "my-l1": "test", + "my-l2": "test", + } + errList := meta.ValidateIdentity(field.NewPath("identity"), identity) + Expect(errList).To(HaveLen(0)) + }) + + It("should forbid if a identity label define the name", func() { + identity := meta.Identity{ + "name": "test", + } + errList := meta.ValidateIdentity(field.NewPath("identity"), identity) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeForbidden), + "Field": Equal("identity[name]"), + })))) + }) + + It("should forbid if a identity label defines a key with invalid characters", func() { + identity := meta.Identity{ + "my-l1!": "test", + } + errList := meta.ValidateIdentity(field.NewPath("identity"), identity) + Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeForbidden), + "Field": Equal("identity[my-l1!]"), + })))) + }) + }) + }) +}) diff --git a/api/ocm/compdesc/versions/ocm.software/v3alpha1/version.go b/api/ocm/compdesc/versions/ocm.software/v3alpha1/version.go new file mode 100644 index 000000000..f39556867 --- /dev/null +++ b/api/ocm/compdesc/versions/ocm.software/v3alpha1/version.go @@ -0,0 +1,310 @@ +package v3alpha1 + +import ( + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/compdesc/versions/ocm.software/v3alpha1/jsonscheme" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + SchemaVersion = GroupVersion + + VersionName = "v3alpha1" + GroupVersion = metav1.GROUP + "/" + VersionName + Kind = metav1.KIND +) + +func init() { + compdesc.RegisterScheme(&DescriptorVersion{}) +} + +type DescriptorVersion struct{} + +var _ compdesc.Scheme = (*DescriptorVersion)(nil) + +func (v *DescriptorVersion) GetVersion() string { + return SchemaVersion +} + +func (v *DescriptorVersion) Decode(data []byte, opts *compdesc.DecodeOptions) (compdesc.ComponentDescriptorVersion, error) { + var cd ComponentDescriptor + if !opts.DisableValidation { + if err := jsonscheme.Validate(data); err != nil { + return nil, err + } + } + var err error + if opts.StrictMode { + err = opts.Codec.DecodeStrict(data, &cd) + } else { + err = opts.Codec.Decode(data, &cd) + } + if err != nil { + return nil, err + } + + if err := cd.Default(); err != nil { + return nil, err + } + + if !opts.DisableValidation { + err = cd.Validate() + if err != nil { + return nil, err + } + } + return &cd, err +} + +//////////////////////////////////////////////////////////////////////////////// +// convert to internal version +//////////////////////////////////////////////////////////////////////////////// + +func (v *DescriptorVersion) ConvertTo(obj compdesc.ComponentDescriptorVersion) (out *compdesc.ComponentDescriptor, err error) { + if obj == nil { + return nil, nil + } + in, ok := obj.(*ComponentDescriptor) + if !ok { + return nil, errors.Newf("%T is no version v2 descriptor", obj) + } + if in.Kind != Kind { + return nil, errors.ErrInvalid("kind", in.Kind) + } + + defer compdesc.CatchConversionError(&err) + out = &compdesc.ComponentDescriptor{ + Metadata: compdesc.Metadata{ConfiguredVersion: in.APIVersion}, + ComponentSpec: compdesc.ComponentSpec{ + ObjectMeta: *in.ObjectMeta.Copy(), + RepositoryContexts: in.RepositoryContexts.Copy(), + Sources: convertSourcesTo(in.Spec.Sources), + Resources: convertResourcesTo(in.Spec.Resources), + References: convertReferencesTo(in.Spec.References), + }, + Signatures: in.Signatures.Copy(), + NestedDigests: in.NestedDigests.Copy(), + } + return out, nil +} + +func convertReferenceTo(in Reference) compdesc.ComponentReference { + return compdesc.ComponentReference{ + ElementMeta: convertElementmetaTo(in.ElementMeta), + ComponentName: in.ComponentName, + Digest: in.Digest.Copy(), + } +} + +func convertReferencesTo(in []Reference) compdesc.References { + out := make(compdesc.References, len(in)) + for i := range in { + out[i] = convertReferenceTo(in[i]) + } + return out +} + +func convertSourceTo(in Source) compdesc.Source { + return compdesc.Source{ + SourceMeta: compdesc.SourceMeta{ + ElementMeta: convertElementmetaTo(in.ElementMeta), + Type: in.Type, + }, + Access: compdesc.GenericAccessSpec(in.Access.DeepCopy()), + } +} + +func convertSourcesTo(in Sources) compdesc.Sources { + if in == nil { + return nil + } + out := make(compdesc.Sources, len(in)) + for i := range in { + out[i] = convertSourceTo(in[i]) + } + return out +} + +func convertElementmetaTo(in ElementMeta) compdesc.ElementMeta { + return compdesc.ElementMeta{ + Name: in.Name, + Version: in.Version, + ExtraIdentity: in.ExtraIdentity.Copy(), + Labels: in.Labels.Copy(), + } +} + +func convertResourceTo(in Resource) compdesc.Resource { + srcRefs := ConvertSourcerefsTo(in.SourceRefs) + if srcRefs == nil { + srcRefs = ConvertSourcerefsTo(in.SourceRef) + } + return compdesc.Resource{ + ResourceMeta: compdesc.ResourceMeta{ + ElementMeta: convertElementmetaTo(in.ElementMeta), + Type: in.Type, + Relation: in.Relation, + SourceRefs: srcRefs, + Digest: in.Digest.Copy(), + }, + Access: compdesc.GenericAccessSpec(in.Access), + } +} + +func convertResourcesTo(in Resources) compdesc.Resources { + if in == nil { + return nil + } + out := make(compdesc.Resources, len(in)) + for i := range in { + out[i] = convertResourceTo(in[i]) + } + return out +} + +func convertSourcerefTo(in SourceRef) compdesc.SourceRef { + return compdesc.SourceRef{ + IdentitySelector: in.IdentitySelector.Copy(), + Labels: in.Labels.Copy(), + } +} + +func ConvertSourcerefsTo(in []SourceRef) []compdesc.SourceRef { + if in == nil { + return nil + } + out := make([]compdesc.SourceRef, len(in)) + for i := range in { + out[i] = convertSourcerefTo(in[i]) + } + return out +} + +//////////////////////////////////////////////////////////////////////////////// +// convert from internal version +//////////////////////////////////////////////////////////////////////////////// + +func (v *DescriptorVersion) ConvertFrom(in *compdesc.ComponentDescriptor) (compdesc.ComponentDescriptorVersion, error) { + if in == nil { + return nil, nil + } + out := &ComponentDescriptor{ + TypeMeta: metav1.TypeMeta{ + APIVersion: SchemaVersion, + Kind: Kind, + }, + ObjectMeta: *in.ObjectMeta.Copy(), + RepositoryContexts: in.RepositoryContexts.Copy(), + Spec: ComponentVersionSpec{ + Sources: convertSourcesFrom(in.Sources), + Resources: convertResourcesFrom(in.Resources), + References: convertReferencesFrom(in.References), + }, + Signatures: in.Signatures.Copy(), + NestedDigests: in.NestedDigests.Copy(), + } + if err := out.Default(); err != nil { + return nil, err + } + return out, nil +} + +func convertReferenceFrom(in compdesc.ComponentReference) Reference { + return Reference{ + ElementMeta: convertElementmetaFrom(in.ElementMeta), + ComponentName: in.ComponentName, + Digest: in.Digest.Copy(), + } +} + +func convertReferencesFrom(in []compdesc.ComponentReference) []Reference { + if in == nil { + return nil + } + out := make([]Reference, len(in)) + for i := range in { + out[i] = convertReferenceFrom(in[i]) + } + return out +} + +func convertSourceFrom(in compdesc.Source) Source { + acc, err := runtime.ToUnstructuredTypedObject(in.Access) + if err != nil { + compdesc.ThrowConversionError(err) + } + return Source{ + SourceMeta: SourceMeta{ + ElementMeta: convertElementmetaFrom(in.ElementMeta), + Type: in.Type, + }, + Access: acc, + } +} + +func convertSourcesFrom(in compdesc.Sources) Sources { + if in == nil { + return nil + } + out := make(Sources, len(in)) + for i := range in { + out[i] = convertSourceFrom(in[i]) + } + return out +} + +func convertElementmetaFrom(in compdesc.ElementMeta) ElementMeta { + return ElementMeta{ + Name: in.Name, + Version: in.Version, + ExtraIdentity: in.ExtraIdentity.Copy(), + Labels: in.Labels.Copy(), + } +} + +func convertResourceFrom(in compdesc.Resource) Resource { + acc, err := runtime.ToUnstructuredTypedObject(in.Access) + if err != nil { + compdesc.ThrowConversionError(err) + } + return Resource{ + ElementMeta: convertElementmetaFrom(in.ElementMeta), + Type: in.Type, + Relation: in.Relation, + SourceRefs: convertSourcerefsFrom(in.SourceRefs), + Access: acc, + Digest: in.Digest.Copy(), + } +} + +func convertResourcesFrom(in compdesc.Resources) Resources { + if in == nil { + return nil + } + out := make(Resources, len(in)) + for i := range in { + out[i] = convertResourceFrom(in[i]) + } + return out +} + +func convertSourcerefFrom(in compdesc.SourceRef) SourceRef { + return SourceRef{ + IdentitySelector: in.IdentitySelector.Copy(), + Labels: in.Labels.Copy(), + } +} + +func convertSourcerefsFrom(in []compdesc.SourceRef) []SourceRef { + if in == nil { + return nil + } + out := make([]SourceRef, len(in)) + for i := range in { + out[i] = convertSourcerefFrom(in[i]) + } + return out +} diff --git a/api/ocm/compdesc/versions/ocm.software/v3alpha1/zz_generated.deepcopy.go b/api/ocm/compdesc/versions/ocm.software/v3alpha1/zz_generated.deepcopy.go new file mode 100644 index 000000000..b2e83b82b --- /dev/null +++ b/api/ocm/compdesc/versions/ocm.software/v3alpha1/zz_generated.deepcopy.go @@ -0,0 +1,199 @@ +//go:build !ignore_autogenerated + +// Code generated by controller-gen. DO NOT EDIT. + +package v3alpha1 + +import ( + "ocm.software/ocm/api/ocm/compdesc/meta/v1" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ComponentVersionSpec) DeepCopyInto(out *ComponentVersionSpec) { + *out = *in + if in.Sources != nil { + in, out := &in.Sources, &out.Sources + *out = make(Sources, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.References != nil { + in, out := &in.References, &out.References + *out = make(References, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = make(Resources, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentVersionSpec. +func (in *ComponentVersionSpec) DeepCopy() *ComponentVersionSpec { + if in == nil { + return nil + } + out := new(ComponentVersionSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ElementMeta) DeepCopyInto(out *ElementMeta) { + *out = *in + if in.ExtraIdentity != nil { + in, out := &in.ExtraIdentity, &out.ExtraIdentity + *out = make(v1.Identity, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(v1.Labels, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ElementMeta. +func (in *ElementMeta) DeepCopy() *ElementMeta { + if in == nil { + return nil + } + out := new(ElementMeta) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Reference) DeepCopyInto(out *Reference) { + *out = *in + in.ElementMeta.DeepCopyInto(&out.ElementMeta) + if in.Digest != nil { + in, out := &in.Digest, &out.Digest + *out = new(v1.DigestSpec) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Reference. +func (in *Reference) DeepCopy() *Reference { + if in == nil { + return nil + } + out := new(Reference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Resource) DeepCopyInto(out *Resource) { + *out = *in + in.ElementMeta.DeepCopyInto(&out.ElementMeta) + if in.SourceRefs != nil { + in, out := &in.SourceRefs, &out.SourceRefs + *out = make([]SourceRef, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.SourceRef != nil { + in, out := &in.SourceRef, &out.SourceRef + *out = make([]SourceRef, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Access != nil { + in, out := &in.Access, &out.Access + *out = (*in).DeepCopy() + } + if in.Digest != nil { + in, out := &in.Digest, &out.Digest + *out = new(v1.DigestSpec) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Resource. +func (in *Resource) DeepCopy() *Resource { + if in == nil { + return nil + } + out := new(Resource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Source) DeepCopyInto(out *Source) { + *out = *in + in.SourceMeta.DeepCopyInto(&out.SourceMeta) + if in.Access != nil { + in, out := &in.Access, &out.Access + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Source. +func (in *Source) DeepCopy() *Source { + if in == nil { + return nil + } + out := new(Source) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SourceMeta) DeepCopyInto(out *SourceMeta) { + *out = *in + in.ElementMeta.DeepCopyInto(&out.ElementMeta) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceMeta. +func (in *SourceMeta) DeepCopy() *SourceMeta { + if in == nil { + return nil + } + out := new(SourceMeta) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SourceRef) DeepCopyInto(out *SourceRef) { + *out = *in + if in.IdentitySelector != nil { + in, out := &in.IdentitySelector, &out.IdentitySelector + *out = make(v1.StringMap, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(v1.Labels, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceRef. +func (in *SourceRef) DeepCopy() *SourceRef { + if in == nil { + return nil + } + out := new(SourceRef) + in.DeepCopyInto(out) + return out +} diff --git a/api/ocm/compdesc/versions/v2/componentdescriptor.go b/api/ocm/compdesc/versions/v2/componentdescriptor.go new file mode 100644 index 000000000..c842619f6 --- /dev/null +++ b/api/ocm/compdesc/versions/v2/componentdescriptor.go @@ -0,0 +1,340 @@ +package v2 + +import ( + "errors" + + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/utils/runtime" +) + +var ErrNotFound = errors.New("NotFound") + +// ComponentDescriptor defines a versioned component with a source and dependencies. +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type ComponentDescriptor struct { + // Metadata specifies the schema version of the component. + Metadata metav1.Metadata `json:"meta"` + // Spec contains the specification of the component. + ComponentSpec `json:"component"` + // Signatures contains a list of signatures for the ComponentDescriptor + Signatures metav1.Signatures `json:"signatures,omitempty"` + // NestedDigests described digest information of resources in aggregated + // omponent versions. + NestedDigests metav1.NestedDigests `json:"nestedDigests,omitempty"` +} + +var _ compdesc.ComponentDescriptorVersion = (*ComponentDescriptor)(nil) + +// SchemeVersion returns the actual scheme version of this component descriptor +// representation. +func (cd *ComponentDescriptor) SchemaVersion() string { + if cd.Metadata.Version == "" { + return SchemaVersion + } + return cd.Metadata.Version +} + +// ComponentSpec defines a virtual component with +// a repository context, source and dependencies. +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type ComponentSpec struct { + ObjectMeta `json:",inline"` + // RepositoryContexts defines the previous repositories of the component + RepositoryContexts runtime.UnstructuredTypedObjectList `json:"repositoryContexts"` + // Provider defines the provider type of a component. + // It can be external or internal. + Provider metav1.ProviderName `json:"provider"` + // Sources defines sources that produced the component + Sources Sources `json:"sources"` + // ComponentReferences references component dependencies that can be resolved in the current context. + ComponentReferences ComponentReferences `json:"componentReferences"` + // Resources defines all resources that are created by the component and by a third party. + Resources Resources `json:"resources"` +} + +// ObjectMeta defines a object that is uniquely identified by its name and version. +// +k8s:deepcopy-gen=true +type ObjectMeta struct { + // Name is the context unique name of the object. + Name string `json:"name"` + // Version is the semver version of the object. + Version string `json:"version"` + // Labels defines an optional set of additional labels + // describing the object. + // +optional + Labels metav1.Labels `json:"labels,omitempty"` + // CreationTime is the creation time of the component version + // +optional + CreationTime *metav1.Timestamp `json:"creationTime,omitempty"` +} + +// GetName returns the name of the object. +func (o ObjectMeta) GetName() string { + return o.Name +} + +// SetName sets the name of the object. +func (o *ObjectMeta) SetName(name string) { + o.Name = name +} + +// GetVersion returns the version of the object. +func (o ObjectMeta) GetVersion() string { + return o.Version +} + +// SetVersion sets the version of the object. +func (o *ObjectMeta) SetVersion(version string) { + o.Version = version +} + +// GetLabels returns the label of the object. +func (o ObjectMeta) GetLabels() metav1.Labels { + return o.Labels +} + +// SetLabels sets the labels of the object. +func (o *ObjectMeta) SetLabels(labels []metav1.Label) { + o.Labels = labels +} + +const ( + SystemIdentityName = metav1.SystemIdentityName + SystemIdentityVersion = metav1.SystemIdentityVersion +) + +// ElementMetaAccessor provides generic access an elements meta information. +type ElementMetaAccessor interface { + GetMeta() *ElementMeta +} + +// ElementAccessor provides generic access to list of elements. +type ElementAccessor interface { + Len() int + Get(i int) ElementMetaAccessor +} + +// ElementMeta defines a object that is uniquely identified by its identity. +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type ElementMeta struct { + // Name is the context unique name of the object. + Name string `json:"name"` + // Version is the semver version of the object. + Version string `json:"version"` + // ExtraIdentity is the identity of an object. + // An additional label with key "name" ist not allowed + ExtraIdentity metav1.Identity `json:"extraIdentity,omitempty"` + // Labels defines an optional set of additional labels + // describing the object. + // +optional + Labels metav1.Labels `json:"labels,omitempty"` +} + +// GetName returns the name of the object. +func (o *ElementMeta) GetName() string { + return o.Name +} + +// GetMeta returns the element meta. +func (o *ElementMeta) GetMeta() *ElementMeta { + return o +} + +// SetName sets the name of the object. +func (o *ElementMeta) SetName(name string) { + o.Name = name +} + +// GetVersion returns the version of the object. +func (o ElementMeta) GetVersion() string { + return o.Version +} + +// SetVersion sets the version of the object. +func (o *ElementMeta) SetVersion(version string) { + o.Version = version +} + +// GetLabels returns the label of the object. +func (o ElementMeta) GetLabels() metav1.Labels { + return o.Labels +} + +// SetLabels sets the labels of the object. +func (o *ElementMeta) SetLabels(labels []metav1.Label) { + o.Labels = labels +} + +// SetExtraIdentity sets the identity of the object. +func (o *ElementMeta) SetExtraIdentity(identity metav1.Identity) { + o.ExtraIdentity = identity +} + +// GetIdentity returns the identity of the object. +func (o *ElementMeta) GetIdentity(accessor ElementAccessor) metav1.Identity { + identity := o.ExtraIdentity.Copy() + if identity == nil { + identity = metav1.Identity{} + } + identity[SystemIdentityName] = o.Name + if accessor != nil { + found := false + l := accessor.Len() + for i := 0; i < l; i++ { + m := accessor.Get(i).GetMeta() + if m.Name == o.Name && m.ExtraIdentity.Equals(o.ExtraIdentity) { + if found { + identity[SystemIdentityVersion] = o.Version + break + } + found = true + } + } + } + return identity +} + +// GetIdentityDigest returns the digest of the object's identity. +func (o *ElementMeta) GetIdentityDigest(accessor ElementAccessor) []byte { + return o.GetIdentity(accessor).Digest() +} + +func (o *ElementMeta) GetRawIdentity() metav1.Identity { + identity := o.ExtraIdentity.Copy() + if identity == nil { + identity = metav1.Identity{} + } + identity[SystemIdentityName] = o.Name + if o.Version != "" { + identity[SystemIdentityVersion] = o.Version + } + return identity +} + +// Sources describes a set of source specifications. +type Sources []Source + +func (r Sources) Len() int { + return len(r) +} + +func (r Sources) Get(i int) ElementMetaAccessor { + return &r[i] +} + +// Source is the definition of a component's source. +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type Source struct { + SourceMeta `json:",inline"` + + Access *runtime.UnstructuredTypedObject `json:"access"` +} + +// SourceMeta is the definition of the meta data of a source. +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type SourceMeta struct { + ElementMeta `json:",inline"` + // Type describes the type of the object. + Type string `json:"type"` +} + +// GetType returns the type of the object. +func (o SourceMeta) GetType() string { + return o.Type +} + +// SetType sets the type of the object. +func (o *SourceMeta) SetType(ttype string) { + o.Type = ttype +} + +// SourceRef defines a reference to a source +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type SourceRef struct { + // IdentitySelector defines the identity that is used to match a source. + IdentitySelector metav1.StringMap `json:"identitySelector,omitempty"` + // Labels defines an optional set of additional labels + // describing the object. + // +optional + Labels metav1.Labels `json:"labels,omitempty"` +} + +// Resources describes a set of resource specifications. +type Resources []Resource + +func (r Resources) Len() int { + return len(r) +} + +func (r Resources) Get(i int) ElementMetaAccessor { + return &r[i] +} + +// Resource describes a resource dependency of a component. +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type Resource struct { + ElementMeta `json:",inline"` + + // Type describes the type of the object. + Type string `json:"type"` + + // Relation describes the relation of the resource to the component. + // Can be a local or external resource + Relation metav1.ResourceRelation `json:"relation,omitempty"` + + // SourceRefs defines a list of source names. + // These entries reference the sources defined in the + // component.sources. + SourceRefs []SourceRef `json:"srcRefs,omitempty"` + // SourceRef is for deserialization compatibility, only. + // The usage of this field in external formats is deprecated. + SourceRef []SourceRef `json:"srcRef,omitempty"` + + // Access describes the type specific method to + // access the defined resource. + Access *runtime.UnstructuredTypedObject `json:"access"` + + // Digest is the optional digest of the referenced resource. + // +optional + Digest *metav1.DigestSpec `json:"digest,omitempty"` +} + +// GetType returns the type of the object. +func (o Resource) GetType() string { + return o.Type +} + +// SetType sets the type of the object. +func (o *Resource) SetType(ttype string) { + o.Type = ttype +} + +type ComponentReferences []ComponentReference + +func (r ComponentReferences) Len() int { + return len(r) +} + +func (r ComponentReferences) Get(i int) ElementMetaAccessor { + return &r[i] +} + +// ComponentReference describes the reference to another component in the registry. +// +k8s:deepcopy-gen=true +// +k8s:openapi-gen=true +type ComponentReference struct { + ElementMeta `json:",inline"` + // ComponentName describes the remote name of the referenced object + ComponentName string `json:"componentName"` + // Digest is the optional digest of the referenced component. + // +optional + Digest *metav1.DigestSpec `json:"digest,omitempty"` +} diff --git a/api/ocm/compdesc/versions/v2/default.go b/api/ocm/compdesc/versions/v2/default.go new file mode 100644 index 000000000..ce4aec220 --- /dev/null +++ b/api/ocm/compdesc/versions/v2/default.go @@ -0,0 +1,49 @@ +package v2 + +import ( + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/utils/runtime" +) + +// Default applies defaults to a component. +func (cd *ComponentDescriptor) Default() error { + if cd.RepositoryContexts == nil { + cd.RepositoryContexts = make([]*runtime.UnstructuredTypedObject, 0) + } + if cd.Sources == nil { + cd.Sources = make([]Source, 0) + } + if cd.ComponentReferences == nil { + cd.ComponentReferences = make([]ComponentReference, 0) + } + if cd.Resources == nil { + cd.Resources = make([]Resource, 0) + } + + DefaultResources(cd) + return nil +} + +// DefaultResources defaults a list of resources. +// The version of the component is defaulted for local resources that do not contain a version. +// adds the version as identity if the resource identity would clash otherwise. +func DefaultResources(component *ComponentDescriptor) { + for i, res := range component.Resources { + if res.Relation == v1.LocalRelation && len(res.Version) == 0 { + component.Resources[i].Version = component.GetVersion() + } + + id := res.GetIdentity(component.Resources) + if v, ok := id[SystemIdentityVersion]; ok { + if res.ExtraIdentity == nil { + res.ExtraIdentity = v1.Identity{ + SystemIdentityVersion: v, + } + } else { + if _, ok := res.ExtraIdentity[SystemIdentityVersion]; !ok { + res.ExtraIdentity[SystemIdentityVersion] = v + } + } + } + } +} diff --git a/api/ocm/compdesc/versions/v2/jsonscheme/bindata.go b/api/ocm/compdesc/versions/v2/jsonscheme/bindata.go new file mode 100644 index 000000000..df9d4b8d9 --- /dev/null +++ b/api/ocm/compdesc/versions/v2/jsonscheme/bindata.go @@ -0,0 +1,261 @@ +// Code generated by go-bindata. (@generated) DO NOT EDIT. + +//Package jsonscheme generated by go-bindata.// sources: +// ../../../../../../resources/component-descriptor-v2-schema.yaml +package jsonscheme + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +// Name return file name +func (fi bindataFileInfo) Name() string { + return fi.name +} + +// Size return file size +func (fi bindataFileInfo) Size() int64 { + return fi.size +} + +// Mode return file mode +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} + +// ModTime return file modify time +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} + +// IsDir return file whether a directory +func (fi bindataFileInfo) IsDir() bool { + return fi.mode&os.ModeDir != 0 +} + +// Sys return file is sys mode +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _ResourcesComponentDescriptorV2SchemaYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xe4\x59\x4f\x6f\x1b\xb7\x12\xbf\xeb\x53\x0c\x60\x03\x94\x62\xaf\x64\xeb\x21\x87\xec\xc5\x08\x92\xcb\xc3\x7b\x6d\x8a\x24\xe8\xa1\x8a\x6a\xd0\xbb\xb3\x12\xdd\x5d\x72\x4b\x52\x8a\xd5\xc4\xdf\xbd\x20\xb9\xdc\xff\x2b\x4b\x72\xd2\xa2\x68\x0e\xb1\x38\x1c\x0e\x87\x33\xbf\xf9\x27\x9d\xb3\x38\x04\xb2\xd6\x3a\x57\xe1\x6c\xb6\xa2\x32\x46\x8e\x72\x1a\xa5\x62\x13\xcf\x54\xb4\xc6\x8c\xaa\x59\x24\xb2\x5c\x70\xe4\x3a\x88\x51\x45\x92\xe5\x5a\xc8\x60\x3b\x27\xa3\x73\xc7\x51\x93\x70\xaf\x04\x0f\x1c\x75\x2a\xe4\x6a\x16\x4b\x9a\xe8\xd9\xfc\x6a\x7e\x15\x5c\xcf\x0b\x81\x64\xe4\xc5\x30\xc1\x43\x20\xef\x72\xe4\xf0\xc6\xdf\x01\x3f\x88\x18\x53\xd8\xce\xc1\x73\x9f\xc7\x98\xa8\x70\x04\x90\xa1\xa6\xe6\x2f\x80\xde\xe5\x18\x02\x11\x77\xf7\x18\x69\x62\x49\x4d\x99\xa5\xca\x50\xa9\x6c\xcf\xc7\x54\x53\x77\x40\xe2\xef\x1b\x26\x31\x76\x12\x01\x02\x20\xee\xc6\x9f\x51\x2a\x26\xb8\xe3\xca\xa5\xc8\x51\x6a\x86\xca\xf3\x35\x98\x3c\xb1\x54\x49\x69\xc9\xf8\x8a\x8c\xac\xba\x72\x85\x83\xfa\x76\x05\xd3\x74\x25\x24\xd3\xeb\xac\x12\x9a\x53\xad\x51\x9a\x07\xfd\xba\xa0\xc1\x1f\x4b\xf3\xdf\x55\xf0\x6a\x76\x1b\x2c\x2f\xce\x49\xc1\x16\x09\x9e\xb0\x55\x08\x5f\xe0\xd1\x52\x68\x1c\x33\x63\x06\x9a\xfe\x54\xdd\x01\x09\x4d\x15\x8e\x00\x52\x7a\x87\xe9\xa0\x56\x3d\x46\xe1\x34\x43\x52\x2d\xb7\x34\xdd\xe0\xd0\x13\x0c\xef\xa0\x49\x1c\xd1\x9e\x0f\xe1\xcb\xa3\x5f\xb7\x0d\x59\x7b\xf3\x76\x71\x15\xbc\xaa\xbd\x54\xb1\x15\x67\x7c\xd5\xb9\xe1\x4e\x88\x14\x29\xf7\x6c\x35\xc3\x9b\x7f\xe7\x12\x93\x10\xc8\xd9\xcc\x02\x69\x66\x77\xad\x83\x4a\x90\xfc\x58\xaa\xdd\xa3\x72\x46\x1f\xfe\x8f\x7c\xa5\xd7\x21\xcc\x5f\xbe\x1c\xf5\xba\x25\x70\x7e\x59\xbe\x18\x2f\xa6\xcb\x16\x69\xf2\xc2\xd3\xbe\xcc\x2f\x1f\xc7\xb3\xc6\xf6\x6d\xcf\x91\x5b\x73\x66\x62\x5e\x3d\x02\x60\x31\x72\xcd\xf4\xee\xb5\xd6\x92\xdd\x6d\x34\xfe\x0f\x77\x4e\xd5\x8c\xf1\x52\xaf\x3e\xad\xcc\xe5\xe3\x45\x70\x7b\xe1\x15\xf1\xc4\xc9\x8d\x13\x2d\x31\xa5\x0f\x18\x7f\xc0\x6c\x8b\xd2\xc9\x3c\x03\x4d\x7f\x43\x0e\x89\x14\x19\x28\xbb\x61\xc2\x18\x28\x8f\x81\xc6\xf7\x1b\xa5\x31\x06\x2d\x80\xa6\xa9\xf8\x0c\x94\x83\xc8\x1d\xd2\x20\x45\x1a\x33\xbe\x02\xb2\x25\x97\x90\xd1\x7b\x21\x03\xc1\xd3\xdd\xa5\x3d\x6a\xd7\xd3\x8c\xf1\x82\xea\xef\x5a\x33\x05\x19\x52\xae\x40\xaf\x11\x12\x61\xa4\x1a\x21\xce\xfc\x0a\xa8\x44\x73\x95\xc1\x0c\x8b\x9b\xfa\x2a\xaf\xf0\xf5\x74\x3e\xfd\x4f\xfd\x73\x90\x08\x71\x71\x47\x65\x41\xdb\xd6\x19\xb6\x7d\x1c\xd7\xd3\xb9\xff\x54\xb2\xd5\xf8\xcb\x8f\x8d\x63\x75\x63\x6f\x97\x37\xe3\xab\xaf\x8b\xeb\xe0\xd5\xf2\x53\xfc\x62\x32\xbe\x09\x3f\x4d\xeb\x84\xc9\x4d\x3f\x29\x18\x8f\x6f\xc2\x8a\xf8\xf5\x53\x6c\x7d\xf4\x3a\xf8\x25\x58\x1a\xe4\xfb\xcf\x5e\xe4\x81\xcc\x13\x7f\xe3\xc5\xb8\xbe\x71\x61\x85\x34\x28\x96\xb3\x88\xae\x6e\xfe\xea\x40\xef\xa9\x5c\xb6\x33\x71\xa4\x4c\x22\x6a\x85\x5c\x1f\x88\x09\x3c\x3a\x10\xe6\x42\x31\x2d\xe4\xee\x8d\xe0\x1a\x1f\xf4\x31\xa9\xc9\x70\x0d\xa5\x22\x2b\x61\x4f\x76\xa6\x51\x84\x4a\x1d\x58\x4e\xee\xa8\x42\xcb\x05\x89\x90\xc5\x51\x54\x30\x36\x2b\x7c\xd0\xc8\x4d\x0a\x53\x93\x27\x14\x1d\x01\x28\xb1\x91\x11\xbe\xc5\x84\x71\x9b\xa3\x8f\x78\xad\xc9\xad\xe5\xa2\xc8\x9a\xe5\xda\x48\x28\x17\x4e\xbf\xd3\x53\x74\x27\x65\xf6\xfa\xaf\x60\xc6\x07\x2d\xe9\x7f\x0b\x86\xc1\xa4\xdb\x91\x40\x86\xd2\x7f\xeb\x60\x23\xe8\xc9\x21\xbe\x75\x44\x5b\xe6\x54\x87\x89\x4a\x49\x77\xd5\x3b\x99\xc6\xac\xc6\xd4\xb9\xdd\x4a\xf1\xec\x75\xc4\xf4\x31\xbb\x7d\xeb\xe6\x98\xad\x50\xe9\x0f\x39\x46\x47\x38\x78\x4d\xd5\xfa\xb5\xef\x01\x2a\xb7\x0b\x99\xd1\x94\x29\x6a\xe0\xd2\xdd\xb6\xe5\x74\xc0\xd5\x0d\x81\x6d\x53\x38\x73\x79\x50\xf4\x5e\xb2\xf7\x88\xab\xe3\xfd\x1c\x23\x00\xcd\x32\x54\x9a\x66\x79\xdb\x08\xce\x06\x03\x1a\xef\x13\x5a\x90\x58\x17\xbe\x0d\x06\x30\x21\x9a\x51\x1d\x42\x4c\x35\x06\x86\xdf\x06\x1e\x5b\x71\xaa\x37\x12\x8f\x74\x0a\xdd\x63\x71\xb3\xca\x30\x66\xf4\xa3\x8f\xbe\x83\x9a\xba\x23\x8d\xe9\x48\xe5\x3d\x15\x57\x33\x45\x7d\x5c\xa3\x63\x72\x79\x4a\x24\xb6\xa6\x96\xcf\x86\x5a\xdf\xd6\xe7\xaf\x92\xf1\xd4\x8c\xe4\x20\x5f\x2e\x4b\x79\x07\xb4\xa4\x87\xa6\xa9\x86\x41\xdc\x7d\x83\x79\xa3\x8a\xc0\x7a\xf3\x58\x7b\x61\xcf\x99\x06\x46\x48\x0d\x70\x16\xc8\x83\xc7\x1a\x50\xb7\xe1\xcf\xd1\x34\x4a\x6f\x4f\x49\x02\xa5\x4d\x4f\x30\x49\x27\x97\xf6\xf0\x3c\x33\x5d\x1f\x61\xf5\xd2\x0e\xe5\x68\xe7\x0c\x32\x5c\x73\x8f\xa8\x79\x4f\x59\xa7\xa5\x57\xa3\xd3\x7f\x56\xe9\x39\x1a\x76\x12\x8b\x9a\x5f\x7f\x3c\x9c\x5c\x91\xda\xd0\x72\x5d\x85\x8c\xde\x63\x72\x60\x2f\x43\x41\x62\x82\x12\x79\x84\xb6\x95\x87\x71\x35\xdf\xa7\x22\xa2\xe9\xa4\xe8\x52\xc8\x89\xa1\xeb\xc1\xf3\x01\x53\x8c\xb4\x90\xc7\xa3\xec\x9b\x16\xef\xfa\xa0\xf7\xde\xbf\xfc\x54\x5b\x95\x92\x0e\x1d\x97\x7b\x91\x67\xc6\xe8\xfa\x97\x0c\xc7\xdb\xb8\x67\x74\x3d\x14\xf6\xfb\x1a\x40\x38\x03\x1a\xe9\x0d\x4d\xd3\x5d\x58\xdd\x11\xd8\x8a\xf2\x79\x06\x2a\xc7\x88\xd1\xd4\x60\x5a\x4b\x16\x19\x95\xd5\x3f\xa5\x67\xfc\x0e\x0d\x61\x3b\x17\x08\x8e\xef\x92\xfa\xe1\xc0\xdf\xc2\x37\x69\x4a\x1a\x1b\xfb\x13\x67\x99\x34\x9e\x1e\x14\xf6\x0d\x2a\x5e\x8c\x3a\xf8\xcb\x9d\x02\x95\x70\x66\xcf\xdb\x74\x50\x49\xb9\x2c\x26\xf6\x8d\xd2\x90\x51\x1d\xad\x6b\xe1\xa0\x3a\x23\x49\x6d\x3a\xb3\x4b\xe3\x15\x5d\x42\xde\x92\x7c\xc7\xdc\x8f\xf1\x7f\xc9\xa4\xe2\x92\xf7\xb3\x91\xe9\xc4\x54\x55\xc7\x19\xfb\x49\xfb\x21\xdf\x64\x21\x2c\x88\x75\x35\xb9\x04\x62\xc6\x59\xc9\x69\x4a\x96\xdf\x2f\x70\x3a\x93\xd4\xd0\x28\xe5\x36\xbf\x5f\x9c\x95\xf8\x3d\xb8\x1e\x1c\x5d\x00\x1a\x89\xbe\x08\x84\xd6\x57\x1e\xaa\xb6\x99\x4b\xb1\x65\x71\x05\xa0\x00\x48\x23\x86\x9b\x45\xa5\xac\x67\xaa\x21\xbf\x71\xe2\x6f\x6b\x95\x22\x89\x16\x83\x1f\x7b\x46\xb6\x85\x07\xe2\x65\xe1\xb4\x65\xc9\xd0\x1d\xdf\x3c\xa4\xdb\x66\x7b\x2e\x26\x3b\x12\xfd\x51\xef\x84\xbf\xfe\x3b\x86\xc2\x73\xcf\x4e\x06\xad\xea\x41\xda\x6d\x43\x05\x9c\xe7\x5e\xd5\x15\xd9\xee\x7c\xbf\x81\x9f\xba\xef\x19\xb5\x62\xb5\x1e\x88\x01\x90\x0c\xdd\xcf\x3c\xf5\x60\x21\xa3\x66\x28\x54\x3f\x27\x75\x7e\x21\x70\x87\x5b\xd9\x61\xe8\xe1\xa4\x3e\x3e\x37\xa7\x9b\xda\x43\x1b\x8f\x1c\x1a\x3c\x49\x6b\x78\x3c\x49\x5a\xff\xd4\x45\xfe\x0c\x00\x00\xff\xff\xd3\x97\x5f\x7a\xeb\x1b\x00\x00") + +func ResourcesComponentDescriptorV2SchemaYamlBytes() ([]byte, error) { + return bindataRead( + _ResourcesComponentDescriptorV2SchemaYaml, + "../../../../../../resources/component-descriptor-v2-schema.yaml", + ) +} + +func ResourcesComponentDescriptorV2SchemaYaml() (*asset, error) { + bytes, err := ResourcesComponentDescriptorV2SchemaYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "../../../../../../resources/component-descriptor-v2-schema.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "../../../../../../resources/component-descriptor-v2-schema.yaml": ResourcesComponentDescriptorV2SchemaYaml, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// +// data/ +// foo.txt +// img/ +// a.png +// b.png +// +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} + +var _bintree = &bintree{nil, map[string]*bintree{ + "..": {nil, map[string]*bintree{ + "..": {nil, map[string]*bintree{ + "..": {nil, map[string]*bintree{ + "..": {nil, map[string]*bintree{ + "..": {nil, map[string]*bintree{ + "..": {nil, map[string]*bintree{ + "resources": {nil, map[string]*bintree{ + "component-descriptor-v2-schema.yaml": {ResourcesComponentDescriptorV2SchemaYaml, map[string]*bintree{}}, + }}, + }}, + }}, + }}, + }}, + }}, + }}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) +} diff --git a/api/ocm/compdesc/versions/v2/jsonscheme/jsonscheme.go b/api/ocm/compdesc/versions/v2/jsonscheme/jsonscheme.go new file mode 100644 index 000000000..d14ff0520 --- /dev/null +++ b/api/ocm/compdesc/versions/v2/jsonscheme/jsonscheme.go @@ -0,0 +1,55 @@ +//go:generate go-bindata -nometadata -pkg jsonscheme ../../../../../../resources/component-descriptor-v2-schema.yaml +//go:generate gofmt -s -w bindata.go + +package jsonscheme + +import ( + "errors" + "fmt" + + "github.com/ghodss/yaml" + "github.com/xeipuuv/gojsonschema" +) + +var Schema *gojsonschema.Schema + +func init() { + dataBytes, err := ResourcesComponentDescriptorV2SchemaYamlBytes() + if err != nil { + panic(err) + } + + data, err := yaml.YAMLToJSON(dataBytes) + if err != nil { + panic(err) + } + + Schema, err = gojsonschema.NewSchema(gojsonschema.NewBytesLoader(data)) + if err != nil { + panic(err) + } +} + +// Validate validates the given data against the component descriptor v2 jsonscheme. +func Validate(src []byte) error { + data, err := yaml.YAMLToJSON(src) + if err != nil { + return err + } + documentLoader := gojsonschema.NewBytesLoader(data) + res, err := Schema.Validate(documentLoader) + if err != nil { + return err + } + + if !res.Valid() { + errs := res.Errors() + errMsg := errs[0].String() + for i := 1; i < len(errs); i++ { + errMsg = fmt.Sprintf("%s;%s", errMsg, errs[i].String()) + } + return errors.New(errMsg) + } + + return nil +} diff --git a/api/ocm/compdesc/versions/v2/signing.go b/api/ocm/compdesc/versions/v2/signing.go new file mode 100644 index 000000000..a3aedc8d6 --- /dev/null +++ b/api/ocm/compdesc/versions/v2/signing.go @@ -0,0 +1,64 @@ +package v2 + +import ( + "encoding/json" + "fmt" + + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/compdesc/normalizations/rules" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/norm/entry" +) + +func providerMapper(v interface{}) interface{} { + var provider map[string]interface{} + err := json.Unmarshal([]byte(v.(string)), &provider) + if err == nil { + return provider + } + return v +} + +// CDExcludes describes the fields relevant for Signing +// ATTENTION: if changed, please adapt the HashEqual Functions +// in the generic part, accordingly. +var CDExcludes = signing.MapExcludes{ + "component": signing.MapExcludes{ + "provider": signing.MapValue{ + Mapping: providerMapper, + Continue: signing.MapExcludes{ + "labels": rules.LabelExcludes, + }, + }, + "labels": rules.LabelExcludes, + "repositoryContexts": nil, + "resources": signing.DefaultedMapFields{ + Next: signing.DynamicArrayExcludes{ + ValueMapper: rules.MapResourcesWithNoneAccess, + Continue: signing.MapExcludes{ + "access": nil, + "srcRefs": nil, + "labels": rules.LabelExcludes, + }, + }, + }.EnforceNull("extraIdentity"), + "sources": nil, + "componentReferences": signing.DefaultedMapFields{ + Next: signing.ArrayExcludes{ + signing.MapExcludes{ + "labels": rules.LabelExcludes, + }, + }, + }.EnforceNull("extraIdentity"), + }, + "signatures": nil, + "nestedDigests": nil, +} + +func (cd *ComponentDescriptor) Normalize(normAlgo string) ([]byte, error) { + if normAlgo != compdesc.JsonNormalisationV1 { + return nil, fmt.Errorf("unsupported cd normalization %q", normAlgo) + } + data, err := signing.Normalize(entry.Type, cd, CDExcludes) + return data, err +} diff --git a/api/ocm/compdesc/versions/v2/validation.go b/api/ocm/compdesc/versions/v2/validation.go new file mode 100644 index 000000000..8760becc0 --- /dev/null +++ b/api/ocm/compdesc/versions/v2/validation.go @@ -0,0 +1,205 @@ +package v2 + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + "k8s.io/apimachinery/pkg/util/validation/field" + + "ocm.software/ocm/api/ocm/compdesc" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" +) + +// Validate validates a parsed v2 component descriptor. +func (cd *ComponentDescriptor) Validate() error { + if err := Validate(nil, cd); err != nil { + return errors.Wrapf(err.ToAggregate(), "%s:%s", cd.Name, cd.Version) + } + return nil +} + +func Validate(fldPath *field.Path, component *ComponentDescriptor) field.ErrorList { + if component == nil { + return nil + } + allErrs := field.ErrorList{} + + if len(component.Metadata.Version) == 0 { + metaPath := field.NewPath("meta").Child("schemaVersion") + if fldPath != nil { + metaPath = fldPath.Child("meta").Child("schemaVersion") + } + allErrs = append(allErrs, field.Required(metaPath, "must specify a version")) + } + + compPath := field.NewPath("component") + if fldPath != nil { + compPath = fldPath.Child("component") + } + + if err := validateProvider(compPath.Child("provider"), component.Provider); err != nil { + allErrs = append(allErrs, err) + } + + allErrs = append(allErrs, ValidateObjectMeta(compPath, component)...) + + srcPath := compPath.Child("sources") + allErrs = append(allErrs, ValidateSources(srcPath, component.Sources)...) + + refPath := compPath.Child("componentReferences") + allErrs = append(allErrs, ValidateComponentReferences(refPath, component.ComponentReferences)...) + + resourcePath := compPath.Child("resources") + allErrs = append(allErrs, ValidateResources(resourcePath, component.Resources, component.GetVersion())...) + + return allErrs +} + +// ValidateObjectMeta Validate the metadata of an object. +func ValidateObjectMeta(fldPath *field.Path, om compdesc.ObjectMetaAccessor) field.ErrorList { + allErrs := field.ErrorList{} + if len(om.GetName()) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("name"), "must specify a name")) + } + if len(om.GetVersion()) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("version"), "must specify a version")) + } + if len(om.GetLabels()) != 0 { + allErrs = append(allErrs, v1.ValidateLabels(fldPath.Child("labels"), om.GetLabels())...) + } + return allErrs +} + +// ValidateSources validates a list of sources. +// It makes sure that no duplicate sources are present. +func ValidateSources(fldPath *field.Path, sources Sources) field.ErrorList { + allErrs := field.ErrorList{} + sourceIDs := make(map[string]struct{}) + for i, src := range sources { + srcPath := fldPath.Index(i) + allErrs = append(allErrs, ValidateSource(srcPath, src, false)...) + + id := src.GetIdentity(sources) + dig := string(id.Digest()) + if _, ok := sourceIDs[dig]; ok { + allErrs = append(allErrs, field.Duplicate(srcPath, fmt.Sprintf("duplicate source %s", id))) + continue + } + sourceIDs[dig] = struct{}{} + } + return allErrs +} + +// ValidateSource validates the a component's source object. +func ValidateSource(fldPath *field.Path, src Source, access bool) field.ErrorList { + allErrs := field.ErrorList{} + if len(src.GetName()) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("name"), "must specify a name")) + } + if len(src.GetType()) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must specify a type")) + } + if src.Access == nil && access { + allErrs = append(allErrs, field.Required(fldPath.Child("access"), "must specify a access")) + } + allErrs = append(allErrs, v1.ValidateIdentity(fldPath.Child("extraIdentity"), src.ExtraIdentity)...) + return allErrs +} + +// ValidateResource validates a components resource. +func ValidateResource(fldPath *field.Path, res Resource, access bool) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, ValidateObjectMeta(fldPath, &res)...) + + if err := v1.ValidateRelation(fldPath.Child("relation"), res.Relation); err != nil { + allErrs = append(allErrs, err) + } + + if !v1.IsIdentity(res.Name) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), res.Name, v1.IdentityKeyValidationErrMsg)) + } + + if len(res.GetType()) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must specify a type")) + } + + if res.Access == nil && access { + allErrs = append(allErrs, field.Required(fldPath.Child("access"), "must specify a access")) + } + allErrs = append(allErrs, v1.ValidateIdentity(fldPath.Child("extraIdentity"), res.ExtraIdentity)...) + return allErrs +} + +func validateProvider(fldPath *field.Path, provider v1.ProviderName) *field.Error { + if len(provider) == 0 { + return field.Required(fldPath, "provider must be set") + } + return nil +} + +// ValidateComponentReference validates a component reference. +func ValidateComponentReference(fldPath *field.Path, cr ComponentReference) field.ErrorList { + allErrs := field.ErrorList{} + if len(cr.ComponentName) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("componentName"), "must specify a component name")) + } + allErrs = append(allErrs, ValidateObjectMeta(fldPath, &cr)...) + return allErrs +} + +// ValidateComponentReferences validates a list of component references. +// It makes sure that no duplicate sources are present. +func ValidateComponentReferences(fldPath *field.Path, refs ComponentReferences) field.ErrorList { + allErrs := field.ErrorList{} + refIDs := make(map[string]struct{}) + for i, ref := range refs { + refPath := fldPath.Index(i) + allErrs = append(allErrs, ValidateComponentReference(refPath, ref)...) + + id := ref.GetIdentity(refs) + dig := string(id.Digest()) + if _, ok := refIDs[dig]; ok { + allErrs = append(allErrs, field.Duplicate(refPath, fmt.Sprintf("duplicate component reference %s", id))) + continue + } + refIDs[dig] = struct{}{} + } + + return allErrs +} + +// ValidateResources validates a list of resources. +// It makes sure that no duplicate sources are present. +func ValidateResources(fldPath *field.Path, resources Resources, componentVersion string) field.ErrorList { + allErrs := field.ErrorList{} + resourceIDs := make(map[string]struct{}) + for i, res := range resources { + localPath := fldPath.Index(i) + allErrs = append(allErrs, ValidateResource(localPath, res, true)...) + + if err := ValidateSourceRefs(localPath.Child("sourceRef"), res.SourceRefs); err != nil { + allErrs = append(allErrs, err...) + } + + id := res.GetIdentity(resources) + dig := string(id.Digest()) + if _, ok := resourceIDs[dig]; ok { + allErrs = append(allErrs, field.Duplicate(localPath, fmt.Sprintf("duplicate resource %s", id))) + continue + } + resourceIDs[dig] = struct{}{} + } + return allErrs +} + +func ValidateSourceRefs(fldPath *field.Path, srcs []SourceRef) field.ErrorList { + allErrs := field.ErrorList{} + for i, src := range srcs { + localPath := fldPath.Index(i) + if err := v1.ValidateLabels(localPath.Child("labels"), src.Labels); err != nil { + allErrs = append(allErrs, err...) + } + } + + return allErrs +} diff --git a/api/ocm/compdesc/versions/v2/validation_test.go b/api/ocm/compdesc/versions/v2/validation_test.go new file mode 100644 index 000000000..471a9b0b0 --- /dev/null +++ b/api/ocm/compdesc/versions/v2/validation_test.go @@ -0,0 +1,477 @@ +package v2_test + +import ( + "encoding/json" + "fmt" + "strings" + "testing" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gstruct" + . "ocm.software/ocm/api/ocm/compdesc/versions/v2" + + "k8s.io/apimachinery/pkg/util/validation/field" + + "ocm.software/ocm/api/ocm/compdesc" + meta "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/compdesc/testutils" + "ocm.software/ocm/api/ocm/compdesc/versions/v2/jsonscheme" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + "ocm.software/ocm/api/utils/runtime" +) + +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "V2 Test Suite") +} + +var _ = Describe("Validation", func() { + testutils.TestCompName(jsonscheme.ResourcesComponentDescriptorV2SchemaYamlBytes()) + + Context("validator", func() { + var ( + comp *ComponentDescriptor + + ociImage1 *Resource + ociRegistry1 *ociartifact.AccessSpec + ociImage2 *Resource + ociRegistry2 *ociartifact.AccessSpec + ) + + BeforeEach(func() { + ociRegistry1 = ociartifact.New("docker/image1:1.2.3") + + unstrucOCIRegistry1, err := runtime.ToUnstructuredTypedObject(ociRegistry1) + Expect(err).ToNot(HaveOccurred()) + + ociImage1 = &Resource{ + ElementMeta: ElementMeta{ + Name: "image1", + Version: "1.2.3", + }, + Relation: meta.ExternalRelation, + Access: unstrucOCIRegistry1, + } + ociRegistry2 = ociartifact.New("docker/image1:1.2.3") + unstrucOCIRegistry2, err := runtime.ToUnstructuredTypedObject(ociRegistry2) + Expect(err).ToNot(HaveOccurred()) + ociImage2 = &Resource{ + ElementMeta: ElementMeta{ + Name: "image2", + Version: "1.2.3", + }, + Relation: meta.ExternalRelation, + Access: unstrucOCIRegistry2, + } + + comp = &ComponentDescriptor{ + Metadata: meta.Metadata{ + Version: SchemaVersion, + }, + ComponentSpec: ComponentSpec{ + ObjectMeta: ObjectMeta{ + Name: "my-comp", + Version: "1.2.3", + }, + Provider: "external", + RepositoryContexts: nil, + Sources: nil, + ComponentReferences: nil, + Resources: []Resource{*ociImage1, *ociImage2}, + }, + } + }) + + Context("#Metadata", func() { + It("should forbid if the component schemaVersion is missing", func() { + comp := ComponentDescriptor{ + Metadata: meta.Metadata{}, + } + + errList := Validate(nil, &comp) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeRequired), + "Field": Equal("meta.schemaVersion"), + })))) + }) + + It("should pass if the component schemaVersion is defined", func() { + errList := Validate(nil, comp) + Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeRequired), + "Field": Equal("meta.schemaVersion"), + })))) + }) + }) + + Context("#ObjectMeta", func() { + It("should forbid if the component's version is missing", func() { + comp := ComponentDescriptor{} + errList := Validate(nil, &comp) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeRequired), + "Field": Equal("component.name"), + })))) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeRequired), + "Field": Equal("component.version"), + })))) + }) + + It("should forbid if the component's name is missing", func() { + comp := ComponentDescriptor{} + errList := Validate(nil, &comp) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeRequired), + "Field": Equal("component.name"), + })))) + }) + }) + + Context("#Sources", func() { + It("should forbid if a duplicated component's source is defined", func() { + comp.Sources = []Source{ + { + SourceMeta: SourceMeta{ + ElementMeta: ElementMeta{ + Name: "a", + }, + }, + Access: runtime.NewEmptyUnstructured("custom"), + }, + { + SourceMeta: SourceMeta{ + ElementMeta: ElementMeta{ + Name: "a", + }, + }, + Access: runtime.NewEmptyUnstructured("custom"), + }, + } + errList := Validate(nil, comp) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeDuplicate), + "Field": Equal("component.sources[1]"), + })))) + }) + }) + + Context("#ComponentReferences", func() { + It("should pass if a reference is set", func() { + comp.ComponentReferences = []ComponentReference{ + { + ElementMeta: ElementMeta{ + Name: "test", + Version: "1.2.3", + }, + ComponentName: "test", + }, + } + errList := Validate(nil, comp) + Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeRequired), + "Field": Equal("component.componentReferences[0].name"), + })))) + Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeRequired), + "Field": Equal("component.componentReferences[0].version"), + })))) + }) + + It("should forbid if a reference's name is missing", func() { + comp.ComponentReferences = []ComponentReference{ + { + ElementMeta: ElementMeta{ + Version: "1.2.3", + }, + ComponentName: "test", + }, + } + errList := Validate(nil, comp) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeRequired), + "Field": Equal("component.componentReferences[0].name"), + })))) + }) + + It("should forbid if a reference's component name is missing", func() { + comp.ComponentReferences = []ComponentReference{ + { + ElementMeta: ElementMeta{ + Name: "test", + Version: "1.2.3", + }, + }, + } + errList := Validate(nil, comp) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeRequired), + "Field": Equal("component.componentReferences[0].componentName"), + })))) + }) + + It("should forbid if a reference's version is missing", func() { + comp.ComponentReferences = []ComponentReference{ + { + ElementMeta: ElementMeta{ + Name: "test", + }, + ComponentName: "test", + }, + } + errList := Validate(nil, comp) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeRequired), + "Field": Equal("component.componentReferences[0].version"), + })))) + }) + + It("should forbid if a duplicated component reference is defined", func() { + comp.ComponentReferences = []ComponentReference{ + { + ElementMeta: ElementMeta{ + Name: "test", + }, + }, + { + ElementMeta: ElementMeta{ + Name: "test", + }, + }, + } + errList := Validate(nil, comp) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeDuplicate), + "Field": Equal("component.componentReferences[1]"), + })))) + }) + }) + + Context("#Resources", func() { + It("should handle srcRefs", func() { + comp.Name = "acme.org/test" + comp.RepositoryContexts = []*runtime.UnstructuredTypedObject{} + comp.ComponentReferences = ComponentReferences{} + comp.Sources = Sources{} + comp.Resources = []Resource{ + { + ElementMeta: ElementMeta{ + Name: "test", + Version: "v1", + }, + Type: "test", + Relation: meta.LocalRelation, + Access: runtime.NewEmptyUnstructured("access"), + SourceRefs: []SourceRef{ + { + IdentitySelector: map[string]string{ + "name": "test", + }, + Labels: nil, + }, + }, + }, + } + v := &DescriptorVersion{} + data := Must(json.Marshal(comp)) + fmt.Printf("%s\n", string(data)) + r := Must(v.Decode(data, &compdesc.DecodeOptions{Codec: compdesc.DefaultYAMLCodec})) + Expect(r).To(DeepEqual(comp)) + + old := strings.Replace(string(data), "srcRefs", "srcRef", -1) + r = Must(v.Decode([]byte(old), &compdesc.DecodeOptions{Codec: compdesc.DefaultYAMLCodec})) + Expect(v.ConvertTo(r)).To(DeepEqual(Must(v.ConvertTo(comp)))) + }) + + It("should forbid if a resource name contains invalid characters", func() { + comp.Resources = []Resource{ + { + ElementMeta: ElementMeta{ + Name: "test$", + }, + }, + { + ElementMeta: ElementMeta{ + Name: "testšŸ™…", + }, + }, + } + errList := Validate(nil, comp) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeInvalid), + "Field": Equal("component.resources[0].name"), + })))) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeInvalid), + "Field": Equal("component.resources[1].name"), + })))) + }) + + It("should forbid if a duplicated local resource is defined", func() { + comp.Resources = []Resource{ + { + ElementMeta: ElementMeta{ + Name: "test", + }, + }, + { + ElementMeta: ElementMeta{ + Name: "test", + }, + }, + } + errList := Validate(nil, comp) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeDuplicate), + "Field": Equal("component.resources[1]"), + })))) + }) + + It("should forbid if a duplicated resource with additional identity labels is defined", func() { + comp.Resources = []Resource{ + { + ElementMeta: ElementMeta{ + Name: "test", + ExtraIdentity: meta.Identity{ + "my-id": "some-id", + }, + }, + }, + { + ElementMeta: ElementMeta{ + Name: "test", + ExtraIdentity: meta.Identity{ + "my-id": "some-id", + }, + }, + }, + } + errList := Validate(nil, comp) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeDuplicate), + "Field": Equal("component.resources[1]"), + })))) + }) + + It("should pass if a duplicated resource has the same name but with different additional identity labels", func() { + comp.Resources = []Resource{ + { + ElementMeta: ElementMeta{ + Name: "test", + ExtraIdentity: meta.Identity{ + "my-id": "some-id", + }, + }, + }, + { + ElementMeta: ElementMeta{ + Name: "test", + }, + }, + } + errList := Validate(nil, comp) + Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeDuplicate), + "Field": Equal("component.resources[1]"), + })))) + Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeDuplicate), + "Field": Equal("component.resources[0]"), + })))) + }) + }) + + Context("#labels", func() { + It("should forbid if labels are defined multiple times in the same context", func() { + comp.ComponentReferences = []ComponentReference{ + { + ElementMeta: ElementMeta{ + Name: "test", + Version: "1.2.3", + Labels: []meta.Label{ + { + Name: "l1", + Value: []byte{}, + }, + { + Name: "l1", + Value: []byte{}, + }, + }, + }, + ComponentName: "test", + }, + } + + errList := Validate(nil, comp) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeDuplicate), + "Field": Equal("component.componentReferences[0].labels[1]"), + })))) + }) + + It("should pass if labels are defined multiple times in the same context with differnet names", func() { + comp.ComponentReferences = []ComponentReference{ + { + ElementMeta: ElementMeta{ + Name: "test", + Version: "1.2.3", + Labels: []meta.Label{ + { + Name: "l1", + Value: []byte{}, + }, + { + Name: "l2", + Value: []byte{}, + }, + }, + }, + ComponentName: "test", + }, + } + + errList := Validate(nil, comp) + Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeDuplicate), + "Field": Equal("component.componentReferences[0].labels[1]"), + })))) + }) + }) + + Context("#Identity", func() { + It("should pass valid identity labels", func() { + identity := meta.Identity{ + "my-l1": "test", + "my-l2": "test", + } + errList := meta.ValidateIdentity(field.NewPath("identity"), identity) + Expect(errList).To(HaveLen(0)) + }) + + It("should forbid if a identity label define the name", func() { + identity := meta.Identity{ + "name": "test", + } + errList := meta.ValidateIdentity(field.NewPath("identity"), identity) + Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeForbidden), + "Field": Equal("identity[name]"), + })))) + }) + + It("should forbid if a identity label defines a key with invalid characters", func() { + identity := meta.Identity{ + "my-l1!": "test", + } + errList := meta.ValidateIdentity(field.NewPath("identity"), identity) + Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeForbidden), + "Field": Equal("identity[my-l1!]"), + })))) + }) + }) + }) +}) diff --git a/api/ocm/compdesc/versions/v2/version.go b/api/ocm/compdesc/versions/v2/version.go new file mode 100644 index 000000000..3d2ea687b --- /dev/null +++ b/api/ocm/compdesc/versions/v2/version.go @@ -0,0 +1,332 @@ +package v2 + +import ( + "encoding/json" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/compdesc/versions/v2/jsonscheme" + "ocm.software/ocm/api/utils/runtime" +) + +const SchemaVersion = "v2" + +func init() { + compdesc.RegisterScheme(&DescriptorVersion{}) +} + +type DescriptorVersion struct{} + +var _ compdesc.Scheme = (*DescriptorVersion)(nil) + +func (v *DescriptorVersion) GetVersion() string { + return SchemaVersion +} + +func (v *DescriptorVersion) Decode(data []byte, opts *compdesc.DecodeOptions) (compdesc.ComponentDescriptorVersion, error) { + var cd ComponentDescriptor + if !opts.DisableValidation { + if err := jsonscheme.Validate(data); err != nil { + return nil, err + } + } + var err error + if opts.StrictMode { + err = opts.Codec.DecodeStrict(data, &cd) + } else { + err = opts.Codec.Decode(data, &cd) + } + if err != nil { + return nil, err + } + + if err := cd.Default(); err != nil { + return nil, err + } + + if !opts.DisableValidation { + err = cd.Validate() + if err != nil { + return nil, err + } + } + return &cd, err +} + +//////////////////////////////////////////////////////////////////////////////// +// convert to internal version +//////////////////////////////////////////////////////////////////////////////// + +func (v *DescriptorVersion) ConvertTo(obj compdesc.ComponentDescriptorVersion) (out *compdesc.ComponentDescriptor, err error) { + if obj == nil { + return nil, nil + } + in, ok := obj.(*ComponentDescriptor) + if !ok { + return nil, errors.Newf("%T is no version v2 descriptor", obj) + } + + defer compdesc.CatchConversionError(&err) + var provider metav1.Provider + err = json.Unmarshal([]byte(in.Provider), &provider) + if err != nil { + provider.Name = in.Provider + provider.Labels = nil + } + + out = &compdesc.ComponentDescriptor{ + Metadata: compdesc.Metadata{ConfiguredVersion: in.Metadata.Version}, + ComponentSpec: compdesc.ComponentSpec{ + ObjectMeta: metav1.ObjectMeta{ + Name: in.Name, + Version: in.Version, + Labels: in.Labels.Copy(), + Provider: provider, + CreationTime: in.CreationTime, + }, + RepositoryContexts: in.RepositoryContexts.Copy(), + Sources: convertSourcesTo(in.Sources), + Resources: convertResourcesTo(in.Resources), + References: convertComponentReferencesTo(in.ComponentReferences), + }, + Signatures: in.Signatures.Copy(), + NestedDigests: in.NestedDigests.Copy(), + } + return out, nil +} + +func convertComponentReferenceTo(in ComponentReference) compdesc.ComponentReference { + return compdesc.ComponentReference{ + ElementMeta: convertElementMetaTo(in.ElementMeta), + ComponentName: in.ComponentName, + Digest: in.Digest.Copy(), + } +} + +func convertComponentReferencesTo(in []ComponentReference) compdesc.References { + if in == nil { + return nil + } + out := make(compdesc.References, len(in)) + for i := range in { + out[i] = convertComponentReferenceTo(in[i]) + } + return out +} + +func convertSourceTo(in Source) compdesc.Source { + return compdesc.Source{ + SourceMeta: compdesc.SourceMeta{ + ElementMeta: convertElementMetaTo(in.ElementMeta), + Type: in.Type, + }, + Access: compdesc.GenericAccessSpec(in.Access.DeepCopy()), + } +} + +func convertSourcesTo(in Sources) compdesc.Sources { + if in == nil { + return nil + } + out := make(compdesc.Sources, len(in)) + for i := range in { + out[i] = convertSourceTo(in[i]) + } + return out +} + +func convertElementMetaTo(in ElementMeta) compdesc.ElementMeta { + return compdesc.ElementMeta{ + Name: in.Name, + Version: in.Version, + ExtraIdentity: in.ExtraIdentity.Copy(), + Labels: in.Labels.Copy(), + } +} + +func convertResourceTo(in Resource) compdesc.Resource { + srcRefs := ConvertSourcerefsTo(in.SourceRefs) + if srcRefs == nil { + srcRefs = ConvertSourcerefsTo(in.SourceRef) + } + return compdesc.Resource{ + ResourceMeta: compdesc.ResourceMeta{ + ElementMeta: convertElementMetaTo(in.ElementMeta), + Type: in.Type, + Relation: in.Relation, + SourceRefs: srcRefs, + Digest: in.Digest.Copy(), + }, + Access: compdesc.GenericAccessSpec(in.Access), + } +} + +func convertResourcesTo(in Resources) compdesc.Resources { + if in == nil { + return nil + } + out := make(compdesc.Resources, len(in)) + for i := range in { + out[i] = convertResourceTo(in[i]) + } + return out +} + +func convertSourceRefTo(in SourceRef) compdesc.SourceRef { + return compdesc.SourceRef{ + IdentitySelector: in.IdentitySelector.Copy(), + Labels: in.Labels.Copy(), + } +} + +func ConvertSourcerefsTo(in []SourceRef) []compdesc.SourceRef { + if in == nil { + return nil + } + out := make([]compdesc.SourceRef, len(in)) + for i := range in { + out[i] = convertSourceRefTo(in[i]) + } + return out +} + +//////////////////////////////////////////////////////////////////////////////// +// convert from internal version +//////////////////////////////////////////////////////////////////////////////// + +func (v *DescriptorVersion) ConvertFrom(in *compdesc.ComponentDescriptor) (compdesc.ComponentDescriptorVersion, error) { + if in == nil { + return nil, nil + } + provider := in.Provider.Name + if len(in.Provider.Labels) != 0 { + data, err := json.Marshal(in.Provider) + if err != nil { + return nil, errors.Wrapf(err, "cannot marshal provider") + } + provider = metav1.ProviderName(data) + } + out := &ComponentDescriptor{ + Metadata: metav1.Metadata{ + Version: SchemaVersion, + }, + ComponentSpec: ComponentSpec{ + ObjectMeta: ObjectMeta{ + Name: in.Name, + Version: in.Version, + Labels: in.Labels.Copy(), + CreationTime: in.CreationTime, + }, + RepositoryContexts: in.RepositoryContexts.Copy(), + Provider: provider, + Sources: convertSourcesFrom(in.Sources), + Resources: convertResourcesFrom(in.Resources), + ComponentReferences: convertComponentReferencesFrom(in.References), + }, + Signatures: in.Signatures.Copy(), + NestedDigests: in.NestedDigests.Copy(), + } + if err := out.Default(); err != nil { + return nil, err + } + return out, nil +} + +func convertComponentReferenceFrom(in compdesc.ComponentReference) ComponentReference { + return ComponentReference{ + ElementMeta: convertElementMetaFrom(in.ElementMeta), + ComponentName: in.ComponentName, + Digest: in.Digest.Copy(), + } +} + +func convertComponentReferencesFrom(in []compdesc.ComponentReference) []ComponentReference { + if in == nil { + return nil + } + out := make([]ComponentReference, len(in)) + for i := range in { + out[i] = convertComponentReferenceFrom(in[i]) + } + return out +} + +func convertSourceFrom(in compdesc.Source) Source { + acc, err := runtime.ToUnstructuredTypedObject(in.Access) + if err != nil { + compdesc.ThrowConversionError(err) + } + return Source{ + SourceMeta: SourceMeta{ + ElementMeta: convertElementMetaFrom(in.ElementMeta), + Type: in.Type, + }, + Access: acc, + } +} + +func convertSourcesFrom(in compdesc.Sources) Sources { + if in == nil { + return nil + } + out := make(Sources, len(in)) + for i := range in { + out[i] = convertSourceFrom(in[i]) + } + return out +} + +func convertElementMetaFrom(in compdesc.ElementMeta) ElementMeta { + return ElementMeta{ + Name: in.Name, + Version: in.Version, + ExtraIdentity: in.ExtraIdentity.Copy(), + Labels: in.Labels.Copy(), + } +} + +func convertResourceFrom(in compdesc.Resource) Resource { + acc, err := runtime.ToUnstructuredTypedObject(in.Access) + if err != nil { + compdesc.ThrowConversionError(err) + } + return Resource{ + ElementMeta: convertElementMetaFrom(in.ElementMeta), + Type: in.Type, + Relation: in.Relation, + SourceRefs: convertSourceRefsFrom(in.SourceRefs), + Access: acc, + Digest: in.Digest.Copy(), + } +} + +func convertResourcesFrom(in compdesc.Resources) Resources { + if in == nil { + return nil + } + out := make(Resources, len(in)) + for i := range in { + out[i] = convertResourceFrom(in[i]) + } + return out +} + +func convertSourceRefFrom(in compdesc.SourceRef) SourceRef { + return SourceRef{ + IdentitySelector: in.IdentitySelector.Copy(), + Labels: in.Labels.Copy(), + } +} + +func convertSourceRefsFrom(in []compdesc.SourceRef) []SourceRef { + if in == nil { + return nil + } + out := make([]SourceRef, len(in)) + for i := range in { + out[i] = convertSourceRefFrom(in[i]) + } + return out +} diff --git a/api/ocm/compdesc/versions/v2/zz_generated.deepcopy.go b/api/ocm/compdesc/versions/v2/zz_generated.deepcopy.go new file mode 100644 index 000000000..4ed4950e7 --- /dev/null +++ b/api/ocm/compdesc/versions/v2/zz_generated.deepcopy.go @@ -0,0 +1,266 @@ +//go:build !ignore_autogenerated + +// Code generated by controller-gen. DO NOT EDIT. + +package v2 + +import ( + "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/utils/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ComponentDescriptor) DeepCopyInto(out *ComponentDescriptor) { + *out = *in + out.Metadata = in.Metadata + in.ComponentSpec.DeepCopyInto(&out.ComponentSpec) + if in.Signatures != nil { + in, out := &in.Signatures, &out.Signatures + *out = make(v1.Signatures, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.NestedDigests != nil { + in, out := &in.NestedDigests, &out.NestedDigests + *out = make(v1.NestedDigests, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentDescriptor. +func (in *ComponentDescriptor) DeepCopy() *ComponentDescriptor { + if in == nil { + return nil + } + out := new(ComponentDescriptor) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ComponentReference) DeepCopyInto(out *ComponentReference) { + *out = *in + in.ElementMeta.DeepCopyInto(&out.ElementMeta) + if in.Digest != nil { + in, out := &in.Digest, &out.Digest + *out = new(v1.DigestSpec) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentReference. +func (in *ComponentReference) DeepCopy() *ComponentReference { + if in == nil { + return nil + } + out := new(ComponentReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ComponentSpec) DeepCopyInto(out *ComponentSpec) { + *out = *in + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.RepositoryContexts != nil { + in, out := &in.RepositoryContexts, &out.RepositoryContexts + *out = make(runtime.UnstructuredTypedObjectList, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Sources != nil { + in, out := &in.Sources, &out.Sources + *out = make(Sources, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ComponentReferences != nil { + in, out := &in.ComponentReferences, &out.ComponentReferences + *out = make(ComponentReferences, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = make(Resources, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentSpec. +func (in *ComponentSpec) DeepCopy() *ComponentSpec { + if in == nil { + return nil + } + out := new(ComponentSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ElementMeta) DeepCopyInto(out *ElementMeta) { + *out = *in + if in.ExtraIdentity != nil { + in, out := &in.ExtraIdentity, &out.ExtraIdentity + *out = make(v1.Identity, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(v1.Labels, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ElementMeta. +func (in *ElementMeta) DeepCopy() *ElementMeta { + if in == nil { + return nil + } + out := new(ElementMeta) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectMeta) DeepCopyInto(out *ObjectMeta) { + *out = *in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(v1.Labels, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.CreationTime != nil { + in, out := &in.CreationTime, &out.CreationTime + *out = new(v1.Timestamp) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectMeta. +func (in *ObjectMeta) DeepCopy() *ObjectMeta { + if in == nil { + return nil + } + out := new(ObjectMeta) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Resource) DeepCopyInto(out *Resource) { + *out = *in + in.ElementMeta.DeepCopyInto(&out.ElementMeta) + if in.SourceRefs != nil { + in, out := &in.SourceRefs, &out.SourceRefs + *out = make([]SourceRef, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.SourceRef != nil { + in, out := &in.SourceRef, &out.SourceRef + *out = make([]SourceRef, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Access != nil { + in, out := &in.Access, &out.Access + *out = (*in).DeepCopy() + } + if in.Digest != nil { + in, out := &in.Digest, &out.Digest + *out = new(v1.DigestSpec) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Resource. +func (in *Resource) DeepCopy() *Resource { + if in == nil { + return nil + } + out := new(Resource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Source) DeepCopyInto(out *Source) { + *out = *in + in.SourceMeta.DeepCopyInto(&out.SourceMeta) + if in.Access != nil { + in, out := &in.Access, &out.Access + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Source. +func (in *Source) DeepCopy() *Source { + if in == nil { + return nil + } + out := new(Source) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SourceMeta) DeepCopyInto(out *SourceMeta) { + *out = *in + in.ElementMeta.DeepCopyInto(&out.ElementMeta) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceMeta. +func (in *SourceMeta) DeepCopy() *SourceMeta { + if in == nil { + return nil + } + out := new(SourceMeta) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SourceRef) DeepCopyInto(out *SourceRef) { + *out = *in + if in.IdentitySelector != nil { + in, out := &in.IdentitySelector, &out.IdentitySelector + *out = make(v1.StringMap, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(v1.Labels, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceRef. +func (in *SourceRef) DeepCopy() *SourceRef { + if in == nil { + return nil + } + out := new(SourceRef) + in.DeepCopyInto(out) + return out +} diff --git a/api/ocm/config/config_test.go b/api/ocm/config/config_test.go new file mode 100644 index 000000000..d9b87df26 --- /dev/null +++ b/api/ocm/config/config_test.go @@ -0,0 +1,79 @@ +package config_test + +import ( + "encoding/json" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/ocm/config" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" +) + +func normalize(i interface{}) ([]byte, error) { + data, err := json.Marshal(i) + if err != nil { + return nil, err + } + var generic map[string]interface{} + err = json.Unmarshal(data, &generic) + if err != nil { + return nil, err + } + return json.Marshal(generic) +} + +var _ = Describe("oci config", func() { + spec := ocireg.NewRepositorySpec("gcr.io", nil) + data, err := normalize(spec) + Expect(err).To(Succeed()) + + specdata := "{\"aliases\":{\"alias\":" + string(data) + "},\"type\":\"" + config.ConfigType + "\"}" + + Context("serialize", func() { + It("serializes config", func() { + cfg := config.New() + err := cfg.SetAlias("alias", spec) + Expect(err).To(Succeed()) + + data, err := normalize(cfg) + + Expect(err).To(Succeed()) + Expect(data).To(Equal([]byte(specdata))) + + cfg2 := config.New() + err = json.Unmarshal(data, cfg2) + Expect(err).To(Succeed()) + Expect(cfg2).To(Equal(cfg)) + }) + }) + + Context("apply", func() { + It("applies directly", func() { + ctx := cpi.New() + + cfg := config.New() + err := cfg.SetAlias("alias", spec) + Expect(err).To(Succeed()) + + Expect(cfg.ApplyTo(ctx.ConfigContext(), ctx)).To(Succeed()) + + found := ctx.GetAlias("alias") + Expect(found).To(Equal(cfg.Aliases["alias"])) + }) + + It("applies via config context", func() { + ctx := cpi.New() + + cfg := config.New() + err := cfg.SetAlias("alias", spec) + Expect(err).To(Succeed()) + + Expect(ctx.ConfigContext().ApplyConfig(cfg, "programmatic")).To(Succeed()) + + found := ctx.GetAlias("alias") + Expect(found).To(Equal(cfg.Aliases["alias"])) + }) + }) +}) diff --git a/pkg/contexts/ocm/config/suite_test.go b/api/ocm/config/suite_test.go similarity index 100% rename from pkg/contexts/ocm/config/suite_test.go rename to api/ocm/config/suite_test.go diff --git a/api/ocm/config/type.go b/api/ocm/config/type.go new file mode 100644 index 000000000..9a951286b --- /dev/null +++ b/api/ocm/config/type.go @@ -0,0 +1,137 @@ +package config + +import ( + "ocm.software/ocm/api/config" + cfgcpi "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ConfigType = "ocm" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX + ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" +) + +func init() { + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1)) +} + +// Config describes a memory based config interface. +type Config struct { + runtime.ObjectVersionedType `json:",inline"` + Aliases map[string]*cpi.GenericRepositorySpec `json:"aliases,omitempty"` + Resolver []ResolverRule `json:"resolvers,omitempty"` +} + +type ResolverRule struct { + Prefix string `json:"prefix,omitempty"` + Prio *int `json:"priority,omitempty"` + Spec *cpi.GenericRepositorySpec `json:"repository"` +} + +// New creates a new memory ConfigSpec. +func New() *Config { + return &Config{ + ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), + } +} + +func (a *Config) GetType() string { + return ConfigType +} + +func (a *Config) SetAlias(name string, spec cpi.RepositorySpec) error { + g, err := cpi.ToGenericRepositorySpec(spec) + if err != nil { + return err + } + if a.Aliases == nil { + a.Aliases = map[string]*cpi.GenericRepositorySpec{} + } + a.Aliases[name] = g + return nil +} + +func (a *Config) AddResolverRule(prefix string, spec cpi.RepositorySpec, prio ...int) error { + gen, err := cpi.ToGenericRepositorySpec(spec) + if err != nil { + return err + } + + r := ResolverRule{ + Prefix: prefix, + Spec: gen, + } + if len(prio) > 0 { + p := prio[0] + r.Prio = &p + } + + a.Resolver = append(a.Resolver, r) + return nil +} + +func (a *Config) ApplyTo(ctx config.Context, target interface{}) error { + t, ok := target.(cpi.Context) + if !ok { + return config.ErrNoContext(ConfigType) + } + for n, s := range a.Aliases { + t.SetAlias(n, s) + } + + if len(a.Resolver) > 0 { + for _, rule := range a.Resolver { + if rule.Prio != nil { + t.AddResolverRule(rule.Prefix, rule.Spec, *rule.Prio) + } else { + t.AddResolverRule(rule.Prefix, rule.Spec) + } + } + } + return nil +} + +const usage = ` +The config type ` + ConfigType + ` can be used to set some +configurations for an OCM context; + +
+    type: ` + ConfigType + `
+    aliases:
+       myrepo: 
+          type: <any repository type>
+          <specification attributes>
+          ...
+    resolvers:
+      - repository:
+          type: <any repository type>
+          <specification attributes>
+          ...
+        prefix: ghcr.io/open-component-model/ocm
+        priority: 10
+
+ +With aliases repository alias names can be mapped to a repository specification. +The alias name can be used in a string notation for an OCM repository. + +Resolvers define a list of OCM repository specifications to be used to resolve +dedicated component versions. These settings are used to compose a standard +component version resolver provided for an OCM context. Optionally, a component +name prefix can be given. It limits the usage of the repository to resolve only +components with the given name prefix (always complete name segments). +An optional priority can be used to influence the lookup order. Larger value +means higher priority (default 10). + +All matching entries are tried to lookup a component version in the following +order: +- highest priority first +- longest matching sequence of component name segments first. + +If resolvers are defined, it is possible to use component version names on the +command line without a repository. The names are resolved with the specified +resolution rule. +They are also used as default lookup repositories to lookup component references +for recursive operations on component versions (--lookup option). +` diff --git a/api/ocm/consts/deprecated.go b/api/ocm/consts/deprecated.go new file mode 100644 index 000000000..07403f4e8 --- /dev/null +++ b/api/ocm/consts/deprecated.go @@ -0,0 +1,18 @@ +package consts + +import ( + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extraid" +) + +const ( + // Deprecated: use extraid.SystemIdentityName. + SystemIdentityName = metav1.SystemIdentityName + // Deprecated: use extraid.SystemIdentityVersion . + SystemIdentityVersion = metav1.SystemIdentityVersion + + // Deprecated: use extraid.ExecutableOperatingSystem . + ExecutableOperatingSystem = extraid.ExecutableOperatingSystem + // Deprecated: use extraid.ExecutableArchitecture . + ExecutableArchitecture = extraid.ExecutableArchitecture +) diff --git a/pkg/contexts/ocm/cpi/README.md b/api/ocm/cpi/README.md similarity index 100% rename from pkg/contexts/ocm/cpi/README.md rename to api/ocm/cpi/README.md diff --git a/pkg/contexts/ocm/cpi/accspeccpi/accessspec_options.go b/api/ocm/cpi/accspeccpi/accessspec_options.go similarity index 75% rename from pkg/contexts/ocm/cpi/accspeccpi/accessspec_options.go rename to api/ocm/cpi/accspeccpi/accessspec_options.go index e9d2cd613..c0a4bcbb0 100644 --- a/pkg/contexts/ocm/cpi/accspeccpi/accessspec_options.go +++ b/api/ocm/cpi/accspeccpi/accessspec_options.go @@ -1,8 +1,8 @@ package accspeccpi import ( - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets/flagsetscheme" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/api/utils/cobrautils/flagsets/flagsetscheme" ) type AccessSpecTypeOption = flagsetscheme.TypeOption diff --git a/api/ocm/cpi/accspeccpi/accesstypes.go b/api/ocm/cpi/accspeccpi/accesstypes.go new file mode 100644 index 000000000..e71fcc9b8 --- /dev/null +++ b/api/ocm/cpi/accspeccpi/accesstypes.go @@ -0,0 +1,44 @@ +package accspeccpi + +import ( + "ocm.software/ocm/api/utils/cobrautils/flagsets/flagsetscheme" + "ocm.software/ocm/api/utils/runtime" +) + +type AccessTypeVersionScheme = runtime.TypeVersionScheme[AccessSpec, AccessType] + +func NewAccessTypeVersionScheme(kind string) AccessTypeVersionScheme { + return runtime.NewTypeVersionScheme[AccessSpec, AccessType](kind, newStrictAccessTypeScheme()) +} + +func RegisterAccessType(atype AccessType) { + defaultAccessTypeScheme.Register(atype) +} + +func RegisterAccessTypeVersions(s AccessTypeVersionScheme) { + defaultAccessTypeScheme.AddKnownTypes(s) +} + +//////////////////////////////////////////////////////////////////////////////// + +type AccessSpecFormatVersionRegistry = runtime.FormatVersionRegistry[AccessSpec] + +func NewAccessSpecFormatVersionRegistry() AccessSpecFormatVersionRegistry { + return runtime.NewFormatVersionRegistry[AccessSpec]() +} + +func MustNewAccessSpecMultiFormatVersion(kind string, formats AccessSpecFormatVersionRegistry) runtime.FormatVersion[AccessSpec] { + return runtime.MustNewMultiFormatVersion[AccessSpec](kind, formats) +} + +func NewAccessSpecType[I AccessSpec](name string, opts ...AccessSpecTypeOption) AccessType { + return flagsetscheme.NewTypedObjectTypeObject[AccessSpec](runtime.NewVersionedTypedObjectType[AccessSpec, I](name), opts...) +} + +func NewAccessSpecTypeByConverter[I AccessSpec, V runtime.VersionedTypedObject](name string, converter runtime.Converter[I, V], opts ...AccessSpecTypeOption) AccessType { + return flagsetscheme.NewTypedObjectTypeObject[AccessSpec](runtime.NewVersionedTypedObjectTypeByConverter[AccessSpec, I, V](name, converter), opts...) +} + +func NewAccessSpecTypeByFormatVersion(name string, fmt runtime.FormatVersion[AccessSpec], opts ...AccessSpecTypeOption) AccessType { + return flagsetscheme.NewTypedObjectTypeObject[AccessSpec](runtime.NewVersionedTypedObjectTypeByFormatVersion[AccessSpec](name, fmt), opts...) +} diff --git a/api/ocm/cpi/accspeccpi/interface.go b/api/ocm/cpi/accspeccpi/interface.go new file mode 100644 index 000000000..2334058bd --- /dev/null +++ b/api/ocm/cpi/accspeccpi/interface.go @@ -0,0 +1,33 @@ +package accspeccpi + +import ( + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/ocm/internal" +) + +type ( + Context = internal.Context + ContextProvider = internal.ContextProvider + + AccessType = internal.AccessType + + AccessMethodImpl = internal.AccessMethodImpl + AccessMethod = internal.AccessMethod + AccessSpec = internal.AccessSpec + AccessSpecRef = internal.AccessSpecRef + + HintProvider = internal.HintProvider + GlobalAccessProvider = internal.GlobalAccessProvider + CosumerIdentityProvider = credentials.ConsumerIdentityProvider + + ComponentVersionAccess = internal.ComponentVersionAccess +) + +var ( + newStrictAccessTypeScheme = internal.NewStrictAccessTypeScheme + defaultAccessTypeScheme = internal.DefaultAccessTypeScheme +) + +func NewAccessSpecRef(spec AccessSpec) *AccessSpecRef { + return internal.NewAccessSpecRef(spec) +} diff --git a/api/ocm/cpi/accspeccpi/method.go b/api/ocm/cpi/accspeccpi/method.go new file mode 100644 index 000000000..84f077c00 --- /dev/null +++ b/api/ocm/cpi/accspeccpi/method.go @@ -0,0 +1,129 @@ +package accspeccpi + +import ( + "io" + "sync" + + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +//////////////////////////////////////////////////////////////////////////////// + +type DefaultAccessMethodImpl struct { + lock sync.Mutex + blob blobaccess.BlobAccess + + factory BlobAccessFactory + comp ComponentVersionAccess + spec AccessSpec + mime string + digest digest.Digest + local bool +} + +var ( + _ AccessMethodImpl = (*DefaultAccessMethodImpl)(nil) + _ blobaccess.DigestSource = (*DefaultAccessMethodImpl)(nil) +) + +type BlobAccessFactory func() (blobaccess.BlobAccess, error) + +func NewDefaultMethod(c ComponentVersionAccess, a AccessSpec, digest digest.Digest, mime string, fac BlobAccessFactory, local ...bool) AccessMethod { + m, _ := AccessMethodForImplementation(NewDefaultMethodImpl(c, a, digest, mime, fac, local...), nil) + return m +} + +func NewDefaultMethodImpl(c ComponentVersionAccess, a AccessSpec, digest digest.Digest, mime string, fac BlobAccessFactory, local ...bool) AccessMethodImpl { + return &DefaultAccessMethodImpl{ + spec: a, + comp: c, + mime: mime, + digest: digest, + factory: fac, + local: utils.Optional(local...), + } +} + +func NewDefaultMethodForBlobAccess(c ComponentVersionAccess, a AccessSpec, digest digest.Digest, blob blobaccess.BlobAccess, local ...bool) (AccessMethod, error) { + return AccessMethodForImplementation(NewDefaultMethodImplForBlobAccess(c, a, digest, blob, local...)) +} + +func NewDefaultMethodImplForBlobAccess(c ComponentVersionAccess, a AccessSpec, digest digest.Digest, blob blobaccess.BlobAccess, local ...bool) (AccessMethodImpl, error) { + blob, err := blob.Dup() + if err != nil { + return nil, err + } + return &DefaultAccessMethodImpl{ + spec: a, + blob: blob, + comp: c, + mime: blob.MimeType(), + digest: digest, + factory: nil, + local: utils.Optional(local...), + }, nil +} + +func (m *DefaultAccessMethodImpl) getAccess() (blobaccess.BlobAccess, error) { + m.lock.Lock() + defer m.lock.Unlock() + if m.blob == nil { + acc, err := m.factory() + if err != nil { + return nil, err + } + m.blob = acc + } + return m.blob, nil +} + +func (m *DefaultAccessMethodImpl) Digest() digest.Digest { + return m.digest +} + +func (m *DefaultAccessMethodImpl) IsLocal() bool { + return m.local +} + +func (m *DefaultAccessMethodImpl) GetKind() string { + return m.spec.GetKind() +} + +func (m *DefaultAccessMethodImpl) AccessSpec() AccessSpec { + return m.spec +} + +func (m *DefaultAccessMethodImpl) Get() ([]byte, error) { + a, err := m.getAccess() + if err != nil { + return nil, err + } + return a.Get() +} + +func (m *DefaultAccessMethodImpl) Reader() (io.ReadCloser, error) { + a, err := m.getAccess() + if err != nil { + return nil, err + } + return a.Reader() +} + +func (m *DefaultAccessMethodImpl) Close() error { + var err error + m.lock.Lock() + defer m.lock.Unlock() + + if m.blob != nil { + err = m.blob.Close() + m.blob = nil + } + return err +} + +func (m *DefaultAccessMethodImpl) MimeType() string { + return m.mime +} diff --git a/pkg/contexts/ocm/cpi/accspeccpi/methodview.go b/api/ocm/cpi/accspeccpi/methodview.go similarity index 93% rename from pkg/contexts/ocm/cpi/accspeccpi/methodview.go rename to api/ocm/cpi/accspeccpi/methodview.go index 61e93bc8b..670d258f0 100644 --- a/pkg/contexts/ocm/cpi/accspeccpi/methodview.go +++ b/api/ocm/cpi/accspeccpi/methodview.go @@ -6,10 +6,10 @@ import ( "github.com/modern-go/reflect2" "github.com/opencontainers/go-digest" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/refmgmt" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/refmgmt" ) type DigestSource interface { diff --git a/api/ocm/cpi/builder.go b/api/ocm/cpi/builder.go new file mode 100644 index 000000000..c858f43b9 --- /dev/null +++ b/api/ocm/cpi/builder.go @@ -0,0 +1,50 @@ +package cpi + +import ( + "context" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/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/compose_test.go b/api/ocm/cpi/compose_test.go similarity index 80% rename from pkg/contexts/ocm/cpi/compose_test.go rename to api/ocm/cpi/compose_test.go index a33277a14..29c9161b9 100644 --- a/pkg/contexts/ocm/cpi/compose_test.go +++ b/api/ocm/cpi/compose_test.go @@ -5,22 +5,22 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/testhelper" + . "ocm.software/ocm/api/ocm/testhelper" "github.com/mandelsoft/vfs/pkg/memoryfs" "github.com/mandelsoft/vfs/pkg/vfs" - "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/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/compositionmodeattr" - "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/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/mime" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/attrs/compositionmodeattr" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/mime" ) const ( diff --git a/pkg/contexts/ocm/cpi/dummy.go b/api/ocm/cpi/dummy.go similarity index 94% rename from pkg/contexts/ocm/cpi/dummy.go rename to api/ocm/cpi/dummy.go index 113e9cef1..29d03a94c 100644 --- a/pkg/contexts/ocm/cpi/dummy.go +++ b/api/ocm/cpi/dummy.go @@ -5,12 +5,12 @@ import ( "github.com/mandelsoft/goutils/errors" - "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/contexts/ocm/internal" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/refsel" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/rscsel" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/srcsel" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/internal" + "ocm.software/ocm/api/ocm/selectors/refsel" + "ocm.software/ocm/api/ocm/selectors/rscsel" + "ocm.software/ocm/api/ocm/selectors/srcsel" ) type DummyComponentVersionAccess struct { diff --git a/api/ocm/cpi/interface.go b/api/ocm/cpi/interface.go new file mode 100644 index 000000000..3665e3e00 --- /dev/null +++ b/api/ocm/cpi/interface.go @@ -0,0 +1,232 @@ +package cpi + +// This is the Context Provider Interface for credential providers + +import ( + _ "unsafe" + + "github.com/mandelsoft/goutils/sliceutils" + "github.com/mandelsoft/logging" + + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/internal" + "ocm.software/ocm/api/utils/registrations" + "ocm.software/ocm/api/utils/runtime" +) + +const CONTEXT_TYPE = internal.CONTEXT_TYPE + +const CommonTransportFormat = internal.CommonTransportFormat + +var TAG_BLOBHANDLER = logging.DefineTag("blobhandler", "execution of blob handler used to upload resource blobs to an ocm repository.") + +func BlobHandlerLogger(ctx Context, messageContext ...logging.MessageContext) logging.Logger { + if len(messageContext) > 0 { + messageContext = sliceutils.CopyAppend[logging.MessageContext](messageContext, TAG_BLOBHANDLER) + return ctx.Logger(messageContext...) + } else { + return ctx.Logger(TAG_BLOBHANDLER) + } +} + +type ( + Context = internal.Context + ContextProvider = internal.ContextProvider + LocalContextProvider = internal.LocalContextProvider + ComponentVersionResolver = internal.ComponentVersionResolver + Repository = internal.Repository + RepositoryTypeProvider = internal.RepositoryTypeProvider + RepositoryTypeScheme = internal.RepositoryTypeScheme + RepositoryDelegationRegistry = internal.RepositoryDelegationRegistry + RepositoryPriorityDecoder = internal.PriorityDecoder[Context, RepositorySpec] + RepositorySpecHandlers = internal.RepositorySpecHandlers + RepositorySpecHandler = internal.RepositorySpecHandler + UniformRepositorySpec = internal.UniformRepositorySpec + ComponentLister = internal.ComponentLister + ComponentAccess = internal.ComponentAccess + ComponentVersionAccess = internal.ComponentVersionAccess + AccessSpec = internal.AccessSpec + AccessSpecDecoder = internal.AccessSpecDecoder + GenericAccessSpec = internal.GenericAccessSpec + AccessMethod = internal.AccessMethod + AccessProvider = internal.AccessProvider + AccessTypeProvider = internal.AccessTypeProvider + AccessTypeScheme = internal.AccessTypeScheme + DataAccess = internal.DataAccess + BlobAccess = internal.BlobAccess + SourceAccess = internal.SourceAccess + SourceMeta = internal.SourceMeta + ResourceAccess = internal.ResourceAccess + ResourceMeta = internal.ResourceMeta + RepositorySpec = internal.RepositorySpec + RepositorySpecDecoder = internal.RepositorySpecDecoder + IntermediateRepositorySpecAspect = internal.IntermediateRepositorySpecAspect + GenericRepositorySpec = internal.GenericRepositorySpec + RepositoryType = internal.RepositoryType + ComponentReference = internal.ComponentReference +) + +type ArtifactAccess[M any] internal.ArtifactAccess[M] + +type ( + BlobHandler = internal.BlobHandler + BlobHandlerProvider = internal.BlobHandlerProvider + BlobHandlerOption = internal.BlobHandlerOption + BlobHandlerOptions = internal.BlobHandlerOptions + BlobHandlerKey = internal.BlobHandlerKey + BlobHandlerRegistry = internal.BlobHandlerRegistry + StorageContext = internal.StorageContext + ImplementationRepositoryType = internal.ImplementationRepositoryType + + BlobHandlerConfig = internal.BlobHandlerConfig + BlobHandlerRegistrationHandler = internal.BlobHandlerRegistrationHandler +) + +type ( + DigesterType = internal.DigesterType + BlobDigester = internal.BlobDigester + BlobDigesterRegistry = internal.BlobDigesterRegistry + DigestDescriptor = internal.DigestDescriptor + HasherProvider = internal.HasherProvider + Hasher = internal.Hasher +) + +type NamePath = registrations.NamePath + +func NewNamePath(p string) NamePath { + return registrations.NewNamePath(p) +} + +func FromProvider(p ContextProvider) Context { + return internal.FromProvider(p) +} + +func NewBlobHandlerOptions(olist ...BlobHandlerOption) *BlobHandlerOptions { + return internal.NewBlobHandlerOptions(olist...) +} + +func DefaultBlobHandlerProvider(ctx Context) BlobHandlerProvider { + return internal.DefaultBlobHandlerProvider(ctx) +} + +func NewResourceMeta(name string, typ string, relation metav1.ResourceRelation) *ResourceMeta { + return compdesc.NewResourceMeta(name, typ, relation) +} + +func NewDigestDescriptor(digest string, typ DigesterType) *DigestDescriptor { + return internal.NewDigestDescriptor(digest, typ.HashAlgorithm, typ.NormalizationAlgorithm) +} + +func DefaultBlobDigesterRegistry() BlobDigesterRegistry { + return internal.DefaultBlobDigesterRegistry +} + +func DefaultDelegationRegistry() RepositoryDelegationRegistry { + return internal.DefaultRepositoryDelegationRegistry +} + +func DefaultContext() internal.Context { + return internal.DefaultContext +} + +func WithPrio(p int) BlobHandlerOption { + return internal.WithPrio(p) +} + +func ForRepo(ctxtype, repostype string) BlobHandlerOption { + return internal.ForRepo(ctxtype, repostype) +} + +func ForMimeType(mimetype string) BlobHandlerOption { + return internal.ForMimeType(mimetype) +} + +func ForArtifactType(arttype string) BlobHandlerOption { + return internal.ForArtifactType(arttype) +} + +func RegisterRepositorySpecHandler(handler RepositorySpecHandler, types ...string) { + internal.RegisterRepositorySpecHandler(handler, types...) +} + +func RegisterBlobHandler(handler BlobHandler, opts ...BlobHandlerOption) { + internal.RegisterBlobHandler(handler, opts...) +} + +func RegisterBlobHandlerRegistrationHandler(path string, handler BlobHandlerRegistrationHandler) { + internal.RegisterBlobHandlerRegistrationHandler(path, handler) +} + +func MustRegisterDigester(digester BlobDigester, arttypes ...string) { + internal.MustRegisterDigester(digester, arttypes...) +} + +func SetDefaultDigester(d BlobDigester) { + internal.SetDefaultDigester(d) +} + +func ToGenericAccessSpec(spec AccessSpec) (*GenericAccessSpec, error) { + return internal.ToGenericAccessSpec(spec) +} + +func ToGenericRepositorySpec(spec RepositorySpec) (*GenericRepositorySpec, error) { + return internal.ToGenericRepositorySpec(spec) +} + +type AccessSpecRef = internal.AccessSpecRef + +func NewAccessSpecRef(spec AccessSpec) *AccessSpecRef { + return internal.NewAccessSpecRef(spec) +} + +func NewRawAccessSpecRef(data []byte, unmarshaler runtime.Unmarshaler) (*AccessSpecRef, error) { + return internal.NewRawAccessSpecRef(data, unmarshaler) +} + +const ( + KIND_REPOSITORY = internal.KIND_REPOSITORY + KIND_COMPONENTVERSION = internal.KIND_COMPONENTVERSION + KIND_RESOURCE = internal.KIND_RESOURCE + KIND_SOURCE = internal.KIND_SOURCE + KIND_REFERENCE = internal.KIND_REFERENCE +) + +func ErrComponentVersionNotFound(name, version string) error { + return internal.ErrComponentVersionNotFound(name, version) +} + +func ErrComponentVersionNotFoundWrap(err error, name, version string) error { + return internal.ErrComponentVersionNotFoundWrap(err, name, version) +} + +// PrefixProvider is supported by RepositorySpecs to +// provide info about a potential path prefix to +// use for globalized local artifacts. +type PrefixProvider interface { + PathPrefix() string +} + +func RepositoryPrefix(spec RepositorySpec) string { + if s, ok := spec.(PrefixProvider); ok { + return s.PathPrefix() + } + return "" +} + +// HintProvider is able to provide a name hint for globalization of local +// artifacts. +type HintProvider internal.HintProvider + +// GlobalAccessProvider is able to provide a non-local access specification. +type GlobalAccessProvider internal.GlobalAccessProvider + +// provide context interface for other files to avoid diffs in imports. +var ( + newStrictRepositoryTypeScheme = internal.NewStrictRepositoryTypeScheme + defaultRepositoryTypeScheme = internal.DefaultRepositoryTypeScheme +) + +func WrapContextProvider(ctx LocalContextProvider) ContextProvider { + return internal.WrapContextProvider(ctx) +} diff --git a/pkg/contexts/ocm/cpi/logging.go b/api/ocm/cpi/logging.go similarity index 100% rename from pkg/contexts/ocm/cpi/logging.go rename to api/ocm/cpi/logging.go diff --git a/api/ocm/cpi/modopts.go b/api/ocm/cpi/modopts.go new file mode 100644 index 000000000..10ef7f7bb --- /dev/null +++ b/api/ocm/cpi/modopts.go @@ -0,0 +1,114 @@ +package cpi + +import ( + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/attrs/hashattr" + "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" + "ocm.software/ocm/api/ocm/internal" + "ocm.software/ocm/api/tech/signing/hasher/sha256" +) + +type ( + TargetElement = internal.TargetElement + TargetOption = internal.TargetOption + TargetOptions = internal.TargetOptions + + ModificationOption = internal.ModificationOption + ModificationOptions = internal.ModificationOptions + + BlobModificationOption = internal.BlobModificationOption + BlobModificationOptions = internal.BlobModificationOptions + + BlobUploadOption = internal.BlobUploadOption + BlobUploadOptions = internal.BlobUploadOptions + + AddVersionOption = internal.AddVersionOption + AddVersionOptions = internal.AddVersionOptions +) + +//////////////////////////////////////////////////////////////////////////////// + +func NewAddVersionOptions(list ...AddVersionOption) *AddVersionOptions { + return internal.NewAddVersionOptions(list...) +} + +// Overwrite enabled the overwrite mode for adding a component version. +func Overwrite(flag ...bool) AddVersionOption { + return internal.Overwrite(flag...) +} + +//////////////////////////////////////////////////////////////////////////////// + +func NewBlobModificationOptions(list ...BlobModificationOption) *BlobModificationOptions { + return internal.NewBlobModificationOptions(list...) +} + +//////////////////////////////////////////////////////////////////////////////// + +func NewBlobUploadOptions(list ...BlobUploadOption) *BlobUploadOptions { + return internal.NewBlobUploadOptions(list...) +} + +func UseBlobHandlers(h BlobHandlerProvider) internal.BlobOptionImpl { + return internal.UseBlobHandlers(h) +} + +//////////////////////////////////////////////////////////////////////////////// + +func NewModificationOptions(list ...ModificationOption) *ModificationOptions { + return internal.NewModificationOptions(list...) +} + +func TargetIndex(idx int) internal.TargetIndex { + return internal.TargetIndex(-1) +} + +const AppendElement = internal.TargetIndex(-1) + +var UpdateElement = internal.UpdateElement + +func TargetIdentity(id v1.Identity) internal.TargetIdentity { + return internal.TargetIdentity(id) +} + +func ModifyResource(flag ...bool) internal.ModOptionImpl { + return internal.ModifyResource(flag...) +} + +func AcceptExistentDigests(flag ...bool) internal.ModOptionImpl { + return internal.AcceptExistentDigests(flag...) +} + +func WithDefaultHashAlgorithm(algo ...string) internal.ModOptionImpl { + return internal.WithDefaultHashAlgorithm(algo...) +} + +func WithHasherProvider(prov HasherProvider) internal.ModOptionImpl { + return internal.WithHasherProvider(prov) +} + +func SkipVerify(flag ...bool) internal.ModOptionImpl { + return internal.SkipVerify(flag...) +} + +// SkipDigest disables digest creation if enabled. +// +// Deprecated: for legacy code, only. +func SkipDigest(flag ...bool) internal.ModOptionImpl { + return internal.SkipDigest(flag...) +} + +/////////////////////////////////////////////////////// + +func CompleteModificationOptions(ctx ContextProvider, m *ModificationOptions) { + attr := hashattr.Get(ctx.OCMContext()) + if m.DefaultHashAlgorithm == "" { + m.DefaultHashAlgorithm = attr.DefaultHasher + } + if m.DefaultHashAlgorithm == "" { + m.DefaultHashAlgorithm = sha256.Algorithm + } + if m.HasherProvider == nil { + m.HasherProvider = signingattr.Get(ctx.OCMContext()) + } +} diff --git a/pkg/contexts/ocm/cpi/ref.go b/api/ocm/cpi/ref.go similarity index 100% rename from pkg/contexts/ocm/cpi/ref.go rename to api/ocm/cpi/ref.go diff --git a/api/ocm/cpi/repocpi/README.md b/api/ocm/cpi/repocpi/README.md new file mode 100644 index 000000000..05b9206f4 --- /dev/null +++ b/api/ocm/cpi/repocpi/README.md @@ -0,0 +1,77 @@ +# Context Programming Interface for Repositories + +Package repocpi contains the implementation support + for repository backends. It offers three methods + to create component version, component and repository + objects based on three simple implementation interfaces. + + The basic provisioning model is layered: + + ![Implamentation Layers](ocmimpllayers.png) + + - on layer 1 there is the *user facing API* defined + in package [ocm.software/ocm/api/ocm]. + + - on layer 2 (this package) there is a backend agnostic + implementation of standard functionality based on layer 3. + This is divided into two parts + + a) the *view* objects provided by the `Dup()` calls of the layer 1 API. + All dups are internally based on a single base object. + These objects are called *bridge*. They act as base object + for the views and as abstraction for the implementation objects + providing *generic* implementations potentially based on + the implementation functionality. + (see bridge design pattern https://refactoring.guru/design-patterns/bridge) + + b) the *bridge* object as base for all dup views is used to implement some + common functionality like the view management. The bridge object + is closed, when the last view disappears. + This bridge object then calls the final + storage backend implementation interface. + + - the storage backend implementations based on the implementation + interfaces provided by layer 2. + + The implementation interfaces and the functions to create API objects are: + + - interface [ComponentVersionAccessImpl] is used to create an ocm.ComponentVersionAccess object + using the function [NewComponentVersionAccess]. + - interface [ComponentAccessImpl] is used to create an ocm.ComponentAccess object + using the function [NewComponentAccess]. + - interface [RepositoryImpl] is used to create an ocm.ComponentAccess object + using the function [NewRepository]. + + Component version implementations provide basic access to component versions + and their descriptors. They keep a reference to component implementations, which are + again based on repository implementations. The task of repository implementations is + to provide component objects. Their implementations are responsible to provide + component version objects. + +## Simplified Respository Implementation Interface + Besides this basic implementation interfaces with separated objects for a + repository, component and component version, there is support for a simplified + implementation interface (`StorageBackendImpl`). This is a single interface + bundling all required functionality to implement the objects for the three + concerned elements. With `NewStorageBackend` it is possible to instantiate + a new kind of repository based on this single interface. The required + objects for components and component versions are generically provided + based on the methods provided by this interface. + +## Comparison of Implementation Models + +The simplified implementation model does not provide access to the +implementation objects for components and component versions. +Therefore, it is not possible to keep state for those elements. + +Storage Backend Implementations requiring such state, like the OCI +implementation based on the OCI abstraction provided by the OCI +context, therefore use dedicated implementations for repository, +component and component version objects. This model provides +complete control over the lifecycle of those elements. + +If a storage backend implementation is stateless or just keeps +state at the repository level, the simplified implementation model +can be chosen. + + diff --git a/pkg/contexts/ocm/cpi/repocpi/backend.go b/api/ocm/cpi/repocpi/backend.go similarity index 96% rename from pkg/contexts/ocm/cpi/repocpi/backend.go rename to api/ocm/cpi/repocpi/backend.go index efa1736ca..166c420e5 100644 --- a/pkg/contexts/ocm/cpi/repocpi/backend.go +++ b/api/ocm/cpi/repocpi/backend.go @@ -6,11 +6,11 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/common" - "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/refmgmt" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/refmgmt" ) // StorageBackendImpl is an interface which can be implemented diff --git a/pkg/contexts/ocm/cpi/repocpi/blobcache.go b/api/ocm/cpi/repocpi/blobcache.go similarity index 96% rename from pkg/contexts/ocm/cpi/repocpi/blobcache.go rename to api/ocm/cpi/repocpi/blobcache.go index 83edc2876..73cd08a18 100644 --- a/pkg/contexts/ocm/cpi/repocpi/blobcache.go +++ b/api/ocm/cpi/repocpi/blobcache.go @@ -5,7 +5,7 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" ) type ( diff --git a/pkg/contexts/ocm/cpi/repocpi/bridge_c.go b/api/ocm/cpi/repocpi/bridge_c.go similarity index 93% rename from pkg/contexts/ocm/cpi/repocpi/bridge_c.go rename to api/ocm/cpi/repocpi/bridge_c.go index eeac749dc..e4ffaa7d7 100644 --- a/pkg/contexts/ocm/cpi/repocpi/bridge_c.go +++ b/api/ocm/cpi/repocpi/bridge_c.go @@ -7,12 +7,12 @@ import ( "github.com/mandelsoft/goutils/finalizer" "github.com/mandelsoft/goutils/optionutils" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/compositionmodeattr" - "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/refmgmt" - "github.com/open-component-model/ocm/pkg/refmgmt/resource" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/attrs/compositionmodeattr" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/refmgmt" + "ocm.software/ocm/api/utils/refmgmt/resource" ) type ComponentVersionAccessInfo struct { diff --git a/pkg/contexts/ocm/cpi/repocpi/bridge_cv.go b/api/ocm/cpi/repocpi/bridge_cv.go similarity index 93% rename from pkg/contexts/ocm/cpi/repocpi/bridge_cv.go rename to api/ocm/cpi/repocpi/bridge_cv.go index d2b39120b..b7529468c 100644 --- a/pkg/contexts/ocm/cpi/repocpi/bridge_cv.go +++ b/api/ocm/cpi/repocpi/bridge_cv.go @@ -10,21 +10,21 @@ import ( "github.com/mandelsoft/goutils/finalizer" "github.com/mandelsoft/goutils/optionutils" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/compose" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/compositionmodeattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/keepblobattr" - "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/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" - "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub" - "github.com/open-component-model/ocm/pkg/refmgmt" - "github.com/open-component-model/ocm/pkg/refmgmt/resource" - "github.com/open-component-model/ocm/pkg/runtimefinalizer" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/compose" + "ocm.software/ocm/api/ocm/extensions/attrs/compositionmodeattr" + "ocm.software/ocm/api/ocm/extensions/attrs/keepblobattr" + "ocm.software/ocm/api/ocm/extensions/pubsub" + "ocm.software/ocm/api/ocm/internal" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/refmgmt" + "ocm.software/ocm/api/utils/refmgmt/resource" + "ocm.software/ocm/api/utils/runtimefinalizer" ) // here, we define the common implementation agnostic parts diff --git a/pkg/contexts/ocm/cpi/repocpi/bridge_r.go b/api/ocm/cpi/repocpi/bridge_r.go similarity index 92% rename from pkg/contexts/ocm/cpi/repocpi/bridge_r.go rename to api/ocm/cpi/repocpi/bridge_r.go index 127f14da7..f7eea176f 100644 --- a/pkg/contexts/ocm/cpi/repocpi/bridge_r.go +++ b/api/ocm/cpi/repocpi/bridge_r.go @@ -5,10 +5,10 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/refmgmt" - "github.com/open-component-model/ocm/pkg/refmgmt/resource" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/refmgmt" + "ocm.software/ocm/api/utils/refmgmt/resource" ) type ComponentAccessInfo struct { diff --git a/api/ocm/cpi/repocpi/doc.go b/api/ocm/cpi/repocpi/doc.go new file mode 100644 index 000000000..df1f45f0a --- /dev/null +++ b/api/ocm/cpi/repocpi/doc.go @@ -0,0 +1,55 @@ +// Package repocpi contains the implementation support +// for repository backends. It offers three methods +// to create component version, component and repository +// objects based on three simple implementation interfaces. +// +// The basic provisioning model is layered: +// +// - on layer 1 there is the user facing API defined +// in package [ocm.software/ocm/api/ocm]. +// +// - on layer 2 (this package) there is a backend agnostic +// implementation of standard functionality based on layer 3. +// This is divided into two parts +// +// a) the view objects provided by the Dup() calls of the layer 1 API. +// All dups are internally based on a single base object. +// These objects are called bridge. They act as base object +// for the views and as abstraction for the implementation objects +// providing generic implementations potentially based on +// the implementation functionality. +// (see bridge design pattern https://refactoring.guru/design-patterns/bridge) +// +// b) the bridge object as base for all dup views is used to implement some +// common functionality like the view management. The bridge object +// is closed, when the last view disappears. +// This bridge object then calls the final +// storage backend implementation interface. +// +// - the storage backend implementations based on the implementation +// interfaces provided by layer 2. +// +// The implementation interfaces and the functions to create API objects are: +// +// - interface [ComponentVersionAccessImpl] is used to create an ocm.ComponentVersionAccess object +// using the function [NewComponentVersionAccess]. +// - interface [ComponentAccessImpl] is used to create an ocm.ComponentAccess object +// using the function [NewComponentAccess]. +// - interface [RepositoryImpl] is used to create an ocm.ComponentAccess object +// using the function [NewRepository]. +// +// Component version implementations provide basic access to component versions +// and their descriptors. They keep a reference to component implementations, which are +// again based on repository implementations. The task of repository implementations is +// to provide component objects. Their implementations are responsible to provide +// component version objects. +// +// Besides this basic implementation interface with separated object for a +// repository, component and component version, there is support for a simplified +// implementation interface (StorageBackendImpl). This is a single interface +// bundling all required functionality to implement the objects for the three +// concerned elements. With NewStorageBackend it is possible to instantiate +// a new kind of repository based on this single interface. The required +// objects for components and component versions are generically provided +// based on the methods provided by this interface. +package repocpi diff --git a/pkg/contexts/ocm/cpi/repocpi/helperinterfaces.go b/api/ocm/cpi/repocpi/helperinterfaces.go similarity index 89% rename from pkg/contexts/ocm/cpi/repocpi/helperinterfaces.go rename to api/ocm/cpi/repocpi/helperinterfaces.go index 4322bf2bd..5a9e2879f 100644 --- a/pkg/contexts/ocm/cpi/repocpi/helperinterfaces.go +++ b/api/ocm/cpi/repocpi/helperinterfaces.go @@ -3,8 +3,8 @@ package repocpi import ( "fmt" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/refmgmt/resource" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils/refmgmt/resource" ) var ( diff --git a/api/ocm/cpi/repocpi/interface.go b/api/ocm/cpi/repocpi/interface.go new file mode 100644 index 000000000..057902c09 --- /dev/null +++ b/api/ocm/cpi/repocpi/interface.go @@ -0,0 +1,7 @@ +package repocpi + +import ( + "ocm.software/ocm/api/ocm/internal" +) + +type Repository = internal.Repository diff --git a/pkg/contexts/ocm/cpi/repocpi/ocmimpllayers.png b/api/ocm/cpi/repocpi/ocmimpllayers.png similarity index 100% rename from pkg/contexts/ocm/cpi/repocpi/ocmimpllayers.png rename to api/ocm/cpi/repocpi/ocmimpllayers.png diff --git a/pkg/contexts/ocm/cpi/repocpi/view_c.go b/api/ocm/cpi/repocpi/view_c.go similarity index 94% rename from pkg/contexts/ocm/cpi/repocpi/view_c.go rename to api/ocm/cpi/repocpi/view_c.go index a35db654f..c4f5727c7 100644 --- a/pkg/contexts/ocm/cpi/repocpi/view_c.go +++ b/api/ocm/cpi/repocpi/view_c.go @@ -6,10 +6,10 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/refmgmt/resource" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/refmgmt/resource" ) type _componentAccessView interface { diff --git a/pkg/contexts/ocm/cpi/repocpi/view_cv.go b/api/ocm/cpi/repocpi/view_cv.go similarity index 96% rename from pkg/contexts/ocm/cpi/repocpi/view_cv.go rename to api/ocm/cpi/repocpi/view_cv.go index ea2a24d97..1e5e1c59e 100644 --- a/pkg/contexts/ocm/cpi/repocpi/view_cv.go +++ b/api/ocm/cpi/repocpi/view_cv.go @@ -7,23 +7,23 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/common/accessio" - "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/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/refsel" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/rscsel" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/srcsel" - "github.com/open-component-model/ocm/pkg/refmgmt" - "github.com/open-component-model/ocm/pkg/refmgmt/resource" - "github.com/open-component-model/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/utils/selector" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/internal" + "ocm.software/ocm/api/ocm/plugin/descriptor" + "ocm.software/ocm/api/ocm/selectors" + "ocm.software/ocm/api/ocm/selectors/refsel" + "ocm.software/ocm/api/ocm/selectors/rscsel" + "ocm.software/ocm/api/ocm/selectors/srcsel" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/refmgmt" + "ocm.software/ocm/api/utils/refmgmt/resource" + "ocm.software/ocm/api/utils/selector" ) // View objects are the user facing generic implementations of the context interfaces. diff --git a/pkg/contexts/ocm/cpi/repocpi/view_r.go b/api/ocm/cpi/repocpi/view_r.go similarity index 94% rename from pkg/contexts/ocm/cpi/repocpi/view_r.go rename to api/ocm/cpi/repocpi/view_r.go index 65d067f6b..4fc9d5a42 100644 --- a/pkg/contexts/ocm/cpi/repocpi/view_r.go +++ b/api/ocm/cpi/repocpi/view_r.go @@ -6,11 +6,11 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/refmgmt" - "github.com/open-component-model/ocm/pkg/refmgmt/resource" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/refmgmt" + "ocm.software/ocm/api/utils/refmgmt/resource" ) // View objects are the user facing generic implementations of the context interfaces. diff --git a/api/ocm/cpi/repotypes.go b/api/ocm/cpi/repotypes.go new file mode 100644 index 000000000..95d5f15d7 --- /dev/null +++ b/api/ocm/cpi/repotypes.go @@ -0,0 +1,59 @@ +package cpi + +// this file is similar to contexts oci. + +import ( + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/utils/runtime" +) + +type RepositoryTypeVersionScheme = runtime.TypeVersionScheme[RepositorySpec, RepositoryType] + +func NewRepositoryTypeVersionScheme(kind string) RepositoryTypeVersionScheme { + return runtime.NewTypeVersionScheme[RepositorySpec, RepositoryType](kind, newStrictRepositoryTypeScheme()) +} + +func RegisterRepositoryType(rtype RepositoryType) { + defaultRepositoryTypeScheme.Register(rtype) +} + +func RegisterRepositoryTypeVersions(s RepositoryTypeVersionScheme) { + defaultRepositoryTypeScheme.AddKnownTypes(s) +} + +//////////////////////////////////////////////////////////////////////////////// + +type repositoryType struct { + runtime.VersionedTypedObjectType[RepositorySpec] + checker RepositoryAccessMethodChecker +} + +type RepositoryAccessMethodChecker func(Context, compdesc.AccessSpec) bool + +func NewRepositoryType[I RepositorySpec](name string, checker RepositoryAccessMethodChecker) RepositoryType { + return &repositoryType{ + VersionedTypedObjectType: runtime.NewVersionedTypedObjectType[RepositorySpec, I](name), + checker: checker, + } +} + +func NewRepositoryTypeByConverter[I RepositorySpec, V runtime.VersionedTypedObject](name string, converter runtime.Converter[I, V], checker RepositoryAccessMethodChecker) RepositoryType { + return &repositoryType{ + VersionedTypedObjectType: runtime.NewVersionedTypedObjectTypeByConverter[RepositorySpec, I, V](name, converter), + checker: checker, + } +} + +func NewRepositoryTypeByFormatVersion(name string, fmt runtime.FormatVersion[RepositorySpec], checker RepositoryAccessMethodChecker) RepositoryType { + return &repositoryType{ + VersionedTypedObjectType: runtime.NewVersionedTypedObjectTypeByFormatVersion[RepositorySpec](name, fmt), + checker: checker, + } +} + +func (t *repositoryType) LocalSupportForAccessSpec(ctx Context, a compdesc.AccessSpec) bool { + if t.checker != nil { + return t.checker(ctx, a) + } + return false +} diff --git a/pkg/contexts/ocm/cpi/storagectx.go b/api/ocm/cpi/storagectx.go similarity index 100% rename from pkg/contexts/ocm/cpi/storagectx.go rename to api/ocm/cpi/storagectx.go diff --git a/pkg/contexts/ocm/cpi/suite_test.go b/api/ocm/cpi/suite_test.go similarity index 100% rename from pkg/contexts/ocm/cpi/suite_test.go rename to api/ocm/cpi/suite_test.go diff --git a/api/ocm/cpi/utils.go b/api/ocm/cpi/utils.go new file mode 100644 index 000000000..433cb3cad --- /dev/null +++ b/api/ocm/cpi/utils.go @@ -0,0 +1,89 @@ +package cpi + +import ( + "io" + + "ocm.software/ocm/api/ocm/internal" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/iotools" +) + +type AccessMethodSource interface { + AccessMethod() (AccessMethod, error) +} + +// ResourceReader gets a Reader for a given resource/source access. +// It provides a Reader handling the Close contract for the access method +// by connecting the access method's Close method to the Readers Close method . +// Deprecated: use GetResourceReader. +// It must be deprecated because of the support of free-floating ReSourceAccess +// implementations, they not necessarily provide an AccessMethod. +func ResourceReader(s AccessMethodSource) (io.ReadCloser, error) { + meth, err := s.AccessMethod() + if err != nil { + return nil, err + } + return toResourceReaderForMethod(meth) +} + +// ResourceMimeReader gets a Reader for a given resource/source access. +// It provides a Reader handling the Close contract for the access method +// by connecting the access method's Close method to the Readers Close method. +// Additionally, the mime type is returned. +// Deprecated: use GetResourceMimeReader. +// It must be deprecated because of the support of free-floating ReSourceAccess +// implementations, they not necessarily provide an AccessMethod. +func ResourceMimeReader(s AccessMethodSource) (io.ReadCloser, string, error) { + meth, err := s.AccessMethod() + if err != nil { + return nil, "", err + } + r, err := toResourceReaderForMethod(meth) + return r, meth.MimeType(), err +} + +func toResourceReaderForMethod(meth AccessMethod) (io.ReadCloser, error) { + r, err := meth.Reader() + if err != nil { + meth.Close() + return nil, err + } + return iotools.AddReaderCloser(r, meth, "access method"), nil +} + +// GetResourceMimeReader gets a Reader for a given resource/source access. +// It provides a Reader handling the Close contract for the access method. +func GetResourceReader(acc AccessProvider) (io.ReadCloser, error) { + return blobaccess.ReaderFromProvider(acc) +} + +// GetResourceMimeReader gets a Reader for a given resource/source access. +// It provides a Reader handling the Close contract for the access method. +// Additionally, the mime type is returned. +func GetResourceMimeReader(acc AccessProvider) (io.ReadCloser, string, error) { + return blobaccess.MimeReaderFromProvider(acc) +} + +//////////////////////////////////////////////////////////////////////////////// + +func ArtifactNameHint(spec AccessSpec, cv ComponentVersionAccess) string { + if h, ok := spec.(HintProvider); ok { + return h.GetReferenceHint(cv) + } + return "" +} + +func ReferenceHint(spec AccessSpec, cv ComponentVersionAccess) string { + if h, ok := spec.(internal.HintProvider); ok { + return h.GetReferenceHint(cv) + } + return "" +} + +func GlobalAccess(spec AccessSpec, ctx Context) AccessSpec { + g := spec.GlobalAccessSpec(ctx) + if g != nil && g.IsLocal(ctx) { + g = nil + } + return g +} diff --git a/pkg/contexts/ocm/cpi/view_rsc.go b/api/ocm/cpi/view_rsc.go similarity index 93% rename from pkg/contexts/ocm/cpi/view_rsc.go rename to api/ocm/cpi/view_rsc.go index 6baca3c0a..55dc9c5e5 100644 --- a/pkg/contexts/ocm/cpi/view_rsc.go +++ b/api/ocm/cpi/view_rsc.go @@ -5,13 +5,13 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - ocm "github.com/open-component-model/ocm/pkg/contexts/ocm/context" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - cpi "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + cpi "ocm.software/ocm/api/ocm/internal" + "ocm.software/ocm/api/ocm/plugin/descriptor" + ocm "ocm.software/ocm/api/ocm/types" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" ) //////////////////////////////////////////////////////////////////////////////// diff --git a/pkg/contexts/ocm/elements/artifactaccess/doc.go b/api/ocm/elements/artifactaccess/doc.go similarity index 100% rename from pkg/contexts/ocm/elements/artifactaccess/doc.go rename to api/ocm/elements/artifactaccess/doc.go diff --git a/api/ocm/elements/artifactaccess/genericaccess/resource.go b/api/ocm/elements/artifactaccess/genericaccess/resource.go new file mode 100644 index 000000000..42439b901 --- /dev/null +++ b/api/ocm/elements/artifactaccess/genericaccess/resource.go @@ -0,0 +1,34 @@ +package genericaccess + +import ( + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/generics" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" +) + +func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, access ocm.AccessSpec) (cpi.ArtifactAccess[M], error) { + prov, err := cpi.NewAccessProviderForExternalAccessSpec(ctx, access) + if err != nil { + return nil, errors.Wrapf(err, "invalid external access method %q", access.GetKind()) + } + return cpi.NewArtifactAccessForProvider(generics.Cast[*M](meta), prov), nil +} + +func MustAccess[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, access ocm.AccessSpec) cpi.ArtifactAccess[M] { + a, err := Access(ctx, meta, access) + if err != nil { + panic(err) + } + return a +} + +func ResourceAccess(ctx ocm.Context, meta *ocm.ResourceMeta, access ocm.AccessSpec) (cpi.ResourceAccess, error) { + return Access(ctx, meta, access) +} + +func SourceAccess(ctx ocm.Context, meta *ocm.SourceMeta, access ocm.AccessSpec) (cpi.SourceAccess, error) { + return Access(ctx, meta, access) +} diff --git a/api/ocm/elements/artifactaccess/genericaccess/resource_test.go b/api/ocm/elements/artifactaccess/genericaccess/resource_test.go new file mode 100644 index 000000000..599ff690c --- /dev/null +++ b/api/ocm/elements/artifactaccess/genericaccess/resource_test.go @@ -0,0 +1,55 @@ +package genericaccess_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + . "ocm.software/ocm/api/oci/testhelper" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/ocm/compdesc" + me "ocm.software/ocm/api/ocm/elements/artifactaccess/genericaccess" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/utils/accessio" +) + +const ( + OCIPATH = "/tmp/oci" + OCIHOST = "alias" +) + +var _ = Describe("dir tree resource access", func() { + var env *Builder + + BeforeEach(func() { + env = NewBuilder() + + FakeOCIRepo(env, OCIPATH, OCIHOST) + + env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { + OCIManifest1(env) + }) + }) + + AfterEach(func() { + env.Cleanup() + }) + + It("creates resource", func() { + spec := ociartifact.New(oci.StandardOCIRef(OCIHOST+".alias", OCINAMESPACE, OCIVERSION)) + + acc := Must(me.ResourceAccess(env.OCMContext(), compdesc.NewResourceMeta("test", resourcetypes.OCI_IMAGE, compdesc.LocalRelation), spec)) + + Expect(acc.ReferenceHint()).To(Equal(OCINAMESPACE + ":" + OCIVERSION)) + Expect(acc.GlobalAccess()).To(BeNil()) + Expect(acc.Meta().Type).To(Equal(resourcetypes.OCI_IMAGE)) + + blob := Must(acc.BlobAccess()) + defer Defer(blob.Close, "blob") + Expect(blob.MimeType()).To(Equal(artifactset.MediaType(artdesc.MediaTypeImageManifest))) + }) +}) diff --git a/pkg/contexts/ocm/elements/artifactaccess/genericaccess/suite_test.go b/api/ocm/elements/artifactaccess/genericaccess/suite_test.go similarity index 100% rename from pkg/contexts/ocm/elements/artifactaccess/genericaccess/suite_test.go rename to api/ocm/elements/artifactaccess/genericaccess/suite_test.go diff --git a/pkg/contexts/ocm/elements/artifactaccess/githubaccess/options.go b/api/ocm/elements/artifactaccess/githubaccess/options.go similarity index 100% rename from pkg/contexts/ocm/elements/artifactaccess/githubaccess/options.go rename to api/ocm/elements/artifactaccess/githubaccess/options.go diff --git a/api/ocm/elements/artifactaccess/githubaccess/resource.go b/api/ocm/elements/artifactaccess/githubaccess/resource.go new file mode 100644 index 000000000..fc668d38e --- /dev/null +++ b/api/ocm/elements/artifactaccess/githubaccess/resource.go @@ -0,0 +1,33 @@ +package githubaccess + +import ( + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/elements/artifactaccess/genericaccess" + access "ocm.software/ocm/api/ocm/extensions/accessmethods/github" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" +) + +const TYPE = resourcetypes.DIRECTORY_TREE + +func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, repo string, commit string, opts ...Option) cpi.ArtifactAccess[M] { + eff := optionutils.EvalOptions(opts...) + if meta.GetType() == "" { + meta.SetType(TYPE) + } + + spec := access.New(repo, eff.APIHostName, commit) + // 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, repo string, commit string, opts ...Option) cpi.ResourceAccess { + return Access(ctx, meta, repo, commit, opts...) +} + +func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, repo string, commit string, opts ...Option) cpi.SourceAccess { + return Access(ctx, meta, repo, commit, opts...) +} diff --git a/api/ocm/elements/artifactaccess/helmaccess/resource.go b/api/ocm/elements/artifactaccess/helmaccess/resource.go new file mode 100644 index 000000000..866d11f80 --- /dev/null +++ b/api/ocm/elements/artifactaccess/helmaccess/resource.go @@ -0,0 +1,30 @@ +package helmaccess + +import ( + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/elements/artifactaccess/genericaccess" + access "ocm.software/ocm/api/ocm/extensions/accessmethods/helm" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" +) + +const TYPE = resourcetypes.HELM_CHART + +func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, chart string, repourl string) cpi.ArtifactAccess[M] { + if meta.GetType() == "" { + meta.SetType(TYPE) + } + + spec := access.New(chart, repourl) + // 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, chart string, repourl string) cpi.ResourceAccess { + return Access(ctx, meta, chart, repourl) +} + +func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, chart string, repourl string) cpi.SourceAccess { + return Access(ctx, meta, chart, repourl) +} diff --git a/api/ocm/elements/artifactaccess/mavenaccess/options.go b/api/ocm/elements/artifactaccess/mavenaccess/options.go new file mode 100644 index 000000000..7c9ef12f7 --- /dev/null +++ b/api/ocm/elements/artifactaccess/mavenaccess/options.go @@ -0,0 +1,20 @@ +package mavenaccess + +import "ocm.software/ocm/api/tech/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/api/ocm/elements/artifactaccess/mavenaccess/resource.go b/api/ocm/elements/artifactaccess/mavenaccess/resource.go new file mode 100644 index 000000000..72debf895 --- /dev/null +++ b/api/ocm/elements/artifactaccess/mavenaccess/resource.go @@ -0,0 +1,39 @@ +package mavenaccess + +import ( + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/elements/artifactaccess/genericaccess" + access "ocm.software/ocm/api/ocm/extensions/accessmethods/maven" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/tech/maven" +) + +const TYPE = resourcetypes.MAVEN_PACKAGE + +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/api/ocm/elements/artifactaccess/npmaccess/resource.go b/api/ocm/elements/artifactaccess/npmaccess/resource.go new file mode 100644 index 000000000..1bb5a0ba9 --- /dev/null +++ b/api/ocm/elements/artifactaccess/npmaccess/resource.go @@ -0,0 +1,30 @@ +package npmaccess + +import ( + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/elements/artifactaccess/genericaccess" + access "ocm.software/ocm/api/ocm/extensions/accessmethods/npm" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" +) + +const TYPE = resourcetypes.NPM_PACKAGE + +func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, registry, pkg, version string) cpi.ArtifactAccess[M] { + if meta.GetType() == "" { + meta.SetType(TYPE) + } + + spec := access.New(registry, pkg, 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, registry, pkg, version string) cpi.ResourceAccess { + return Access(ctx, meta, registry, pkg, version) +} + +func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, registry, pkg, version string) cpi.SourceAccess { + return Access(ctx, meta, registry, pkg, version) +} diff --git a/api/ocm/elements/artifactaccess/ociartifactaccess/resource.go b/api/ocm/elements/artifactaccess/ociartifactaccess/resource.go new file mode 100644 index 000000000..0c43847ee --- /dev/null +++ b/api/ocm/elements/artifactaccess/ociartifactaccess/resource.go @@ -0,0 +1,30 @@ +package ociartifactaccess + +import ( + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/elements/artifactaccess/genericaccess" + access "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" +) + +const TYPE = resourcetypes.OCI_IMAGE + +func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, refname string) cpi.ArtifactAccess[M] { + if meta.GetType() == "" { + meta.SetType(TYPE) + } + + spec := access.New(refname) + // 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, path string) cpi.ResourceAccess { + return Access(ctx, meta, path) +} + +func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, path string) cpi.SourceAccess { + return Access(ctx, meta, path) +} diff --git a/pkg/contexts/ocm/elements/artifactaccess/ociblobaccess/options.go b/api/ocm/elements/artifactaccess/ociblobaccess/options.go similarity index 100% rename from pkg/contexts/ocm/elements/artifactaccess/ociblobaccess/options.go rename to api/ocm/elements/artifactaccess/ociblobaccess/options.go diff --git a/api/ocm/elements/artifactaccess/ociblobaccess/resource.go b/api/ocm/elements/artifactaccess/ociblobaccess/resource.go new file mode 100644 index 000000000..fc46049ec --- /dev/null +++ b/api/ocm/elements/artifactaccess/ociblobaccess/resource.go @@ -0,0 +1,39 @@ +package github + +import ( + "github.com/mandelsoft/goutils/optionutils" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/elements/artifactaccess/genericaccess" + access "ocm.software/ocm/api/ocm/extensions/accessmethods/ociblob" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/utils/mime" +) + +const TYPE = resourcetypes.BLOB + +func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, repository string, digest digest.Digest, size int64, opts ...Option) cpi.ArtifactAccess[M] { + eff := optionutils.EvalOptions(opts...) + if meta.GetType() == "" { + meta.SetType(TYPE) + } + + media := eff.MediaType + if media == "" { + media = mime.MIME_OCTET + } + spec := access.New(repository, digest, media, size) + // 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 string, digest digest.Digest, size int64, opts ...Option) cpi.ResourceAccess { + return Access(ctx, meta, repository, digest, size, opts...) +} + +func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, repository string, digest digest.Digest, size int64, opts ...Option) cpi.SourceAccess { + return Access(ctx, meta, repository, digest, size, opts...) +} diff --git a/pkg/contexts/ocm/elements/artifactaccess/s3access/options.go b/api/ocm/elements/artifactaccess/s3access/options.go similarity index 100% rename from pkg/contexts/ocm/elements/artifactaccess/s3access/options.go rename to api/ocm/elements/artifactaccess/s3access/options.go diff --git a/api/ocm/elements/artifactaccess/s3access/resource.go b/api/ocm/elements/artifactaccess/s3access/resource.go new file mode 100644 index 000000000..f44a985f4 --- /dev/null +++ b/api/ocm/elements/artifactaccess/s3access/resource.go @@ -0,0 +1,38 @@ +package github + +import ( + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/elements/artifactaccess/genericaccess" + access "ocm.software/ocm/api/ocm/extensions/accessmethods/s3" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/utils/mime" +) + +const TYPE = resourcetypes.BLOB + +func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, bucket, key string, opts ...Option) cpi.ArtifactAccess[M] { + eff := optionutils.EvalOptions(opts...) + if meta.GetType() == "" { + meta.SetType(TYPE) + } + + media := eff.MediaType + if media == "" { + media = mime.MIME_OCTET + } + spec := access.New(eff.Region, bucket, key, eff.Version, media) + // 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, bucket, key string, opts ...Option) cpi.ResourceAccess { + return Access(ctx, meta, bucket, key, opts...) +} + +func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, bucket, key string, opts ...Option) cpi.SourceAccess { + return Access(ctx, meta, bucket, key, opts...) +} diff --git a/api/ocm/elements/artifactaccess/wgetaccess/options.go b/api/ocm/elements/artifactaccess/wgetaccess/options.go new file mode 100644 index 000000000..66f1ef0a4 --- /dev/null +++ b/api/ocm/elements/artifactaccess/wgetaccess/options.go @@ -0,0 +1,48 @@ +package wgetaccess + +import ( + "io" + "net/http" + + "github.com/mandelsoft/logging" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/utils/blobaccess/wget" +) + +type ( + Options = wget.Options + 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) +} + +func WithVerb(v string) Option { + return wget.WithVerb(v) +} + +func WithBody(v io.Reader) Option { + return wget.WithBody(v) +} + +func WithNoRedirect(r ...bool) Option { + return wget.WithNoRedirect(r...) +} diff --git a/api/ocm/elements/artifactaccess/wgetaccess/resource.go b/api/ocm/elements/artifactaccess/wgetaccess/resource.go new file mode 100644 index 000000000..56127b100 --- /dev/null +++ b/api/ocm/elements/artifactaccess/wgetaccess/resource.go @@ -0,0 +1,30 @@ +package wgetaccess + +import ( + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/elements/artifactaccess/genericaccess" + access "ocm.software/ocm/api/ocm/extensions/accessmethods/wget" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" +) + +const TYPE = resourcetypes.BLOB + +func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, url string, opts ...Option) cpi.ArtifactAccess[M] { + if meta.GetType() == "" { + meta.SetType(TYPE) + } + + spec := access.New(url, 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, url string, opts ...Option) cpi.ResourceAccess { + return Access(ctx, meta, url, opts...) +} + +func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, url string, opts ...Option) cpi.SourceAccess { + return Access(ctx, meta, url, opts...) +} diff --git a/api/ocm/elements/artifactblob/api/options.go b/api/ocm/elements/artifactblob/api/options.go new file mode 100644 index 000000000..442b77f48 --- /dev/null +++ b/api/ocm/elements/artifactblob/api/options.go @@ -0,0 +1,71 @@ +package api + +import ( + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/ocm/cpi" +) + +type ( + Option = optionutils.Option[*Options] + GeneralOptionsProvider = optionutils.NestedOptionsProvider[*Options] +) + +type Options struct { + Global cpi.AccessSpec + Hint string +} + +var ( + _ optionutils.NestedOptionsProvider[*Options] = (*Options)(nil) + _ optionutils.Option[*Options] = (*Options)(nil) +) + +func (w *Options) NestedOptions() *Options { + return w +} + +func (o *Options) ApplyTo(opts *Options) { + if o.Global != nil { + opts.Global = o.Global + } + if o.Hint != "" { + opts.Hint = o.Hint + } +} + +func (o *Options) Apply(opts ...Option) { + optionutils.ApplyOptions(o, opts...) +} + +type hint string + +func (o hint) ApplyTo(opts *Options) { + opts.Hint = string(o) +} + +func WithHint(h string) Option { + return hint(h) +} + +func WrapHint[O any, P optionutils.OptionTargetProvider[*Options, O]](h string) optionutils.Option[P] { + return optionutils.OptionWrapper[*Options, O, P](WithHint(h)) +} + +//////////////////////////////////////////////////////////////////////////////// + +type global struct { + cpi.AccessSpec +} + +func (o global) ApplyTo(opts *Options) { + opts.Global = o.AccessSpec +} + +func WithGlobalAccess(a cpi.AccessSpec) Option { + return global{a} +} + +func WrapGlobalAccess[O any, P optionutils.OptionTargetProvider[*Options, O]](a cpi.AccessSpec) optionutils.Option[P] { + return optionutils.OptionWrapper[*Options, O, P](WithGlobalAccess(a)) +} diff --git a/api/ocm/elements/artifactblob/datablob/options.go b/api/ocm/elements/artifactblob/datablob/options.go new file mode 100644 index 000000000..487cd991b --- /dev/null +++ b/api/ocm/elements/artifactblob/datablob/options.go @@ -0,0 +1,84 @@ +package datablob + +import ( + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/elements/artifactblob/api" +) + +type Option = optionutils.Option[*Options] + +type compressionMode string + +const ( + COMPRESSION = compressionMode("compression") + DECOMPRESSION = compressionMode("decompression") + NONE = compressionMode("") +) + +type Options struct { + api.Options + MimeType string + Compression compressionMode +} + +var ( + _ api.GeneralOptionsProvider = (*Options)(nil) + _ Option = (*Options)(nil) +) + +func (o *Options) ApplyTo(opts *Options) { + o.Options.ApplyTo(&opts.Options) + if o.MimeType != "" { + opts.MimeType = o.MimeType + } +} + +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 + +type mimetype struct { + mime string +} + +func (o mimetype) ApplyTo(opts *Options) { + opts.MimeType = o.mime +} + +func WithMimeType(mime string) Option { + return mimetype{mime} +} + +//////////////////////////////////////////////////////////////////////////////// + +type compression struct { + mode compressionMode +} + +func (o compression) ApplyTo(opts *Options) { + opts.Compression = o.mode +} + +func WithCompression() Option { + return compression{COMPRESSION} +} + +func WithDecompression() Option { + return compression{DECOMPRESSION} +} diff --git a/api/ocm/elements/artifactblob/datablob/resource.go b/api/ocm/elements/artifactblob/datablob/resource.go new file mode 100644 index 000000000..2f5babc90 --- /dev/null +++ b/api/ocm/elements/artifactblob/datablob/resource.go @@ -0,0 +1,49 @@ +package datablob + +import ( + "github.com/mandelsoft/goutils/generics" + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/mime" +) + +func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, blob []byte, opts ...Option) cpi.ArtifactAccess[M] { + eff := optionutils.EvalOptions(opts...) + + media := eff.MimeType + if media == "" { + media = mime.MIME_OCTET + } + + var blobprov blobaccess.BlobAccessProvider + switch eff.Compression { + case NONE: + blobprov = blobaccess.ProviderForData(media, blob) + case COMPRESSION: + blob := blobaccess.ForData(media, blob) + defer blob.Close() + blob, _ = blobaccess.WithCompression(blob) + blobprov = blobaccess.ProviderForBlobAccess(blob) + case DECOMPRESSION: + blob := blobaccess.ForData(media, blob) + defer blob.Close() + blob, _ = blobaccess.WithDecompression(blob) + blobprov = blobaccess.ProviderForBlobAccess(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, media string, meta *ocm.ResourceMeta, blob []byte, opts ...Option) cpi.ResourceAccess { + return Access(ctx, meta, blob, opts...) +} + +func SourceAccess(ctx ocm.Context, media string, meta *ocm.SourceMeta, blob []byte, opts ...Option) cpi.SourceAccess { + return Access(ctx, meta, blob, opts...) +} diff --git a/api/ocm/elements/artifactblob/dirtreeblob/options.go b/api/ocm/elements/artifactblob/dirtreeblob/options.go new file mode 100644 index 000000000..642207e72 --- /dev/null +++ b/api/ocm/elements/artifactblob/dirtreeblob/options.go @@ -0,0 +1,77 @@ +package dirtreeblob + +import ( + "github.com/mandelsoft/goutils/optionutils" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/elements/artifactblob/api" + base "ocm.software/ocm/api/utils/blobaccess/dirtree" +) + +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) +} + +//////////////////////////////////////////////////////////////////////////////// +// DirTree BlobAccess 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 WithFileSystem(fs vfs.FileSystem) Option { + return wrapBase(base.WithFileSystem(fs)) +} + +func WithExcludeFiles(files []string) Option { + return wrapBase(base.WithExcludeFiles(files)) +} + +func WithIncludeFiles(files []string) Option { + return wrapBase(base.WithIncludeFiles(files)) +} + +func WithFollowSymlinks(b ...bool) Option { + return wrapBase(base.WithFollowSymlinks(b...)) +} + +func WithPreserveDir(b ...bool) Option { + return wrapBase(base.WithPreserveDir(b...)) +} + +func WithCompressWithGzip(b ...bool) Option { + return wrapBase(base.WithCompressWithGzip(b...)) +} diff --git a/api/ocm/elements/artifactblob/dirtreeblob/resource.go b/api/ocm/elements/artifactblob/dirtreeblob/resource.go new file mode 100644 index 000000000..217046924 --- /dev/null +++ b/api/ocm/elements/artifactblob/dirtreeblob/resource.go @@ -0,0 +1,33 @@ +package dirtreeblob + +import ( + "github.com/mandelsoft/goutils/generics" + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/utils/blobaccess/dirtree" +) + +const TYPE = resourcetypes.DIRECTORY_TREE + +func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, path string, opts ...Option) cpi.ArtifactAccess[M] { + eff := optionutils.EvalOptions(opts...) + if meta.GetType() == "" { + meta.SetType(TYPE) + } + blobprov := dirtree.Provider(path, &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 *cpi.ResourceMeta, path string, opts ...Option) cpi.ResourceAccess { + return Access(ctx, meta, path, opts...) +} + +func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, path string, opts ...Option) cpi.SourceAccess { + return Access(ctx, meta, path, opts...) +} diff --git a/api/ocm/elements/artifactblob/dirtreeblob/resource_test.go b/api/ocm/elements/artifactblob/dirtreeblob/resource_test.go new file mode 100644 index 000000000..5e49d9954 --- /dev/null +++ b/api/ocm/elements/artifactblob/dirtreeblob/resource_test.go @@ -0,0 +1,75 @@ +package dirtreeblob_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + testenv "ocm.software/ocm/api/helper/env" + "ocm.software/ocm/api/ocm/compdesc" + me "ocm.software/ocm/api/ocm/elements/artifactblob/dirtreeblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/api/utils/tarutils" +) + +var _ = Describe("dir tree resource access", func() { + var env *testenv.Environment + + BeforeEach(func() { + env = testenv.NewEnvironment(testenv.TestData()) + }) + + AfterEach(func() { + env.Cleanup() + }) + + It("creates resource", func() { + global := ociartifact.New("ghcr.io/mandelsoft/demo:v1.0.0") + + acc := me.ResourceAccess(env.OCMContext(), compdesc.NewResourceMeta("test", "", compdesc.LocalRelation), "testdata", + me.WithExcludeFiles([]string{"dir/a"}), + me.WithFileSystem(env.FileSystem()), + me.WithHint("demo"), + me.WithGlobalAccess(global), + ) + + Expect(acc.ReferenceHint()).To(Equal("demo")) + Expect(acc.GlobalAccess()).To(Equal(global)) + Expect(acc.Meta().Type).To(Equal(resourcetypes.DIRECTORY_TREE)) + + blob := Must(acc.BlobAccess()) + defer Defer(blob.Close, "blob") + Expect(blob.MimeType()).To(Equal(mime.MIME_TAR)) + + r := Must(blob.Reader()) + defer Defer(r.Close, "reader") + files := Must(tarutils.ListArchiveContentFromReader(r)) + Expect(files).To(ConsistOf([]string{ + "dir", + "dir/b", + "dir/c", + })) + }) + + It("adds resource", func() { + global := ociartifact.New("ghcr.io/mandelsoft/demo:v1.0.0") + + acc := me.ResourceAccess(env.OCMContext(), compdesc.NewResourceMeta("test", "", compdesc.LocalRelation), "testdata", + me.WithExcludeFiles([]string{"dir/a"}), + me.WithFileSystem(env.FileSystem()), + me.WithHint("demo"), + me.WithGlobalAccess(global), + ) + + arch := Must(ctf.Create(env, accessobj.ACC_CREATE, "ctf", 0o700, env, accessobj.FormatDirectory)) + c := Must(arch.LookupComponent("arcme.org/test")) + v := Must(c.NewVersion("v1.0.0")) + + MustBeSuccessful(v.SetResourceByAccess(acc)) + MustBeSuccessful(c.AddVersion(v)) + }) +}) diff --git a/pkg/contexts/ocm/elements/artifactblob/dirtreeblob/suite_test.go b/api/ocm/elements/artifactblob/dirtreeblob/suite_test.go similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/dirtreeblob/suite_test.go rename to api/ocm/elements/artifactblob/dirtreeblob/suite_test.go diff --git a/pkg/contexts/ocm/elements/artifactblob/dirtreeblob/testdata/dir/a b/api/ocm/elements/artifactblob/dirtreeblob/testdata/dir/a similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/dirtreeblob/testdata/dir/a rename to api/ocm/elements/artifactblob/dirtreeblob/testdata/dir/a diff --git a/pkg/contexts/ocm/elements/artifactblob/dirtreeblob/testdata/dir/b b/api/ocm/elements/artifactblob/dirtreeblob/testdata/dir/b similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/dirtreeblob/testdata/dir/b rename to api/ocm/elements/artifactblob/dirtreeblob/testdata/dir/b diff --git a/pkg/contexts/ocm/elements/artifactblob/dirtreeblob/testdata/dir/c b/api/ocm/elements/artifactblob/dirtreeblob/testdata/dir/c similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/dirtreeblob/testdata/dir/c rename to api/ocm/elements/artifactblob/dirtreeblob/testdata/dir/c diff --git a/pkg/contexts/ocm/elements/artifactblob/doc.go b/api/ocm/elements/artifactblob/doc.go similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/doc.go rename to api/ocm/elements/artifactblob/doc.go diff --git a/api/ocm/elements/artifactblob/dockerdaemonblob/options.go b/api/ocm/elements/artifactblob/dockerdaemonblob/options.go new file mode 100644 index 000000000..60d1907f0 --- /dev/null +++ b/api/ocm/elements/artifactblob/dockerdaemonblob/options.go @@ -0,0 +1,69 @@ +package dockerdaemonblob + +import ( + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/elements/artifactblob/api" + base "ocm.software/ocm/api/utils/blobaccess/dockerdaemon" + common "ocm.software/ocm/api/utils/misc" +) + +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) +} + +//////////////////////////////////////////////////////////////////////////////// +// Docker BlobAccess 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 WithName(n string) Option { + return wrapBase(base.WithName(n)) +} + +func WithVersion(v string) Option { + return wrapBase(base.WithVersion(v)) +} + +func WithVersionOverride(v string, flag ...bool) Option { + return wrapBase(base.WithVersionOverride(v, flag...)) +} + +func WithOrigin(o common.NameVersion) Option { + return wrapBase(base.WithOrigin(o)) +} diff --git a/api/ocm/elements/artifactblob/dockerdaemonblob/resource.go b/api/ocm/elements/artifactblob/dockerdaemonblob/resource.go new file mode 100644 index 000000000..33001bc89 --- /dev/null +++ b/api/ocm/elements/artifactblob/dockerdaemonblob/resource.go @@ -0,0 +1,40 @@ +package dockerdaemonblob + +import ( + "github.com/mandelsoft/goutils/generics" + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/utils/blobaccess/dockerdaemon" +) + +const TYPE = resourcetypes.OCI_IMAGE + +func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, name string, opts ...Option) cpi.ArtifactAccess[M] { + eff := optionutils.EvalOptions(opts...) + if meta.GetType() == "" { + meta.SetType(TYPE) + } + eff.Blob.Context = ctx.OCIContext() + locator, version, err := dockerdaemon.ImageInfoFor(name, &eff.Blob) + if err == nil { + version = eff.Blob.Version + } + hint := ociartifact.Hint(optionutils.AsValue(eff.Blob.Origin), locator, eff.Hint, version) + blobprov := dockerdaemon.Provider(name, &eff.Blob) + accprov := cpi.NewAccessProviderForBlobAccessProvider(ctx, blobprov, 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 *cpi.ResourceMeta, path string, opts ...Option) cpi.ResourceAccess { + return Access(ctx, meta, path, opts...) +} + +func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, path string, opts ...Option) cpi.SourceAccess { + return Access(ctx, meta, path, opts...) +} diff --git a/api/ocm/elements/artifactblob/dockermultiblob/options.go b/api/ocm/elements/artifactblob/dockermultiblob/options.go new file mode 100644 index 000000000..e7f5630a1 --- /dev/null +++ b/api/ocm/elements/artifactblob/dockermultiblob/options.go @@ -0,0 +1,69 @@ +package dockermultiblob + +import ( + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/elements/artifactblob/api" + base "ocm.software/ocm/api/utils/blobaccess/dockermulti" + common "ocm.software/ocm/api/utils/misc" +) + +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) +} + +//////////////////////////////////////////////////////////////////////////////// +// Docker BlobAccess 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 WithVariants(names ...string) Option { + return wrapBase(base.WithVariants(names...)) +} + +func WithVersion(v string) Option { + return wrapBase(base.WithVersion(v)) +} + +func WithOrigin(o common.NameVersion) Option { + return wrapBase(base.WithOrigin(o)) +} + +func WithPrinter(p common.Printer) Option { + return wrapBase(base.WithPrinter(p)) +} diff --git a/api/ocm/elements/artifactblob/dockermultiblob/resource.go b/api/ocm/elements/artifactblob/dockermultiblob/resource.go new file mode 100644 index 000000000..9c58dd3bb --- /dev/null +++ b/api/ocm/elements/artifactblob/dockermultiblob/resource.go @@ -0,0 +1,35 @@ +package dockermultiblob + +import ( + "github.com/mandelsoft/goutils/generics" + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/utils/blobaccess/dockermulti" +) + +const TYPE = resourcetypes.OCI_IMAGE + +func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, opts ...Option) cpi.ArtifactAccess[M] { + eff := optionutils.EvalOptions(opts...) + if meta.GetType() == "" { + meta.SetType(TYPE) + } + eff.Blob.Context = ctx.OCIContext() + + blobprov := dockermulti.Provider(&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 *cpi.ResourceMeta, opts ...Option) cpi.ResourceAccess { + return Access(ctx, meta, opts...) +} + +func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, name string, opts ...Option) cpi.SourceAccess { + return Access(ctx, meta, opts...) +} diff --git a/pkg/contexts/ocm/elements/artifactblob/externalblob/doc.go b/api/ocm/elements/artifactblob/externalblob/doc.go similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/externalblob/doc.go rename to api/ocm/elements/artifactblob/externalblob/doc.go diff --git a/api/ocm/elements/artifactblob/externalblob/options.go b/api/ocm/elements/artifactblob/externalblob/options.go new file mode 100644 index 000000000..74a51d1f9 --- /dev/null +++ b/api/ocm/elements/artifactblob/externalblob/options.go @@ -0,0 +1,22 @@ +package externalblob + +import ( + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/elements/artifactblob/api" +) + +type ( + Option = api.Option + Options = api.Options +) + +//////////////////////////////////////////////////////////////////////////////// +// General Options + +func WithHint(h string) Option { + return api.WrapHint[Options](h) +} + +func WithGlobalAccess(a cpi.AccessSpec) Option { + return api.WrapGlobalAccess[Options](a) +} diff --git a/api/ocm/elements/artifactblob/externalblob/resource.go b/api/ocm/elements/artifactblob/externalblob/resource.go new file mode 100644 index 000000000..0bf8f45ac --- /dev/null +++ b/api/ocm/elements/artifactblob/externalblob/resource.go @@ -0,0 +1,68 @@ +package externalblob + +import ( + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/generics" + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" +) + +func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, access ocm.AccessSpec, opts ...Option) (cpi.ArtifactAccess[M], error) { + eff := optionutils.EvalOptions(opts...) + + hint := eff.Hint + if hint == "" { + hint = ocm.ReferenceHint(access, &cpi.DummyComponentVersionAccess{ctx}) + } + global := eff.Global + if global == nil { + global = ocm.GlobalAccess(access, ctx) + } + + prov, err := cpi.NewAccessProviderForExternalAccessSpec(ctx, access) + if err != nil { + return nil, errors.Wrapf(err, "invalid external access method %q", access.GetKind()) + } + return cpi.NewArtifactAccessForProvider(generics.Cast[*M](meta), newAccessProvider(prov, hint, global)), nil +} + +type _accessProvider = cpi.AccessProvider + +type accessProvider struct { + _accessProvider + hint string + global cpi.AccessSpec +} + +func newAccessProvider(prov cpi.AccessProvider, hint string, global cpi.AccessSpec) cpi.AccessProvider { + return &accessProvider{ + _accessProvider: prov, + hint: hint, + global: global, + } +} + +func (p *accessProvider) ReferenceHint() string { + if p.hint != "" { + return p.hint + } + return p._accessProvider.ReferenceHint() +} + +func (p *accessProvider) GlobalAccess() cpi.AccessSpec { + if p.global != nil { + return p.global + } + return p._accessProvider.GlobalAccess() +} + +func ResourceAccess(ctx ocm.Context, meta *cpi.ResourceMeta, access cpi.AccessSpec, opts ...Option) (cpi.ResourceAccess, error) { + return Access(ctx, meta, access, opts...) +} + +func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, access cpi.AccessSpec, opts ...Option) (cpi.SourceAccess, error) { + return Access(ctx, meta, access, opts...) +} diff --git a/api/ocm/elements/artifactblob/fileblob/options.go b/api/ocm/elements/artifactblob/fileblob/options.go new file mode 100644 index 000000000..915a19f7e --- /dev/null +++ b/api/ocm/elements/artifactblob/fileblob/options.go @@ -0,0 +1,85 @@ +package fileblob + +import ( + "github.com/mandelsoft/goutils/optionutils" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/elements/artifactblob/api" +) + +type Option = optionutils.Option[*Options] + +type compressionMode string + +const ( + COMPRESSION = compressionMode("compression") + DECOMPRESSION = compressionMode("decompression") + NONE = compressionMode("") +) + +type Options struct { + api.Options + FileSystem vfs.FileSystem + Compression compressionMode +} + +var ( + _ api.GeneralOptionsProvider = (*Options)(nil) + _ Option = (*Options)(nil) +) + +func (o *Options) ApplyTo(opts *Options) { + o.Options.ApplyTo(&opts.Options) + if o.FileSystem != nil { + opts.FileSystem = o.FileSystem + } +} + +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 + +type filesystem struct { + fs vfs.FileSystem +} + +func (o filesystem) ApplyTo(opts *Options) { + opts.FileSystem = o.fs +} + +func WithFileSystem(fs vfs.FileSystem) Option { + return filesystem{fs} +} + +//////////////////////////////////////////////////////////////////////////////// + +type compression struct { + mode compressionMode +} + +func (o compression) ApplyTo(opts *Options) { + opts.Compression = o.mode +} + +func WithCompression() Option { + return compression{COMPRESSION} +} + +func WithDecompression() Option { + return compression{DECOMPRESSION} +} diff --git a/api/ocm/elements/artifactblob/fileblob/resource.go b/api/ocm/elements/artifactblob/fileblob/resource.go new file mode 100644 index 000000000..63587e05d --- /dev/null +++ b/api/ocm/elements/artifactblob/fileblob/resource.go @@ -0,0 +1,54 @@ +package fileblob + +import ( + "github.com/mandelsoft/goutils/generics" + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/file" + "ocm.software/ocm/api/utils/mime" +) + +const TYPE = "blob" + +func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, media string, meta P, path string, opts ...Option) cpi.ArtifactAccess[M] { + eff := optionutils.EvalOptions(opts...) + + if meta.GetType() == "" { + meta.SetType(TYPE) + } + if media == "" { + media = mime.MIME_OCTET + } + + var blobprov blobaccess.BlobAccessProvider + switch eff.Compression { + case NONE: + blobprov = file.Provider(media, path, eff.FileSystem) + case COMPRESSION: + blob := file.BlobAccess(media, path, eff.FileSystem) + defer blob.Close() + blob, _ = blobaccess.WithCompression(blob) + blobprov = blobaccess.ProviderForBlobAccess(blob) + case DECOMPRESSION: + blob := file.BlobAccess(media, path, eff.FileSystem) + defer blob.Close() + blob, _ = blobaccess.WithDecompression(blob) + blobprov = blobaccess.ProviderForBlobAccess(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, media string, meta *ocm.ResourceMeta, path string, opts ...Option) cpi.ResourceAccess { + return Access(ctx, media, meta, path, opts...) +} + +func SourceAccess(ctx ocm.Context, media string, meta *ocm.SourceMeta, path string, opts ...Option) cpi.SourceAccess { + return Access(ctx, media, meta, path, opts...) +} diff --git a/api/ocm/elements/artifactblob/genericblob/options.go b/api/ocm/elements/artifactblob/genericblob/options.go new file mode 100644 index 000000000..5fa0688ca --- /dev/null +++ b/api/ocm/elements/artifactblob/genericblob/options.go @@ -0,0 +1,19 @@ +package genericblob + +import ( + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/elements/artifactblob/api" +) + +type ( + Options = api.Options + Option = api.Option +) + +func WithHint(h string) Option { + return api.WithHint(h) +} + +func WithGlobalAccess(a cpi.AccessSpec) Option { + return api.WithGlobalAccess(a) +} diff --git a/api/ocm/elements/artifactblob/genericblob/resource.go b/api/ocm/elements/artifactblob/genericblob/resource.go new file mode 100644 index 000000000..1bde5e9a4 --- /dev/null +++ b/api/ocm/elements/artifactblob/genericblob/resource.go @@ -0,0 +1,25 @@ +package genericblob + +import ( + "github.com/mandelsoft/goutils/generics" + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx cpi.Context, meta P, blob blobaccess.BlobAccessProvider, opts ...Option) cpi.ArtifactAccess[M] { + eff := optionutils.EvalOptions(opts...) + accprov := cpi.NewAccessProviderForBlobAccessProvider(ctx, blob, 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 cpi.Context, media string, meta *cpi.ResourceMeta, blob blobaccess.BlobAccessProvider, opts ...Option) cpi.ResourceAccess { + return Access(ctx, meta, blob, opts...) +} + +func SourceAccess(ctx cpi.Context, media string, meta *cpi.SourceMeta, blob blobaccess.BlobAccessProvider, opts ...Option) cpi.SourceAccess { + return Access(ctx, meta, blob, opts...) +} diff --git a/api/ocm/elements/artifactblob/helmblob/helmblob_test.go b/api/ocm/elements/artifactblob/helmblob/helmblob_test.go new file mode 100644 index 000000000..c30fdb7ca --- /dev/null +++ b/api/ocm/elements/artifactblob/helmblob/helmblob_test.go @@ -0,0 +1,36 @@ +package helmblob_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "ocm.software/ocm/api/helper/builder" + + "ocm.software/ocm/api/helper/env" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/cpi" + me "ocm.software/ocm/api/ocm/elements/artifactblob/helmblob" + ctfocm "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessobj" +) + +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.SetResourceByAccess(me.ResourceAccess(e.OCMContext(), cpi.NewResourceMeta("helm1", "blob", metav1.LocalRelation), "/testdata/testchart1", me.WithFileSystem(e.FileSystem())))) + MustBeSuccessful(cv.SetResourceByAccess(me.ResourceAccess(e.OCMContext(), cpi.NewResourceMeta("helm2", "blob", metav1.LocalRelation), "/testdata/testchart2", me.WithFileSystem(e.FileSystem())))) + MustBeSuccessful(ctf.AddComponentVersion(cv, true)) + }) +}) diff --git a/api/ocm/elements/artifactblob/helmblob/options.go b/api/ocm/elements/artifactblob/helmblob/options.go new file mode 100644 index 000000000..97aa0f3a4 --- /dev/null +++ b/api/ocm/elements/artifactblob/helmblob/options.go @@ -0,0 +1,87 @@ +package helmblob + +import ( + "github.com/mandelsoft/goutils/optionutils" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/elements/artifactblob/api" + base "ocm.software/ocm/api/utils/blobaccess/helm" + common "ocm.software/ocm/api/utils/misc" +) + +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) +} + +//////////////////////////////////////////////////////////////////////////////// +// DirTree BlobAccess 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 WithFileSystem(fs vfs.FileSystem) Option { + return wrapBase(base.WithFileSystem(fs)) +} + +func WithContext(ctx oci.ContextProvider) Option { + return wrapBase(base.WithContext(ctx)) +} + +func WithIVersion(v string) Option { + return wrapBase(base.WithVersion(v)) +} + +func WithIVersionOverride(v string, flag ...bool) Option { + return wrapBase(base.WithVersionOverride(v, flag...)) +} + +func WithCACert(v string) Option { + return wrapBase(base.WithCACert(v)) +} + +func WithCACertFile(v string) Option { + return wrapBase(base.WithCACertFile(v)) +} + +func WithHelmRepository(v string) Option { + return wrapBase(base.WithHelmRepository(v)) +} + +func WithPrinter(v common.Printer) Option { + return wrapBase(base.WithPrinter(v)) +} diff --git a/api/ocm/elements/artifactblob/helmblob/resource.go b/api/ocm/elements/artifactblob/helmblob/resource.go new file mode 100644 index 000000000..3b5a85910 --- /dev/null +++ b/api/ocm/elements/artifactblob/helmblob/resource.go @@ -0,0 +1,34 @@ +package helmblob + +import ( + "github.com/mandelsoft/goutils/generics" + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/utils/blobaccess/helm" +) + +const TYPE = resourcetypes.HELM_CHART + +func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, path string, opts ...Option) cpi.ArtifactAccess[M] { + eff := optionutils.EvalOptions(append(opts, WithContext(ctx))...) + if meta.GetType() == "" { + meta.SetType(TYPE) + } + hint := eff.Hint + blobprov := helm.Provider(path, &eff.Blob) + accprov := cpi.NewAccessProviderForBlobAccessProvider(ctx, blobprov, 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 *cpi.ResourceMeta, path string, opts ...Option) cpi.ResourceAccess { + return Access(ctx, meta, path, opts...) +} + +func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, path string, opts ...Option) cpi.SourceAccess { + return Access(ctx, meta, path, opts...) +} diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/suite_test.go b/api/ocm/elements/artifactblob/helmblob/suite_test.go similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/helmblob/suite_test.go rename to api/ocm/elements/artifactblob/helmblob/suite_test.go diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/.helmignore b/api/ocm/elements/artifactblob/helmblob/testdata/testchart1/.helmignore similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/.helmignore rename to api/ocm/elements/artifactblob/helmblob/testdata/testchart1/.helmignore diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/.idea/somefile b/api/ocm/elements/artifactblob/helmblob/testdata/testchart1/.idea/somefile similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/.idea/somefile rename to api/ocm/elements/artifactblob/helmblob/testdata/testchart1/.idea/somefile diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/Chart.yaml b/api/ocm/elements/artifactblob/helmblob/testdata/testchart1/Chart.yaml similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/Chart.yaml rename to api/ocm/elements/artifactblob/helmblob/testdata/testchart1/Chart.yaml diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/NOTES.txt b/api/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/NOTES.txt similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/NOTES.txt rename to api/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/NOTES.txt diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/_helpers.tpl b/api/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/_helpers.tpl similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/_helpers.tpl rename to api/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/_helpers.tpl diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/deployment.yaml b/api/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/deployment.yaml similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/deployment.yaml rename to api/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/deployment.yaml diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/hpa.yaml b/api/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/hpa.yaml similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/hpa.yaml rename to api/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/hpa.yaml diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/ingress.yaml b/api/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/ingress.yaml similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/ingress.yaml rename to api/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/ingress.yaml diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/service.yaml b/api/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/service.yaml similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/service.yaml rename to api/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/service.yaml diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/serviceaccount.yaml b/api/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/serviceaccount.yaml similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/serviceaccount.yaml rename to api/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/serviceaccount.yaml diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/tests/test-connection.yaml b/api/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/tests/test-connection.yaml similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/tests/test-connection.yaml rename to api/ocm/elements/artifactblob/helmblob/testdata/testchart1/templates/tests/test-connection.yaml diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/values.yaml b/api/ocm/elements/artifactblob/helmblob/testdata/testchart1/values.yaml similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart1/values.yaml rename to api/ocm/elements/artifactblob/helmblob/testdata/testchart1/values.yaml diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/.helmignore b/api/ocm/elements/artifactblob/helmblob/testdata/testchart2/.helmignore similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/.helmignore rename to api/ocm/elements/artifactblob/helmblob/testdata/testchart2/.helmignore diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/.idea/somefile b/api/ocm/elements/artifactblob/helmblob/testdata/testchart2/.idea/somefile similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/.idea/somefile rename to api/ocm/elements/artifactblob/helmblob/testdata/testchart2/.idea/somefile diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/Chart.yaml b/api/ocm/elements/artifactblob/helmblob/testdata/testchart2/Chart.yaml similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/Chart.yaml rename to api/ocm/elements/artifactblob/helmblob/testdata/testchart2/Chart.yaml diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/NOTES.txt b/api/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/NOTES.txt similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/NOTES.txt rename to api/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/NOTES.txt diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/_helpers.tpl b/api/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/_helpers.tpl similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/_helpers.tpl rename to api/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/_helpers.tpl diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/deployment.yaml b/api/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/deployment.yaml similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/deployment.yaml rename to api/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/deployment.yaml diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/hpa.yaml b/api/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/hpa.yaml similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/hpa.yaml rename to api/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/hpa.yaml diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/ingress.yaml b/api/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/ingress.yaml similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/ingress.yaml rename to api/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/ingress.yaml diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/service.yaml b/api/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/service.yaml similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/service.yaml rename to api/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/service.yaml diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/serviceaccount.yaml b/api/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/serviceaccount.yaml similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/serviceaccount.yaml rename to api/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/serviceaccount.yaml diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/tests/test-connection.yaml b/api/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/tests/test-connection.yaml similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/tests/test-connection.yaml rename to api/ocm/elements/artifactblob/helmblob/testdata/testchart2/templates/tests/test-connection.yaml diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/values.yaml b/api/ocm/elements/artifactblob/helmblob/testdata/testchart2/values.yaml similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/helmblob/testdata/testchart2/values.yaml rename to api/ocm/elements/artifactblob/helmblob/testdata/testchart2/values.yaml diff --git a/api/ocm/elements/artifactblob/mavenblob/access_test.go b/api/ocm/elements/artifactblob/mavenblob/access_test.go new file mode 100644 index 000000000..c2810e18c --- /dev/null +++ b/api/ocm/elements/artifactblob/mavenblob/access_test.go @@ -0,0 +1,71 @@ +package mavenblob_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + + "ocm.software/ocm/api/ocm/elements" + me "ocm.software/ocm/api/ocm/elements/artifactblob/mavenblob" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/repositories/composition" + "ocm.software/ocm/api/tech/maven" + "ocm.software/ocm/api/tech/maven/maventest" +) + +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())) + Expect(a.ReferenceHint()).To(Equal("")) + b := Must(a.BlobAccess()) + defer Close(b) + Expect(string(Must(b.Get()))).To(Equal(`{"some": "test content"}`)) + + MustBeSuccessful(cv.SetResourceByAccess(a)) + r := Must(cv.GetResourceByIndex(0)) + m := Must(r.AccessMethod()) + defer Close(m) + Expect(string(Must(m.Get()))).To(Equal(`{"some": "test content"}`)) + }) + + It("blobaccess for package", 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") + + a := me.ResourceAccessForMavenCoords(env.OCMContext(), Must(elements.ResourceMeta("mavenblob", resourcetypes.OCM_JSON, elements.WithLocalRelation())), repo, coords, me.WithCachingFileSystem(env.FileSystem())) + Expect(a.ReferenceHint()).To(Equal(coords.GAV())) + }) + }) +}) diff --git a/api/ocm/elements/artifactblob/mavenblob/options.go b/api/ocm/elements/artifactblob/mavenblob/options.go new file mode 100644 index 000000000..ae05fd95e --- /dev/null +++ b/api/ocm/elements/artifactblob/mavenblob/options.go @@ -0,0 +1,104 @@ +package mavenblob + +import ( + "github.com/mandelsoft/goutils/optionutils" + "github.com/mandelsoft/logging" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/elements/artifactblob/api" + "ocm.software/ocm/api/tech/maven" + base "ocm.software/ocm/api/utils/blobaccess/maven" +) + +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 WithHintForCoords(coords *maven.Coordinates) Option { + if coords.IsPackage() { + return WithHint(coords.GAV()) + } + return optionutils.NoOption[*Options]{} +} + +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/api/ocm/elements/artifactblob/mavenblob/resource.go b/api/ocm/elements/artifactblob/mavenblob/resource.go new file mode 100644 index 000000000..b30878cee --- /dev/null +++ b/api/ocm/elements/artifactblob/mavenblob/resource.go @@ -0,0 +1,46 @@ +package mavenblob + +import ( + "github.com/mandelsoft/goutils/generics" + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/utils/blobaccess/maven" +) + +const TYPE = resourcetypes.MAVEN_PACKAGE + +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 eff.Blob.IsPackage() && eff.Hint == "" { + eff.Hint = maven.NewCoordinates(groupId, artifactId, version).GAV() + } + + if meta.GetType() == "" { + meta.SetType(TYPE) + } + + blobprov := maven.Provider(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/api/ocm/elements/artifactblob/mavenblob/suite_test.go similarity index 100% rename from pkg/contexts/ocm/elements/artifactblob/mavenblob/suite_test.go rename to api/ocm/elements/artifactblob/mavenblob/suite_test.go diff --git a/api/ocm/elements/artifactblob/ociartifactblob/options.go b/api/ocm/elements/artifactblob/ociartifactblob/options.go new file mode 100644 index 000000000..65a4fe6d0 --- /dev/null +++ b/api/ocm/elements/artifactblob/ociartifactblob/options.go @@ -0,0 +1,66 @@ +package ociartifactblob + +import ( + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/elements/artifactblob/api" + base "ocm.software/ocm/api/utils/blobaccess/ociartifact" + common "ocm.software/ocm/api/utils/misc" +) + +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) +} + +//////////////////////////////////////////////////////////////////////////////// +// DirTree BlobAccess 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 WithContext(ctx oci.ContextProvider) Option { + return wrapBase(base.WithContext(ctx)) +} + +func WithVersion(v string) Option { + return wrapBase(base.WithVersion(v)) +} + +func WithPrinter(v common.Printer) Option { + return wrapBase(base.WithPrinter(v)) +} diff --git a/api/ocm/elements/artifactblob/ociartifactblob/resource.go b/api/ocm/elements/artifactblob/ociartifactblob/resource.go new file mode 100644 index 000000000..48c848f36 --- /dev/null +++ b/api/ocm/elements/artifactblob/ociartifactblob/resource.go @@ -0,0 +1,43 @@ +package ociartifactblob + +import ( + "github.com/mandelsoft/goutils/generics" + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + blob "ocm.software/ocm/api/utils/blobaccess/ociartifact" +) + +const TYPE = resourcetypes.OCI_IMAGE + +func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, refname string, opts ...Option) cpi.ArtifactAccess[M] { + eff := optionutils.EvalOptions(append(opts, WithContext(ctx))...) + if meta.GetType() == "" { + meta.SetType(TYPE) + } + + hint := eff.Hint + if hint == "" { + ref, err := oci.ParseRef(refname) + if err == nil { + hint = ref.String() + } + } + + blobprov := blob.Provider(refname, &eff.Blob) + accprov := cpi.NewAccessProviderForBlobAccessProvider(ctx, blobprov, 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 *cpi.ResourceMeta, path string, opts ...Option) cpi.ResourceAccess { + return Access(ctx, meta, path, opts...) +} + +func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, path string, opts ...Option) cpi.SourceAccess { + return Access(ctx, meta, path, opts...) +} diff --git a/api/ocm/elements/artifactblob/textblob/options.go b/api/ocm/elements/artifactblob/textblob/options.go new file mode 100644 index 000000000..145c9f0ba --- /dev/null +++ b/api/ocm/elements/artifactblob/textblob/options.go @@ -0,0 +1,43 @@ +package textblob + +import ( + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/elements/artifactblob/datablob" +) + +type ( + Option = datablob.Option + Options = datablob.Options +) + +const ( + COMPRESSION = datablob.COMPRESSION + DECOMPRESSION = datablob.DECOMPRESSION + NONE = datablob.NONE +) + +//////////////////////////////////////////////////////////////////////////////// +// General Options + +func WithHint(h string) Option { + return datablob.WithHint(h) +} + +func WithGlobalAccess(a cpi.AccessSpec) Option { + return datablob.WithGlobalAccess(a) +} + +//////////////////////////////////////////////////////////////////////////////// +// Local Options + +func WithMimeType(mime string) Option { + return datablob.WithMimeType(mime) +} + +func WithCompression() Option { + return datablob.WithCompression() +} + +func WithDecompression() Option { + return datablob.WithDecompression() +} diff --git a/api/ocm/elements/artifactblob/textblob/resource.go b/api/ocm/elements/artifactblob/textblob/resource.go new file mode 100644 index 000000000..7b20ff68d --- /dev/null +++ b/api/ocm/elements/artifactblob/textblob/resource.go @@ -0,0 +1,27 @@ +package textblob + +import ( + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/elements/artifactblob/datablob" + "ocm.software/ocm/api/utils/mime" +) + +func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, blob string, opts ...Option) cpi.ArtifactAccess[M] { + eff := optionutils.EvalOptions(opts...) + if eff.MimeType == "" { + eff.MimeType = mime.MIME_TEXT + } + return datablob.Access(ctx, meta, []byte(blob), eff) +} + +func ResourceAccess(ctx ocm.Context, meta *ocm.ResourceMeta, blob string, opts ...Option) cpi.ResourceAccess { + return Access(ctx, meta, blob, opts...) +} + +func SourceAccess(ctx ocm.Context, meta *ocm.SourceMeta, blob string, opts ...Option) cpi.SourceAccess { + return Access(ctx, meta, blob, opts...) +} diff --git a/api/ocm/elements/artifactblob/wgetblob/options.go b/api/ocm/elements/artifactblob/wgetblob/options.go new file mode 100644 index 000000000..cf17a570f --- /dev/null +++ b/api/ocm/elements/artifactblob/wgetblob/options.go @@ -0,0 +1,90 @@ +package wgetblob + +import ( + "io" + "net/http" + + "github.com/mandelsoft/goutils/optionutils" + "github.com/mandelsoft/logging" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/elements/artifactblob/api" + "ocm.software/ocm/api/ocm/extensions/accessmethods/wget" + base "ocm.software/ocm/api/utils/blobaccess/wget" +) + +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 WithMimeType(mime string) Option { + return wrapBase(base.WithMimeType(mime)) +} + +func WithCredentials(creds credentials.Credentials) Option { + return wrapBase(base.WithCredentials(creds)) +} + +func WithHeader(h http.Header) Option { + return wrapBase(base.WithHeader(h)) +} + +func WithVerb(v string) Option { + return wrapBase(base.WithVerb(v)) +} + +func WithBody(v io.Reader) Option { + return wrapBase(base.WithBody(v)) +} + +func WithNoRedirect(r ...bool) Option { + return wrapBase(wget.WithNoRedirect(r...)) +} diff --git a/api/ocm/elements/artifactblob/wgetblob/resource.go b/api/ocm/elements/artifactblob/wgetblob/resource.go new file mode 100644 index 000000000..02306f06d --- /dev/null +++ b/api/ocm/elements/artifactblob/wgetblob/resource.go @@ -0,0 +1,35 @@ +package wgetblob + +import ( + "github.com/mandelsoft/goutils/generics" + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/utils/blobaccess/wget" +) + +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(optionutils.WithDefaults(opts, WithCredentialContext(ctx))...) + + if meta.GetType() == "" { + meta.SetType(TYPE) + } + + blobprov := wget.Provider(url, &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, url string, opts ...Option) cpi.ResourceAccess { + return Access(ctx, meta, url, opts...) +} + +func SourceAccess(ctx ocm.Context, meta *ocm.SourceMeta, url string, opts ...Option) cpi.SourceAccess { + return Access(ctx, meta, url, opts...) +} diff --git a/pkg/contexts/ocm/elements/artifacts.go b/api/ocm/elements/artifacts.go similarity index 100% rename from pkg/contexts/ocm/elements/artifacts.go rename to api/ocm/elements/artifacts.go diff --git a/pkg/contexts/ocm/elements/common.go b/api/ocm/elements/common.go similarity index 93% rename from pkg/contexts/ocm/elements/common.go rename to api/ocm/elements/common.go index c4321bff1..1b210eeab 100644 --- a/pkg/contexts/ocm/elements/common.go +++ b/api/ocm/elements/common.go @@ -1,8 +1,8 @@ package elements import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" ) type CommonOption interface { diff --git a/pkg/contexts/ocm/elements/digests.go b/api/ocm/elements/digests.go similarity index 84% rename from pkg/contexts/ocm/elements/digests.go rename to api/ocm/elements/digests.go index 2a80de0eb..4aa372761 100644 --- a/pkg/contexts/ocm/elements/digests.go +++ b/api/ocm/elements/digests.go @@ -1,8 +1,8 @@ package elements import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" ) type ResourceReferenceOption interface { diff --git a/pkg/contexts/ocm/elements/doc.go b/api/ocm/elements/doc.go similarity index 100% rename from pkg/contexts/ocm/elements/doc.go rename to api/ocm/elements/doc.go diff --git a/api/ocm/elements/references.go b/api/ocm/elements/references.go new file mode 100644 index 000000000..64bb1619f --- /dev/null +++ b/api/ocm/elements/references.go @@ -0,0 +1,22 @@ +package elements + +import ( + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/ocm/compdesc" +) + +type ReferenceOption interface { + ApplyToReference(reference *compdesc.ComponentReference) error +} + +func Reference(name, comp, vers string, opts ...ReferenceOption) (*compdesc.ComponentReference, error) { + m := compdesc.NewComponentReference(name, comp, vers, nil) + list := errors.ErrList() + for _, o := range opts { + if o != nil { + list.Add(o.ApplyToReference(m)) + } + } + return m, list.Result() +} diff --git a/api/ocm/elements/resources.go b/api/ocm/elements/resources.go new file mode 100644 index 000000000..1938877f0 --- /dev/null +++ b/api/ocm/elements/resources.go @@ -0,0 +1,78 @@ +package elements + +import ( + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/utils" +) + +type ResourceMetaOption interface { + ApplyToResourceMeta(*compdesc.ResourceMeta) error +} + +func ResourceMeta(name, typ string, opts ...ResourceMetaOption) (*compdesc.ResourceMeta, error) { + m := compdesc.NewResourceMeta(name, typ, metav1.LocalRelation) + list := errors.ErrList() + for _, o := range opts { + if o != nil { + list.Add(o.ApplyToResourceMeta(m)) + } + } + return m, list.Result() +} + +//////////////////////////////////////////////////////////////////////////////// + +type local bool + +func (o local) ApplyToResourceMeta(m *compdesc.ResourceMeta) error { + if o { + m.Relation = metav1.LocalRelation + } else { + m.Relation = metav1.ExternalRelation + } + return nil +} + +// WithLocalRelation sets the resource relation to metav1.LocalRelation. +func WithLocalRelation(flag ...bool) ResourceMetaOption { + return local(utils.OptionalDefaultedBool(true, flag...)) +} + +// WithExternalRelation sets the resource relation to metav1.ExternalRelation. +func WithExternalRelation(flag ...bool) ResourceMetaOption { + return local(!utils.OptionalDefaultedBool(true, flag...)) +} + +//////////////////////////////////////////////////////////////////////////////// + +type srcref struct { + ref metav1.StringMap + labels metav1.Labels + errlist errors.ErrorList +} + +var _ ResourceMetaOption = (*srcref)(nil) + +func (o *srcref) ApplyToResourceMeta(m *compdesc.ResourceMeta) error { + if err := o.errlist.Result(); err != nil { + return err + } + m.SourceRefs = append(m.SourceRefs, compdesc.SourceRef{IdentitySelector: o.ref.Copy(), Labels: o.labels.Copy()}) + return nil +} + +func (o *srcref) WithLabel(name string, value interface{}, opts ...metav1.LabelOption) *srcref { + r := &srcref{ref: o.ref, labels: o.labels.Copy()} + r.errlist.Add(r.labels.Set(name, value, opts...)) + return r +} + +// WithSourceRef adds a source reference to a resource meta object. +// this is a sequence of name/value pairs. +// Optionally, additional labels can be added with srcref.WithLabel. +func WithSourceRef(sel ...string) *srcref { + return &srcref{ref: metav1.StringMap(metav1.NewExtraIdentity(sel...))} +} diff --git a/pkg/contexts/ocm/elements/resources_test.go b/api/ocm/elements/resources_test.go similarity index 77% rename from pkg/contexts/ocm/elements/resources_test.go rename to api/ocm/elements/resources_test.go index 002398fac..33c37829c 100644 --- a/pkg/contexts/ocm/elements/resources_test.go +++ b/api/ocm/elements/resources_test.go @@ -5,10 +5,10 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/digester/digesters/blob" - me "github.com/open-component-model/ocm/pkg/contexts/ocm/elements" - "github.com/open-component-model/ocm/pkg/signing/hasher/sha256" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + me "ocm.software/ocm/api/ocm/elements" + "ocm.software/ocm/api/ocm/extensions/digester/digesters/blob" + "ocm.software/ocm/api/tech/signing/hasher/sha256" ) type value struct { diff --git a/pkg/contexts/ocm/elements/sources.go b/api/ocm/elements/sources.go similarity index 86% rename from pkg/contexts/ocm/elements/sources.go rename to api/ocm/elements/sources.go index 22f7e1292..c4b053ab1 100644 --- a/pkg/contexts/ocm/elements/sources.go +++ b/api/ocm/elements/sources.go @@ -3,7 +3,7 @@ package elements import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" + "ocm.software/ocm/api/ocm/compdesc" ) type SourceMetaOption interface { diff --git a/pkg/contexts/ocm/elements/suite_test.go b/api/ocm/elements/suite_test.go similarity index 100% rename from pkg/contexts/ocm/elements/suite_test.go rename to api/ocm/elements/suite_test.go diff --git a/api/ocm/extensions/accessmethods/compose/method.go b/api/ocm/extensions/accessmethods/compose/method.go new file mode 100644 index 000000000..9228d2b1c --- /dev/null +++ b/api/ocm/extensions/accessmethods/compose/method.go @@ -0,0 +1,145 @@ +package compose + +import ( + "fmt" + "io" + "sync/atomic" + + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/runtime" +) + +// Type is the access type of GitHub registry. +const ( + Type = "compose" + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +func Is(spec accspeccpi.AccessSpec) bool { + return spec != nil && spec.GetKind() == Type +} + +// AccessSpec describes the access for a GitHub registry. +type AccessSpec struct { + runtime.ObjectVersionedType `json:",inline"` + + // Id is the internal id to identify the content + Id string `json:"id"` + + // MediaType is the media type of the object represented by the blob + MediaType string `json:"mediaType"` + + // GlobalAccess is an optional field describing a possibility + // for a global access. If given, it MUST describe a global access method. + GlobalAccess *accspeccpi.AccessSpecRef `json:"globalAccess,omitempty"` + // ReferenceName is an optional static name the object should be + // use in a local repository context. It is use by a repository + // to optionally determine a globally referencable access according + // to the OCI distribution spec. The result will be stored + // by the repository in the field ImageReference. + // The value is typically an OCI repository name optionally + // followed by a colon ':' and a tag + ReferenceName string `json:"referenceName,omitempty"` +} + +var ( + _ accspeccpi.AccessSpec = (*AccessSpec)(nil) + _ accspeccpi.HintProvider = (*AccessSpec)(nil) + _ accspeccpi.GlobalAccessProvider = (*AccessSpec)(nil) +) + +// New creates a new GitHub registry access spec version v1. +func New(hint string, mediaType string, global accspeccpi.AccessSpec) *AccessSpec { + id := fmt.Sprintf("compose-%d", number.Add(1)) + s := &AccessSpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(Type), + Id: id, + ReferenceName: hint, + MediaType: mediaType, + GlobalAccess: accspeccpi.NewAccessSpecRef(global), + } + return s +} + +var number atomic.Int64 + +func (a *AccessSpec) Describe(ctx accspeccpi.Context) string { + return fmt.Sprintf("Composition blob %s", a.Id) +} + +func (_ *AccessSpec) IsLocal(accspeccpi.Context) bool { + return true +} + +func (a *AccessSpec) GetReferenceHint(cv accspeccpi.ComponentVersionAccess) string { + return a.ReferenceName +} + +func (a *AccessSpec) GlobalAccessSpec(ctx accspeccpi.Context) accspeccpi.AccessSpec { + if g, err := ctx.AccessSpecForSpec(a.GlobalAccess); err == nil { + return g + } + return a.GlobalAccess.Unwrap() +} + +func (_ *AccessSpec) GetType() string { + return Type +} + +func (a *AccessSpec) AccessMethod(cv accspeccpi.ComponentVersionAccess) (accspeccpi.AccessMethod, error) { + return cv.AccessMethod(a) +} + +type accessMethod struct { + access blobaccess.BlobAccess + + spec *AccessSpec +} + +var _ accspeccpi.AccessMethodImpl = (*accessMethod)(nil) + +func NewMethod(spec *AccessSpec, blob blobaccess.BlobAccess) (accspeccpi.AccessMethod, error) { + if blob.MimeType() != spec.MediaType { + return nil, fmt.Errorf("mimetype mismatch (spec=%s, blob=%s)", spec.MediaType, blob.MimeType()) + } + b, err := blob.Dup() + if err != nil { + return nil, err + } + return accspeccpi.AccessMethodForImplementation(&accessMethod{ + access: b, + spec: spec, + }, nil) +} + +func (_ *accessMethod) IsLocal() bool { + return true +} + +func (m *accessMethod) GetKind() string { + return Type +} + +func (m *accessMethod) MimeType() string { + return m.access.MimeType() +} + +func (m *accessMethod) AccessSpec() accspeccpi.AccessSpec { + return m.spec +} + +func (m *accessMethod) Get() ([]byte, error) { + return m.access.Get() +} + +func (m *accessMethod) Reader() (io.ReadCloser, error) { + return m.access.Reader() +} + +func (m *accessMethod) Close() error { + if m.access == nil { + return nil + } + return m.access.Close() +} diff --git a/pkg/contexts/ocm/accessmethods/github/README.md b/api/ocm/extensions/accessmethods/github/README.md similarity index 100% rename from pkg/contexts/ocm/accessmethods/github/README.md rename to api/ocm/extensions/accessmethods/github/README.md diff --git a/api/ocm/extensions/accessmethods/github/cli.go b/api/ocm/extensions/accessmethods/github/cli.go new file mode 100644 index 000000000..9b39bbdce --- /dev/null +++ b/api/ocm/extensions/accessmethods/github/cli.go @@ -0,0 +1,43 @@ +package github + +import ( + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + "ocm.software/ocm/api/utils/cobrautils/flagsets" +) + +func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { + return flagsets.NewConfigOptionTypeSetHandler( + Type, AddConfig, + options.RepositoryOption, + options.HostnameOption, + options.CommitOption, + ) +} + +func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { + flagsets.AddFieldByOptionP(opts, options.RepositoryOption, config, "repoUrl") + flagsets.AddFieldByOptionP(opts, options.CommitOption, config, "commit") + flagsets.AddFieldByOptionP(opts, options.HostnameOption, config, "apiHostname") + return nil +} + +var usage = ` +This method implements the access of the content of a git commit stored in a +GitHub repository. +` + +var formatV1 = ` +The type specific specification fields are: + +- **repoUrl** *string* + + Repository URL with or without scheme. + +- **ref** (optional) *string* + + Original ref used to get the commit from + +- **commit** *string* + + The sha/id of the git commit +` diff --git a/api/ocm/extensions/accessmethods/github/method.go b/api/ocm/extensions/accessmethods/github/method.go new file mode 100644 index 000000000..886a7536f --- /dev/null +++ b/api/ocm/extensions/accessmethods/github/method.go @@ -0,0 +1,314 @@ +package github + +import ( + "context" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "sync" + "unicode" + + "github.com/google/go-github/v45/github" + "github.com/mandelsoft/goutils/errors" + "golang.org/x/oauth2" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/builtin/github/identity" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessio/downloader" + hd "ocm.software/ocm/api/utils/accessio/downloader/http" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/api/utils/runtime" +) + +// Type is the access type of GitHub registry. +const ( + Type = "gitHub" + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +const ( + LegacyType = "github" + LegacyTypeV1 = LegacyType + runtime.VersionSeparator + "v1" +) + +const ShaLength = 40 + +func init() { + accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](Type, accspeccpi.WithDescription(usage))) + accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](TypeV1, accspeccpi.WithFormatSpec(formatV1), accspeccpi.WithConfigHandler(ConfigHandler()))) + accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](LegacyType)) + accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](LegacyTypeV1)) +} + +func Is(spec accspeccpi.AccessSpec) bool { + return spec != nil && spec.GetKind() == Type || spec.GetKind() == LegacyType +} + +// AccessSpec describes the access for a GitHub registry. +type AccessSpec struct { + runtime.ObjectVersionedType `json:",inline"` + + // RepoUrl is the repository URL, with host, owner and repository + RepoURL string `json:"repoUrl"` + + // APIHostname is an optional different hostname for accessing the GitHub REST API + // for enterprise installations + APIHostname string `json:"apiHostname,omitempty"` + + // Commit defines the hash of the commit + Commit string `json:"commit"` + + client *http.Client + downloader downloader.Downloader +} + +var _ accspeccpi.AccessSpec = (*AccessSpec)(nil) + +// AccessSpecOptions defines a set of options which can be applied to the access spec. +type AccessSpecOptions func(s *AccessSpec) + +// WithClient creates an access spec with a custom http client. +func WithClient(client *http.Client) AccessSpecOptions { + return func(s *AccessSpec) { + s.client = client + } +} + +// WithDownloader defines a client with a custom downloader. +func WithDownloader(downloader downloader.Downloader) AccessSpecOptions { + return func(s *AccessSpec) { + s.downloader = downloader + } +} + +// New creates a new GitHub registry access spec version v1. +func New(repoURL, apiHostname, commit string, opts ...AccessSpecOptions) *AccessSpec { + s := &AccessSpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(Type), + RepoURL: repoURL, + APIHostname: apiHostname, + Commit: commit, + } + for _, o := range opts { + o(s) + } + return s +} + +func (a *AccessSpec) Describe(ctx accspeccpi.Context) string { + return fmt.Sprintf("GitHub commit %s[%s]", a.RepoURL, a.Commit) +} + +func (_ *AccessSpec) IsLocal(accspeccpi.Context) bool { + return false +} + +func (a *AccessSpec) GlobalAccessSpec(ctx accspeccpi.Context) accspeccpi.AccessSpec { + return a +} + +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) createHTTPClient(token string) *http.Client { + if token != "" { + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: token}, + ) + ctx := context.Background() + // set up the test client if we have one + if a.client != nil { + ctx = context.WithValue(ctx, oauth2.HTTPClient, a.client) + } + return oauth2.NewClient(ctx, ts) + } + return a.client +} + +// RepositoryService defines capabilities of a GitHub repository. +type RepositoryService interface { + GetArchiveLink(ctx context.Context, owner, repo string, archiveformat github.ArchiveFormat, opts *github.RepositoryContentGetOptions, followRedirects bool) (*url.URL, *github.Response, error) +} + +type accessMethod struct { + lock sync.Mutex + access blobaccess.BlobAccess + + compvers accspeccpi.ComponentVersionAccess + spec *AccessSpec + repositoryService RepositoryService + owner string + repo string + cid credentials.ConsumerIdentity +} + +var _ accspeccpi.AccessMethodImpl = (*accessMethod)(nil) + +func newMethod(c accspeccpi.ComponentVersionAccess, a *AccessSpec) (*accessMethod, error) { + if err := validateCommit(a.Commit); err != nil { + return nil, fmt.Errorf("failed to validate commit: %w", err) + } + + unparsed := a.RepoURL + if !strings.HasPrefix(unparsed, "https://") && !strings.HasPrefix(unparsed, "http://") { + unparsed = "https://" + unparsed + } + u, err := url.Parse(unparsed) + if err != nil { + return nil, errors.ErrInvalidWrap(err, "repository url", a.RepoURL) + } + + path := strings.Trim(u.Path, "/") + pathcomps := strings.Split(path, "/") + if len(pathcomps) != 2 { + return nil, errors.ErrInvalid("repository path", path, a.RepoURL) + } + + token, cid, err := getCreds(unparsed, path, c.GetContext().CredentialsContext()) + if err != nil { + return nil, fmt.Errorf("failed to get creds: %w", err) + } + + var client *github.Client + httpclient := a.createHTTPClient(token) + + if u.Hostname() == "github.com" { + client = github.NewClient(httpclient) + } else { + t := *u + t.Path = "" + if a.APIHostname != "" { + t.Host = a.APIHostname + } + + client, err = github.NewEnterpriseClient(t.String(), t.String(), httpclient) + if err != nil { + return nil, err + } + } + + return &accessMethod{ + spec: a, + compvers: c, + owner: pathcomps[0], + repo: pathcomps[1], + cid: cid, + repositoryService: client.Repositories, + }, nil +} + +func validateCommit(commit string) error { + if len(commit) != ShaLength { + return fmt.Errorf("commit is not a SHA") + } + for _, c := range commit { + if !unicode.IsOneOf([]*unicode.RangeTable{unicode.Letter, unicode.Digit}, c) { + return fmt.Errorf("commit contains invalid characters for a SHA") + } + } + return nil +} + +func getCreds(serverurl, path string, cctx credentials.Context) (string, credentials.ConsumerIdentity, error) { + id := identity.GetConsumerId(serverurl, path) + creds, err := credentials.CredentialsForConsumer(cctx.CredentialsContext(), id, identity.IdentityMatcher) + if creds == nil || err != nil { + return "", id, err + } + return creds.GetProperty(credentials.ATTR_TOKEN), id, nil +} + +func (_ *accessMethod) IsLocal() bool { + return false +} + +func (m *accessMethod) GetKind() string { + return Type +} + +func (m *accessMethod) MimeType() string { + return mime.MIME_TGZ +} + +func (m *accessMethod) AccessSpec() accspeccpi.AccessSpec { + return m.spec +} + +func (m *accessMethod) Get() ([]byte, error) { + if err := m.setup(); err != nil { + return nil, err + } + return m.access.Get() +} + +func (m *accessMethod) Reader() (io.ReadCloser, error) { + if err := m.setup(); err != nil { + return nil, err + } + return m.access.Reader() +} + +func (m *accessMethod) Close() error { + if m.access == nil { + return nil + } + return m.access.Close() +} + +func (m *accessMethod) setup() error { + m.lock.Lock() + defer m.lock.Unlock() + + if m.access != nil { + return nil + } + + // to get the download link technical access to the github repo is required. + // therefore, this part has to be delayed until the effective access and cannot + // be done during creation of the access method object. + link, err := m.getDownloadLink() + if err != nil { + return fmt.Errorf("failed to get download link: %w", err) + } + + d := hd.NewDownloader(link) + if m.spec.downloader != nil { + d = m.spec.downloader + } + + w := accessio.NewWriteAtWriter(d.Download) + cacheBlobAccess := accessobj.CachedBlobAccessForWriter(m.compvers.GetContext(), m.MimeType(), w) + m.access = cacheBlobAccess + return nil +} + +func (m *accessMethod) getDownloadLink() (string, error) { + link, resp, err := m.repositoryService.GetArchiveLink(context.Background(), m.owner, m.repo, github.Tarball, &github.RepositoryContentGetOptions{ + Ref: m.spec.Commit, + }, true) + if err != nil { + return "", err + } + defer resp.Body.Close() + + return link.String(), nil +} + +func (m *accessMethod) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { + return m.cid +} + +func (m *accessMethod) GetIdentityMatcher() string { + return identity.CONSUMER_TYPE +} diff --git a/api/ocm/extensions/accessmethods/github/method_test.go b/api/ocm/extensions/accessmethods/github/method_test.go new file mode 100644 index 000000000..aa1dc69fe --- /dev/null +++ b/api/ocm/extensions/accessmethods/github/method_test.go @@ -0,0 +1,280 @@ +package github_test + +import ( + "bytes" + "fmt" + "io" + "net/http" + "os" + + _ "ocm.software/ocm/api/datacontext/config" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/mandelsoft/filepath/pkg/filepath" + "github.com/mandelsoft/vfs/pkg/osfs" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/builtin/github/identity" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/datacontext/attrs/tmpcache" + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/cpi" + me "ocm.software/ocm/api/ocm/extensions/accessmethods/github" +) + +type mockDownloader struct { + expected []byte + err error +} + +func (m *mockDownloader) Download(w io.WriterAt) error { + if _, err := w.WriteAt(m.expected, 0); err != nil { + return fmt.Errorf("failed to write to mock writer: %w", err) + } + return m.err +} + +// RoundTripFunc . +type RoundTripFunc func(req *http.Request) *http.Response + +// RoundTrip . +func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { + return f(req), nil +} + +// NewTestClient returns *http.Client with Transport replaced to avoid making real calls +func NewTestClient(fn RoundTripFunc) *http.Client { + return &http.Client{ + Transport: fn, + } +} + +var _ = Describe("Method", func() { + var ( + ctx ocm.Context + expectedBlobContent []byte + err error + defaultLink string + accessSpec *me.AccessSpec + fs vfs.FileSystem + expectedURL string + clientFn func(url string) *http.Client + ) + + BeforeEach(func() { + ctx = ocm.New() + expectedBlobContent, err = os.ReadFile(filepath.Join("testdata", "repo.tar.gz")) + Expect(err).ToNot(HaveOccurred()) + defaultLink = "https://github.com/test/test/sha?token=token" + expectedURL = "https://api.github.com/repos/test/test/tarball/7b1445755ee2527f0bf80ef9eeb59a5d2e6e3e1f" + + clientFn = func(url string) *http.Client { + return NewTestClient(func(req *http.Request) *http.Response { + if req.URL.String() != url { + Fail(fmt.Sprintf("failed to match url to expected url. want: %s; got: %s", expectedURL, req.URL.String())) + } + return &http.Response{ + StatusCode: http.StatusFound, + Status: http.StatusText(http.StatusFound), + Body: io.NopCloser(bytes.NewBufferString(`{}`)), + Header: http.Header{ + "Location": []string{defaultLink}, + }, + } + }) + } + + accessSpec = me.New( + "https://github.com/test/test", + "", + "7b1445755ee2527f0bf80ef9eeb59a5d2e6e3e1f", + me.WithClient(clientFn(expectedURL)), + me.WithDownloader(&mockDownloader{ + expected: expectedBlobContent, + }), + ) + fs, err = osfs.NewTempFileSystem() + Expect(err).To(Succeed()) + vfsattr.Set(ctx, fs) + tmpcache.Set(ctx, &tmpcache.Attribute{Path: "/tmp", Filesystem: fs}) + }) + + AfterEach(func() { + vfs.Cleanup(fs) + }) + + It("provides comsumer id", func() { + m, err := accessSpec.AccessMethod(&cpi.DummyComponentVersionAccess{Context: ctx}) + Expect(err).ToNot(HaveOccurred()) + Expect(credentials.GetProvidedConsumerId(m)).To(Equal(credentials.NewConsumerIdentity(identity.CONSUMER_TYPE, + identity.ID_HOSTNAME, "github.com", + identity.ID_PATHPREFIX, "test/test"))) + }) + + It("downloads artifacts", func() { + m, err := accessSpec.AccessMethod(&cpi.DummyComponentVersionAccess{Context: ctx}) + Expect(err).ToNot(HaveOccurred()) + content, err := m.Get() + Expect(err).ToNot(HaveOccurred()) + Expect(content).To(Equal(expectedBlobContent)) + }) + + When("the commit sha is of an invalid length", func() { + It("errors", func() { + accessSpec := me.New( + "hostname", + "", + "not-a-sha", + me.WithClient(clientFn(expectedURL)), + me.WithDownloader(&mockDownloader{ + expected: expectedBlobContent, + }), + ) + m, err := accessSpec.AccessMethod(&cpi.DummyComponentVersionAccess{Context: ctx}) + Expect(err).To(MatchError(ContainSubstring("commit is not a SHA"))) + if m != nil { + m.Close() + } + }) + }) + + When("the commit sha is of the right length but contains invalid characters", func() { + It("errors", func() { + accessSpec := me.New( + "hostname", + "1234", + "refs/heads/veryinteresting_branch_namess", + me.WithClient(clientFn(expectedURL)), + me.WithDownloader(&mockDownloader{ + expected: expectedBlobContent, + }), + ) + m, err := accessSpec.AccessMethod(&cpi.DummyComponentVersionAccess{Context: ctx}) + Expect(err).To(MatchError(ContainSubstring("commit contains invalid characters for a SHA"))) + if m != nil { + m.Close() + } + }) + }) + + When("credentials are provided", func() { + BeforeEach(func() { + clientFn = func(url string) *http.Client { + return NewTestClient(func(req *http.Request) *http.Response { + if v, ok := req.Header["Authorization"]; ok { + Expect(v).To(ContainElement("Bearer test")) + } else { + Fail("Authorization header not found in request") + } + if req.URL.String() != url { + Fail(fmt.Sprintf("failed to match url to expected url. want: %s; got: %s", expectedURL, req.URL.String())) + } + return &http.Response{ + StatusCode: http.StatusFound, + Status: http.StatusText(http.StatusFound), + // Must be set to non-nil value or it panics + Body: io.NopCloser(bytes.NewBufferString(`{}`)), + Header: http.Header{ + "Location": []string{defaultLink}, + }, + } + }) + } + accessSpec = me.New( + "https://github.com/test/test", + "", + "7b1445755ee2527f0bf80ef9eeb59a5d2e6e3e1f", + me.WithClient(clientFn(expectedURL)), + me.WithDownloader(&mockDownloader{ + expected: expectedBlobContent, + }), + ) + }) + It("can use those to access private repos", func() { + mcc := ocm.New(datacontext.MODE_INITIAL) + src := &mockCredSource{ + Context: mcc.CredentialsContext(), + cred: credentials.DirectCredentials{ + credentials.ATTR_TOKEN: "test", + }, + } + mcc.CredentialsContext().SetCredentialsForConsumer(credentials.NewConsumerIdentity(identity.CONSUMER_TYPE), src) + m, err := accessSpec.AccessMethod(&mockComponentVersionAccess{ + ocmContext: mcc, + }) + Expect(err).ToNot(HaveOccurred()) + _, err = m.Get() + Expect(err).ToNot(HaveOccurred()) + m.Close() + Expect(src.called).To(BeTrue()) + }) + }) + + When("GetCredentialsForConsumer returns an error", func() { + It("errors", func() { + mcc := ocm.New(datacontext.MODE_INITIAL) + src := &mockCredSource{ + Context: mcc.CredentialsContext(), + err: fmt.Errorf("danger will robinson"), + } + mcc.CredentialsContext().SetCredentialsForConsumer(credentials.NewConsumerIdentity(identity.CONSUMER_TYPE), src) + _, err := accessSpec.AccessMethod(&mockComponentVersionAccess{ + ocmContext: mcc, + }) + Expect(err).To(MatchError(ContainSubstring("danger will robinson"))) + Expect(src.called).To(BeTrue()) + }) + }) + + When("an enterprise repo URL is provided", func() { + It("uses that domain and includes api/v3 in the request URL", func() { + expectedURL = "https://github.tools.sap/api/v3/repos/test/test/tarball/25d9a3f0031c0b42e9ef7ab0117c35378040ef82" + spec := me.New("https://github.tools.sap/test/test", "", "25d9a3f0031c0b42e9ef7ab0117c35378040ef82", me.WithClient(clientFn(expectedURL))) + _, err := spec.AccessMethod(&cpi.DummyComponentVersionAccess{Context: ctx}) + Expect(err).ToNot(HaveOccurred()) + }) + }) + + When("hostname is different from github.com", func() { + It("will use an enterprise client", func() { + expectedURL = "https://custom/api/v3/repos/test/test/tarball/25d9a3f0031c0b42e9ef7ab0117c35378040ef82" + spec := me.New("https://github.tools.sap/test/test", "custom", "25d9a3f0031c0b42e9ef7ab0117c35378040ef82", me.WithClient(clientFn(expectedURL))) + _, err := spec.AccessMethod(&cpi.DummyComponentVersionAccess{Context: ctx}) + Expect(err).ToNot(HaveOccurred()) + }) + }) + + When("repoURL doesn't have an https prefix", func() { + It("will add one", func() { + expectedURL = "https://api.github.com/repos/test/test/tarball/25d9a3f0031c0b42e9ef7ab0117c35378040ef82" + spec := me.New("github.com/test/test", "", "25d9a3f0031c0b42e9ef7ab0117c35378040ef82", me.WithClient(clientFn(expectedURL))) + _, err := spec.AccessMethod(&cpi.DummyComponentVersionAccess{Context: ctx}) + Expect(err).ToNot(HaveOccurred()) + }) + }) +}) + +type mockComponentVersionAccess struct { + ocm.ComponentVersionAccess + ocmContext ocm.Context +} + +func (m *mockComponentVersionAccess) GetContext() ocm.Context { + return m.ocmContext +} + +type mockCredSource struct { + credentials.Context + cred credentials.Credentials + called bool + err error +} + +func (m *mockCredSource) Credentials(credentials.Context, ...credentials.CredentialsSource) (credentials.Credentials, error) { + m.called = true + return m.cred, m.err +} diff --git a/pkg/contexts/ocm/accessmethods/github/suite_test.go b/api/ocm/extensions/accessmethods/github/suite_test.go similarity index 100% rename from pkg/contexts/ocm/accessmethods/github/suite_test.go rename to api/ocm/extensions/accessmethods/github/suite_test.go diff --git a/pkg/contexts/ocm/accessmethods/github/testdata/repo.tar.gz b/api/ocm/extensions/accessmethods/github/testdata/repo.tar.gz similarity index 100% rename from pkg/contexts/ocm/accessmethods/github/testdata/repo.tar.gz rename to api/ocm/extensions/accessmethods/github/testdata/repo.tar.gz diff --git a/pkg/contexts/ocm/accessmethods/helm/README.md b/api/ocm/extensions/accessmethods/helm/README.md similarity index 100% rename from pkg/contexts/ocm/accessmethods/helm/README.md rename to api/ocm/extensions/accessmethods/helm/README.md diff --git a/api/ocm/extensions/accessmethods/helm/cli.go b/api/ocm/extensions/accessmethods/helm/cli.go new file mode 100644 index 000000000..42f65a478 --- /dev/null +++ b/api/ocm/extensions/accessmethods/helm/cli.go @@ -0,0 +1,53 @@ +package helm + +import ( + "ocm.software/ocm/api/credentials/builtin/helm/identity" + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + "ocm.software/ocm/api/utils/cobrautils/flagsets" +) + +func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { + return flagsets.NewConfigOptionTypeSetHandler( + Type, AddConfig, + options.RepositoryOption, + options.PackageOption, + options.VersionOption, + ) +} + +func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { + flagsets.AddFieldByOptionP(opts, options.RepositoryOption, config, "helmRepository") + flagsets.AddFieldByOptionP(opts, options.PackageOption, config, "helmChart") + flagsets.AddFieldByOptionP(opts, options.VersionOption, config, "version") + return nil +} + +var usage = ` +This method implements the access of a Helm chart stored in a Helm repository. +` + +var formatV1 = ` +The type specific specification fields are: + +- **helmRepository** *string* + + Helm repository URL. + +- **helmChart** *string* + + The name of the Helm chart and its version separated by a colon. + +- **version** *string* + + The version of the Helm chart if not specified as part of the chart name. + +- **caCert** *string* + + An optional TLS root certificate. + +- **keyring** *string* + + An optional keyring used to verify the chart. + +It uses the consumer identity type ` + identity.CONSUMER_TYPE + ` with the fields +for a hostpath identity matcher (see ocm get credentials).` diff --git a/api/ocm/extensions/accessmethods/helm/method.go b/api/ocm/extensions/accessmethods/helm/method.go new file mode 100644 index 000000000..c4e980713 --- /dev/null +++ b/api/ocm/extensions/accessmethods/helm/method.go @@ -0,0 +1,191 @@ +package helm + +import ( + "fmt" + "io" + "strings" + "sync" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/builtin/helm/identity" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/tech/helm" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" +) + +// Type is the access type for a blob in an OCI repository. +const ( + Type = "helm" + 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()))) +} + +// New creates a new Helm Chart accessor for helm repositories. +func New(chart string, repourl string) *AccessSpec { + return &AccessSpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(Type), + HelmChart: chart, + HelmRepository: repourl, + } +} + +// AccessSpec describes the access for a helm repository. +type AccessSpec struct { + runtime.ObjectVersionedType `json:",inline"` + + // HelmRepository is the URL og the helm repository to load the chart from. + HelmRepository string `json:"helmRepository"` + + // HelmChart if the name of the helm chart and its version separated by a colon. + HelmChart string `json:"helmChart"` + + // Version can either be specified as part of the chart name or separately. + Version string `json:"version,omitempty"` + + // CACert is an optional root TLS certificate + CACert string `json:"caCert,omitempty"` + + // Keyring is an optional keyring to verify the chart. + Keyring string `json:"keyring,omitempty"` +} + +var _ accspeccpi.AccessSpec = (*AccessSpec)(nil) + +func (a *AccessSpec) Describe(ctx accspeccpi.Context) string { + return fmt.Sprintf("Helm chart %s:%s in repository %s", a.GetChartName(), a.GetVersion(), a.HelmRepository) +} + +func (a *AccessSpec) IsLocal(ctx accspeccpi.Context) bool { + return false +} + +func (a *AccessSpec) GlobalAccessSpec(ctx accspeccpi.Context) accspeccpi.AccessSpec { + return a +} + +func (a *AccessSpec) GetMimeType() string { + return helm.ChartMediaType +} + +func (a *AccessSpec) AccessMethod(access accspeccpi.ComponentVersionAccess) (accspeccpi.AccessMethod, error) { + return accspeccpi.AccessMethodForImplementation(&accessMethod{comp: access, spec: a}, nil) +} + +/////////////////// + +func (a *AccessSpec) GetVersion() string { + parts := strings.Split(a.HelmChart, ":") + if len(parts) > 1 { + return parts[1] + } + return a.Version +} + +func (a *AccessSpec) GetChartName() string { + parts := strings.Split(a.HelmChart, ":") + return parts[0] +} + +//////////////////////////////////////////////////////////////////////////////// + +type accessMethod struct { + lock sync.Mutex + blob blobaccess.BlobAccess + comp accspeccpi.ComponentVersionAccess + spec *AccessSpec + + acc helm.ChartAccess +} + +var _ accspeccpi.AccessMethodImpl = (*accessMethod)(nil) + +func (_ *accessMethod) IsLocal() bool { + return false +} + +func (m *accessMethod) GetKind() string { + return Type +} + +func (m *accessMethod) AccessSpec() accspeccpi.AccessSpec { + return m.spec +} + +func (m *accessMethod) Close() error { + m.lock.Lock() + defer m.lock.Unlock() + if m.blob != nil { + m.blob.Close() + m.acc.Close() + m.blob = nil + } + return nil +} + +func (m *accessMethod) Get() ([]byte, error) { + return blobaccess.BlobData(m.getBlob()) +} + +func (m *accessMethod) Reader() (io.ReadCloser, error) { + return blobaccess.BlobReader(m.getBlob()) +} + +func (m *accessMethod) MimeType() string { + return helm.ChartMediaType +} + +func (m *accessMethod) getBlob() (blobaccess.BlobAccess, error) { + m.lock.Lock() + defer m.lock.Unlock() + + if m.blob != nil { + return m.blob, nil + } + + vers := m.spec.GetVersion() + name := m.spec.GetChartName() + + parts := strings.Split(m.spec.HelmChart, ":") + switch len(parts) { + case 1: + if vers == "" { + return nil, errors.ErrInvalid("helm chart", m.spec.HelmChart) + } + case 2: + if vers != parts[1] { + return nil, errors.ErrInvalid("helm chart", m.spec.HelmChart+"["+vers+"]") + } + default: + return nil, errors.ErrInvalid("helm chart", m.spec.HelmChart) + } + + acc, err := helm.DownloadChart(common.NonePrinter, m.comp.GetContext(), name, vers, m.spec.HelmRepository, + helm.WithCredentials(identity.GetCredentials(m.comp.GetContext(), m.spec.HelmRepository, m.spec.GetChartName())), + helm.WithKeyring([]byte(m.spec.Keyring)), + helm.WithRootCert([]byte(m.spec.CACert))) + if err != nil { + return nil, err + } + m.blob, err = acc.Chart() + if err != nil { + acc.Close() + } + m.acc = acc + return m.blob, nil +} + +func (m *accessMethod) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { + return identity.GetConsumerId(m.spec.HelmRepository, m.spec.GetChartName()) +} + +func (m *accessMethod) GetIdentityMatcher() string { + return identity.CONSUMER_TYPE +} diff --git a/api/ocm/extensions/accessmethods/helm/method_test.go b/api/ocm/extensions/accessmethods/helm/method_test.go new file mode 100644 index 000000000..6da6516b3 --- /dev/null +++ b/api/ocm/extensions/accessmethods/helm/method_test.go @@ -0,0 +1,50 @@ +package helm_test + +import ( + "fmt" + "net/http" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + + "helm.sh/helm/v3/pkg/chart/loader" + + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/helm" + helm2 "ocm.software/ocm/api/tech/helm" +) + +var _ = Describe("Method", func() { + var env *Builder + + BeforeEach(func() { + env = NewBuilder() + }) + + AfterEach(func() { + env.Cleanup() + }) + + It("accesses artifact", func() { + resp, err := http.Get("https://charts.helm.sh/stable") + if err == nil { // only if connected to internet + resp.Body.Close() + fmt.Fprintf(GinkgoWriter, "helm executed\n") + spec := helm.New("cockroachdb:3.0.8", "https://charts.helm.sh/stable") + + m := Must(spec.AccessMethod(&cpi.DummyComponentVersionAccess{env.OCMContext()})) + Expect(m.MimeType()).To(Equal(helm2.ChartMediaType)) + defer Close(m) + blob := Must(m.Reader()) + defer Close(blob) + + chart := Must(loader.LoadArchive(blob)) + Expect(chart.Name()).To(Equal("cockroachdb")) + Expect(chart.Metadata.Version).To(Equal("3.0.8")) + } else { + fmt.Fprintf(GinkgoWriter, "helm test skipped\n") + } + }) +}) diff --git a/pkg/contexts/ocm/accessmethods/helm/suite_test.go b/api/ocm/extensions/accessmethods/helm/suite_test.go similarity index 100% rename from pkg/contexts/ocm/accessmethods/helm/suite_test.go rename to api/ocm/extensions/accessmethods/helm/suite_test.go diff --git a/api/ocm/extensions/accessmethods/init.go b/api/ocm/extensions/accessmethods/init.go new file mode 100644 index 000000000..d7db2ddb7 --- /dev/null +++ b/api/ocm/extensions/accessmethods/init.go @@ -0,0 +1,17 @@ +package accessmethods + +import ( + _ "ocm.software/ocm/api/ocm/extensions/accessmethods/github" + _ "ocm.software/ocm/api/ocm/extensions/accessmethods/helm" + _ "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + _ "ocm.software/ocm/api/ocm/extensions/accessmethods/localfsblob" + _ "ocm.software/ocm/api/ocm/extensions/accessmethods/localociblob" + _ "ocm.software/ocm/api/ocm/extensions/accessmethods/maven" + _ "ocm.software/ocm/api/ocm/extensions/accessmethods/none" + _ "ocm.software/ocm/api/ocm/extensions/accessmethods/npm" + _ "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + _ "ocm.software/ocm/api/ocm/extensions/accessmethods/ociblob" + _ "ocm.software/ocm/api/ocm/extensions/accessmethods/relativeociref" + _ "ocm.software/ocm/api/ocm/extensions/accessmethods/s3" + _ "ocm.software/ocm/api/ocm/extensions/accessmethods/wget" +) diff --git a/pkg/contexts/ocm/accessmethods/localblob/README.md b/api/ocm/extensions/accessmethods/localblob/README.md similarity index 100% rename from pkg/contexts/ocm/accessmethods/localblob/README.md rename to api/ocm/extensions/accessmethods/localblob/README.md diff --git a/api/ocm/extensions/accessmethods/localblob/cli.go b/api/ocm/extensions/accessmethods/localblob/cli.go new file mode 100644 index 000000000..f85b79461 --- /dev/null +++ b/api/ocm/extensions/accessmethods/localblob/cli.go @@ -0,0 +1,76 @@ +package localblob + +import ( + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + "ocm.software/ocm/api/utils/cobrautils/flagsets" +) + +func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { + return flagsets.NewConfigOptionTypeSetHandler( + Type, AddConfig, + options.ReferenceOption, + options.MediatypeOption, + options.HintOption, + options.GlobalAccessOption, + ) +} + +func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { + flagsets.AddFieldByOptionP(opts, options.ReferenceOption, config, "localReference") + flagsets.AddFieldByOptionP(opts, options.HintOption, config, "referenceName") + flagsets.AddFieldByOptionP(opts, options.MediatypeOption, config, "mediaType") + flagsets.AddFieldByOptionP(opts, options.GlobalAccessOption, config, "globalAccess") + return nil +} + +var usage = ` +This method is used to store a resource blob along with the component descriptor +on behalf of the hosting OCM repository. + +Its implementation is specific to the implementation of OCM +repository used to read the component descriptor. Every repository +implementation may decide how and where local blobs are stored, +but it MUST provide an implementation for this method. + +Regardless of the chosen implementation the attribute specification is +defined globally the same. +` + +var formatV1 = ` +The type specific specification fields are: + +- **localReference** *string* + + Repository type specific location information as string. The value + may encode any deep structure, but typically just an access path is sufficient. + +- **mediaType** *string* + + The media type of the blob used to store the resource. It may add + format information like +tar or +gzip. + +- **referenceName** (optional) *string* + + This optional attribute may contain identity information used by + other repositories to restore some global access with an identity + related to the original source. + + For example, if an OCI artifact originally referenced using the + access method ociArtifact is stored during + some transport step as local artifact, the reference name can be set + to its original repository name. An import step into an OCI based OCM + repository may then decide to make this artifact available again as + regular OCI artifact. + +- **globalAccess** (optional) *access method specification* + + If a resource blob is stored locally, the repository implementation + may decide to provide an external access information (independent + of the OCM model). + + For example, an OCI artifact stored as local blob + can be additionally stored as regular OCI artifact in an OCI registry. + + This additional external access information can be added using + a second external access method specification. +` diff --git a/api/ocm/extensions/accessmethods/localblob/method.go b/api/ocm/extensions/accessmethods/localblob/method.go new file mode 100644 index 000000000..83fbcc4b0 --- /dev/null +++ b/api/ocm/extensions/accessmethods/localblob/method.go @@ -0,0 +1,188 @@ +package localblob + +import ( + "encoding/json" + "fmt" + + . "github.com/mandelsoft/goutils/exception" + + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/api/utils/runtime" +) + +// Type is the access type of a blob local to a component. +const ( + Type = "localBlob" + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +// this package shows how to implement access types with multiple serialization versions. +// So far, only one is implemented, but it shows how to add other ones. +// +// Specifications using multiple format versions allways provide a single common +// *internal* Go representation, intended to be used by library users. Only this +// internal version should be used outside this package. Additionally, there +// are Go types representing the various format versions, which will be used +// for the de-/serialization process (here AccessSpecV1). +// +// The supported versions are gathered in a dedicated scheme object (variable versions), +// which is then used to register all available versions at the default scheme (see +// init method). +// The *internal* specification Go type (here AccessSpec) must be based on +// runtime.InternalVersionedObjectType. +// It is initialized with the effective type/version name and the versions scheme +// and represents the Go representation used by API users, the format versions +// are never used outside this package. +// +// Additionally, this *internal* type must implement the MarshalJSON method, which +// can be implemented by delegating to the runtime.MarshalVersionedTypedObject +// method, which evaluated the versions scheme to finds the applicable conversion +// provided by the runtime.InternalVersionedObjectType. +// +// For every format version runtime.FormatVersion is required, which can be created +// with cpi.NewAccessSpecVersion, which takes the prototype and a converter, +// which converts between the internal go representation and the external formats, +// given by a dedicated go Type with serialization annotations. + +var versions = accspeccpi.NewAccessTypeVersionScheme(Type) + +func init() { + Must(versions.Register(accspeccpi.NewAccessSpecTypeByConverter[*AccessSpec, *AccessSpecV1](Type, &converterV1{}, accspeccpi.WithDescription(usage)))) + Must(versions.Register(accspeccpi.NewAccessSpecTypeByConverter[*AccessSpec, *AccessSpecV1](TypeV1, &converterV1{}, accspeccpi.WithFormatSpec(formatV1), accspeccpi.WithConfigHandler(ConfigHandler())))) + accspeccpi.RegisterAccessTypeVersions(versions) +} + +func Is(spec accspeccpi.AccessSpec) bool { + return spec != nil && spec.GetKind() == Type +} + +// New creates a new localFilesystemBlob accessor. +func New(local, hint string, mediaType string, global accspeccpi.AccessSpec) *AccessSpec { + return &AccessSpec{ + InternalVersionedTypedObject: runtime.NewInternalVersionedTypedObject[accspeccpi.AccessSpec](versions, Type), + LocalReference: local, + ReferenceName: hint, + MediaType: mediaType, + GlobalAccess: accspeccpi.NewAccessSpecRef(global), + } +} + +func Decode(data []byte) (*AccessSpec, error) { + spec, err := versions.Decode(data, runtime.DefaultYAMLEncoding) + if err != nil { + return nil, err + } + return spec.(*AccessSpec), nil +} + +// AccessSpec describes the access for a local blob. +type AccessSpec struct { + runtime.InternalVersionedTypedObject[accspeccpi.AccessSpec] + // LocalReference is the repository local identity of the blob. + // it is used by the repository implementation to get access + // to the blob and if therefore specific to a dedicated repository type. + LocalReference string `json:"localReference"` + // MediaType is the media type of the object represented by the blob + MediaType string `json:"mediaType"` + + // GlobalAccess is an optional field describing a possibility + // for a global access. If given, it MUST describe a global access method. + GlobalAccess *accspeccpi.AccessSpecRef `json:"globalAccess,omitempty"` + // ReferenceName is an optional static name the object should be + // use in a local repository context. It is use by a repository + // to optionally determine a globally referencable access according + // to the OCI distribution spec. The result will be stored + // by the repository in the field ImageReference. + // The value is typically an OCI repository name optionally + // followed by a colon ':' and a tag + ReferenceName string `json:"referenceName,omitempty"` +} + +var ( + _ json.Marshaler = (*AccessSpec)(nil) + _ accspeccpi.HintProvider = (*AccessSpec)(nil) + _ accspeccpi.GlobalAccessProvider = (*AccessSpec)(nil) + _ accspeccpi.AccessSpec = (*AccessSpec)(nil) +) + +func (a AccessSpec) MarshalJSON() ([]byte, error) { + return runtime.MarshalVersionedTypedObject(&a) + // return cpi.MarshalConvertedAccessSpec(cpi.DefaultContext(), &a) +} + +func (a *AccessSpec) Describe(ctx accspeccpi.Context) string { + return fmt.Sprintf("Local blob %s[%s]", a.LocalReference, a.ReferenceName) +} + +func (a *AccessSpec) IsLocal(accspeccpi.Context) bool { + return true +} + +func (a *AccessSpec) GlobalAccessSpec(ctx accspeccpi.Context) accspeccpi.AccessSpec { + if g, err := ctx.AccessSpecForSpec(a.GlobalAccess); err == nil { + return g + } + return a.GlobalAccess.Unwrap() +} + +func (a *AccessSpec) GetMimeType() string { + if a.MediaType == "" { + return mime.MIME_OCTET + } + return a.MediaType +} + +func (a *AccessSpec) GetReferenceHint(cv accspeccpi.ComponentVersionAccess) string { + return a.ReferenceName +} + +func (a *AccessSpec) AccessMethod(cv accspeccpi.ComponentVersionAccess) (accspeccpi.AccessMethod, error) { + return cv.AccessMethod(a) +} + +//////////////////////////////////////////////////////////////////////////////// + +type AccessSpecV1 struct { + runtime.ObjectVersionedType `json:",inline"` + // LocalReference is the repository local identity of the blob. + // it is used by the repository implementation to get access + // to the blob and if therefore specific to a dedicated repository type. + LocalReference string `json:"localReference"` + // MediaType is the media type of the object represented by the blob + MediaType string `json:"mediaType"` + + // GlobalAccess is an optional field describing a possibility + // for a global access. If given, it MUST describe a global access method. + GlobalAccess *accspeccpi.AccessSpecRef `json:"globalAccess,omitempty"` + // ReferenceName is an optional static name the object should be + // use in a local repository context. It is use by a repository + // to optionally determine a globally referencable access according + // to the OCI distribution spec. The result will be stored + // by the repository in the field ImageReference. + // The value is typically an OCI repository name optionally + // followed by a colon ':' and a tag + ReferenceName string `json:"referenceName,omitempty"` +} + +type converterV1 struct{} + +func (_ converterV1) ConvertFrom(in *AccessSpec) (*AccessSpecV1, error) { + return &AccessSpecV1{ + ObjectVersionedType: runtime.NewVersionedTypedObject(in.Type), + LocalReference: in.LocalReference, + ReferenceName: in.ReferenceName, + GlobalAccess: accspeccpi.NewAccessSpecRef(in.GlobalAccess), + MediaType: in.MediaType, + }, nil +} + +func (_ converterV1) ConvertTo(in *AccessSpecV1) (*AccessSpec, error) { + return &AccessSpec{ + InternalVersionedTypedObject: runtime.NewInternalVersionedTypedObject[accspeccpi.AccessSpec](versions, in.Type), + LocalReference: in.LocalReference, + ReferenceName: in.ReferenceName, + GlobalAccess: in.GlobalAccess, + MediaType: in.MediaType, + }, nil +} diff --git a/api/ocm/extensions/accessmethods/localblob/method_test.go b/api/ocm/extensions/accessmethods/localblob/method_test.go new file mode 100644 index 000000000..097a518af --- /dev/null +++ b/api/ocm/extensions/accessmethods/localblob/method_test.go @@ -0,0 +1,93 @@ +package localblob_test + +import ( + "encoding/json" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociblob" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + CTF = "ctf" + COMPONENT = "fabianburth.org/component" + VERSION = "v1.0" + ARTIFACT_NAME = "artifact" + ARTIFACT_VERSION = "v1.0" +) + +var _ = Describe("Method", func() { + data := `globalAccess: + digest: sha256:1bf729fa00e355199e711933ccfa27467ee3d2de1343aef2a7c1ecbdf885e63a + mediaType: application/tar+gzip + ref: ghcr.io/vasu1124/ocm/component-descriptors/github.com/vasu1124/introspect-delivery + size: 11287 + type: ociBlob +localReference: sha256:1bf729fa00e355199e711933ccfa27467ee3d2de1343aef2a7c1ecbdf885e63a +mediaType: application/tar+gzip +type: localBlob +` + _ = data + + It("marshal/unmarshal simple", func() { + spec := localblob.New("path", "hint", mime.MIME_TEXT, nil) + data := Must(json.Marshal(spec)) + Expect(string(data)).To(Equal("{\"type\":\"localBlob\",\"localReference\":\"path\",\"mediaType\":\"text/plain\",\"referenceName\":\"hint\"}")) + r := Must(localblob.Decode(data)) + Expect(r).To(Equal(spec)) + }) + + It("marshal/unmarshal with global", func() { + spec := localblob.New("", "", "", nil) + Expect(runtime.DefaultYAMLEncoding.Unmarshal([]byte(data), spec)).To(Succeed()) + + r := Must(runtime.DefaultYAMLEncoding.Marshal(spec)) + Expect(string(r)).To(Equal(data)) + + global := ociblob.New( + "ghcr.io/vasu1124/ocm/component-descriptors/github.com/vasu1124/introspect-delivery", + "sha256:1bf729fa00e355199e711933ccfa27467ee3d2de1343aef2a7c1ecbdf885e63a", + "application/tar+gzip", + 11287, + ) + Expect(spec.GlobalAccess.Evaluate(ocm.DefaultContext())).To(Equal(global)) + + r = Must(runtime.DefaultYAMLEncoding.Marshal(spec)) + Expect(string(r)).To(Equal(data)) + }) + + It("check get inexpensive content version identity method", func() { + var env *Builder + + env = NewBuilder() + defer env.Cleanup() + + env.OCMCommonTransport(CTF, accessio.FormatDirectory, func() { + env.ComponentVersion(COMPONENT, VERSION, func() { + env.Resource(ARTIFACT_NAME, ARTIFACT_VERSION, resourcetypes.BLOB, metav1.LocalRelation, func() { + env.BlobData(mime.MIME_TEXT, []byte("testdata")) + }) + }) + }) + + repo := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, CTF, 0, env)) + defer Close(repo) + cv := Must(repo.LookupComponentVersion(COMPONENT, VERSION)) + defer Close(cv) + access := cv.GetDescriptor().Resources[0].Access + spec := Must(env.OCMContext().AccessSpecForSpec(access)) + Expect(spec.GetVersion()).To(Equal("v1")) + }) +}) diff --git a/pkg/contexts/ocm/accessmethods/localblob/suite_test.go b/api/ocm/extensions/accessmethods/localblob/suite_test.go similarity index 100% rename from pkg/contexts/ocm/accessmethods/localblob/suite_test.go rename to api/ocm/extensions/accessmethods/localblob/suite_test.go diff --git a/api/ocm/extensions/accessmethods/localfsblob/method.go b/api/ocm/extensions/accessmethods/localfsblob/method.go new file mode 100644 index 000000000..50c9895d6 --- /dev/null +++ b/api/ocm/extensions/accessmethods/localfsblob/method.go @@ -0,0 +1,76 @@ +package localfsblob + +import ( + . "github.com/mandelsoft/goutils/exception" + + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/utils/runtime" +) + +// Type is the access type of a blob in a local filesystem. +const ( + Type = "localFilesystemBlob" + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +// Keep old access method and map generic one to this implementation for component archives + +// This method uses the localblob internal format and converts it to/from the +// appropriate serialization version. +// The attributes referenceName and globalAccess are NOT supported. + +var versions = accspeccpi.NewAccessTypeVersionScheme(Type) + +func init() { + Must(versions.Register(accspeccpi.NewAccessSpecTypeByConverter[*localblob.AccessSpec, *AccessSpec](Type, &converterV1{}))) + Must(versions.Register(accspeccpi.NewAccessSpecTypeByConverter[*localblob.AccessSpec, *AccessSpec](TypeV1, &converterV1{}))) + accspeccpi.RegisterAccessTypeVersions(versions) +} + +// New creates a new localFilesystemBlob accessor. +func New(path string, media string) *localblob.AccessSpec { + return &localblob.AccessSpec{ + InternalVersionedTypedObject: runtime.NewInternalVersionedTypedObject[accspeccpi.AccessSpec](versions, Type), + LocalReference: path, + MediaType: media, + } +} + +func Decode(data []byte) (*localblob.AccessSpec, error) { + spec, err := versions.Decode(data, runtime.DefaultYAMLEncoding) + if err != nil { + return nil, err + } + return spec.(*localblob.AccessSpec), nil +} + +// AccessSpec describes the access for a blob on the filesystem. +// Deprecated: use LocalBlob. +type AccessSpec struct { + runtime.ObjectVersionedType `json:",inline"` + // FileName is the + Filename string `json:"fileName"` + // MediaType is the media type of the object represented by the blob + MediaType string `json:"mediaType"` +} + +//////////////////////////////////////////////////////////////////////////////// + +type converterV1 struct{} + +func (_ converterV1) ConvertFrom(in *localblob.AccessSpec) (*AccessSpec, error) { + return &AccessSpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(in.Type), + Filename: in.LocalReference, + MediaType: in.MediaType, + }, nil +} + +func (_ converterV1) ConvertTo(in *AccessSpec) (*localblob.AccessSpec, error) { + return &localblob.AccessSpec{ + InternalVersionedTypedObject: runtime.NewInternalVersionedTypedObject[accspeccpi.AccessSpec](versions, in.Type), + LocalReference: in.Filename, + MediaType: in.MediaType, + }, nil +} diff --git a/api/ocm/extensions/accessmethods/localfsblob/method_test.go b/api/ocm/extensions/accessmethods/localfsblob/method_test.go new file mode 100644 index 000000000..d5bc28601 --- /dev/null +++ b/api/ocm/extensions/accessmethods/localfsblob/method_test.go @@ -0,0 +1,34 @@ +package localfsblob_test + +import ( + "encoding/json" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/ocm/extensions/accessmethods/localfsblob" + "ocm.software/ocm/api/utils/mime" +) + +var _ = Describe("Method", func() { + data := `globalAccess: + digest: sha256:1bf729fa00e355199e711933ccfa27467ee3d2de1343aef2a7c1ecbdf885e63a + mediaType: application/tar+gzip + ref: ghcr.io/vasu1124/ocm/component-descriptors/github.com/vasu1124/introspect-delivery + size: 11287 + type: ociBlob +localReference: sha256:1bf729fa00e355199e711933ccfa27467ee3d2de1343aef2a7c1ecbdf885e63a +mediaType: application/tar+gzip +type: localBlob +` + _ = data + + It("marshal/unmarshal simple", func() { + spec := localfsblob.New("path", mime.MIME_TEXT) + data := Must(json.Marshal(spec)) + Expect(string(data)).To(Equal("{\"type\":\"localFilesystemBlob\",\"fileName\":\"path\",\"mediaType\":\"text/plain\"}")) + r := Must(localfsblob.Decode(data)) + Expect(r).To(Equal(spec)) + }) +}) diff --git a/pkg/contexts/ocm/accessmethods/localfsblob/suite_test.go b/api/ocm/extensions/accessmethods/localfsblob/suite_test.go similarity index 100% rename from pkg/contexts/ocm/accessmethods/localfsblob/suite_test.go rename to api/ocm/extensions/accessmethods/localfsblob/suite_test.go diff --git a/api/ocm/extensions/accessmethods/localociblob/method.go b/api/ocm/extensions/accessmethods/localociblob/method.go new file mode 100644 index 000000000..034f34a70 --- /dev/null +++ b/api/ocm/extensions/accessmethods/localociblob/method.go @@ -0,0 +1,70 @@ +package localociblob + +import ( + . "github.com/mandelsoft/goutils/exception" + + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/utils/runtime" +) + +// Type is the access type for a component version local blob in an OCI repository. +const ( + Type = "localOciBlob" + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +var versions = accspeccpi.NewAccessTypeVersionScheme(Type) + +func init() { + Must(versions.Register(accspeccpi.NewAccessSpecTypeByConverter[*localblob.AccessSpec, *AccessSpec](Type, &converterV1{}))) + Must(versions.Register(accspeccpi.NewAccessSpecTypeByConverter[*localblob.AccessSpec, *AccessSpec](TypeV1, &converterV1{}))) + accspeccpi.RegisterAccessTypeVersions(versions) +} + +// New creates a new LocalOCIBlob accessor. +// Deprecated: Use LocalBlob. +func New(digest digest.Digest) *localblob.AccessSpec { + return &localblob.AccessSpec{ + InternalVersionedTypedObject: runtime.NewInternalVersionedTypedObject[accspeccpi.AccessSpec](versions, Type), + LocalReference: digest.String(), + } +} + +func Decode(data []byte) (*localblob.AccessSpec, error) { + spec, err := versions.Decode(data, runtime.DefaultYAMLEncoding) + if err != nil { + return nil, err + } + return spec.(*localblob.AccessSpec), nil +} + +// AccessSpec describes the access for a oci registry. +// Deprecated: Use LocalBlob. +type AccessSpec struct { + runtime.ObjectVersionedType `json:",inline"` + + // Digest is the digest of the targeted content. + Digest digest.Digest `json:"digest"` +} + +//////////////////////////////////////////////////////////////////////////////// + +type converterV1 struct{} + +func (_ converterV1) ConvertFrom(in *localblob.AccessSpec) (*AccessSpec, error) { + return &AccessSpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(in.Type), + Digest: digest.Digest(in.LocalReference), + }, nil +} + +func (_ converterV1) ConvertTo(in *AccessSpec) (*localblob.AccessSpec, error) { + return &localblob.AccessSpec{ + InternalVersionedTypedObject: runtime.NewInternalVersionedTypedObject[accspeccpi.AccessSpec](versions, in.Type), + LocalReference: in.Digest.String(), + MediaType: "", + }, nil +} diff --git a/api/ocm/extensions/accessmethods/localociblob/method_test.go b/api/ocm/extensions/accessmethods/localociblob/method_test.go new file mode 100644 index 000000000..e0d324930 --- /dev/null +++ b/api/ocm/extensions/accessmethods/localociblob/method_test.go @@ -0,0 +1,21 @@ +package localociblob_test + +import ( + "encoding/json" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/ocm/extensions/accessmethods/localociblob" +) + +var _ = Describe("Method", func() { + It("marshal/unmarshal simple", func() { + spec := localociblob.New("sha256:1bf729fa00e355199e711933ccfa27467ee3d2de1343aef2a7c1ecbdf885e63a") + data := Must(json.Marshal(spec)) + Expect(string(data)).To(Equal("{\"type\":\"localOciBlob\",\"digest\":\"sha256:1bf729fa00e355199e711933ccfa27467ee3d2de1343aef2a7c1ecbdf885e63a\"}")) + r := Must(localociblob.Decode(data)) + Expect(r).To(Equal(spec)) + }) +}) diff --git a/pkg/contexts/ocm/accessmethods/localociblob/suite_test.go b/api/ocm/extensions/accessmethods/localociblob/suite_test.go similarity index 100% rename from pkg/contexts/ocm/accessmethods/localociblob/suite_test.go rename to api/ocm/extensions/accessmethods/localociblob/suite_test.go diff --git a/pkg/contexts/ocm/accessmethods/maven/README.md b/api/ocm/extensions/accessmethods/maven/README.md similarity index 100% rename from pkg/contexts/ocm/accessmethods/maven/README.md rename to api/ocm/extensions/accessmethods/maven/README.md diff --git a/api/ocm/extensions/accessmethods/maven/cli.go b/api/ocm/extensions/accessmethods/maven/cli.go new file mode 100644 index 000000000..4af69b8ef --- /dev/null +++ b/api/ocm/extensions/accessmethods/maven/cli.go @@ -0,0 +1,62 @@ +package maven + +import ( + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + "ocm.software/ocm/api/utils/cobrautils/flagsets" +) + +func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { + return flagsets.NewConfigOptionTypeSetHandler( + Type, AddConfig, + options.RepositoryOption, + options.GroupOption, + options.ArtifactOption, + options.VersionOption, + // optional + options.ClassifierOption, + options.ExtensionOption, + ) +} + +func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { + 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") + // optional + flagsets.AddFieldByOptionP(opts, options.ClassifierOption, config, "classifier") + flagsets.AddFieldByOptionP(opts, options.ExtensionOption, config, "extension") + return nil +} + +var usage = ` +This method implements the access of a Maven artifact in a Maven repository. +` + +var formatV1 = ` +The type specific specification fields are: + +- **repoUrl** *string* + + URL of the Maven repository + +- **groupId** *string* + + The groupId of the Maven artifact + +- **artifactId** *string* + + The artifactId of the Maven artifact + +- **version** *string* + + The version name of the Maven artifact + +- **classifier** *string* + + The optional classifier of the Maven artifact + +- **extension** *string* + + The optional extension of the Maven artifact +` diff --git a/api/ocm/extensions/accessmethods/maven/method.go b/api/ocm/extensions/accessmethods/maven/method.go new file mode 100644 index 000000000..e37cad5bc --- /dev/null +++ b/api/ocm/extensions/accessmethods/maven/method.go @@ -0,0 +1,132 @@ +package maven + +import ( + "fmt" + + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/tech/maven" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + mavenblob "ocm.software/ocm/api/utils/blobaccess/maven" + "ocm.software/ocm/api/utils/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 { + if a.IsPackage() { + return a.GAV() + } + return "" +} + +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.BlobAccessForCoords(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) 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/api/ocm/extensions/accessmethods/maven/method_test.go b/api/ocm/extensions/accessmethods/maven/method_test.go new file mode 100644 index 000000000..5874b5548 --- /dev/null +++ b/api/ocm/extensions/accessmethods/maven/method_test.go @@ -0,0 +1,137 @@ +package maven_test + +import ( + "crypto" + "time" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/cpi" + me "ocm.software/ocm/api/ocm/extensions/accessmethods/maven" + "ocm.software/ocm/api/tech/maven/maventest" + "ocm.software/ocm/api/utils/iotools" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/api/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.SHA256, r) + li := Must(tarutils.ListArchiveContentFromReader(dr)) + 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(dr.Size()).To(Equal(int64(maventest.ARTIFACT_SIZE))) + Expect(dr.Digest().String()).To(Equal("SHA-256:" + maventest.ARTIFACT_DIGEST)) + }) + It("test empty repoUrl", func() { + acc := me.New("", "com.sap.cloud.sdk", "sdk-modules-bom", "5.7.0") + ExpectError(acc.AccessMethod(cv)).ToNot(BeNil()) + }) + + 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(maventest.POM_SHA1)) + }) + + 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.ArtifactId).To(Equal("maven")) + }) + } + }) +}) diff --git a/pkg/contexts/ocm/accessmethods/maven/suite_test.go b/api/ocm/extensions/accessmethods/maven/suite_test.go similarity index 100% rename from pkg/contexts/ocm/accessmethods/maven/suite_test.go rename to api/ocm/extensions/accessmethods/maven/suite_test.go diff --git a/api/ocm/extensions/accessmethods/none/method.go b/api/ocm/extensions/accessmethods/none/method.go new file mode 100644 index 000000000..7804cd878 --- /dev/null +++ b/api/ocm/extensions/accessmethods/none/method.go @@ -0,0 +1,96 @@ +package none + +import ( + "io" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/utils/runtime" +) + +// Type is the access type for no blob. +const ( + Type = compdesc.NoneType + TypeV1 = Type + runtime.VersionSeparator + "v1" + LegacyType = compdesc.NoneLegacyType +) + +func init() { + accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](Type, accspeccpi.WithDescription("dummy resource with no access"))) + accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](TypeV1)) + accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](LegacyType)) +} + +// New creates a new OCIBlob accessor. +func New() *AccessSpec { + return &AccessSpec{ObjectVersionedType: runtime.NewVersionedTypedObject(Type)} +} + +func IsNone(kind string) bool { + return compdesc.IsNoneAccessKind(kind) +} + +// AccessSpec describes the access for a oci registry. +type AccessSpec struct { + runtime.ObjectVersionedType `json:",inline"` +} + +var _ accspeccpi.AccessSpec = (*AccessSpec)(nil) + +func (a *AccessSpec) Describe(ctx accspeccpi.Context) string { + return "none" +} + +func (s *AccessSpec) IsLocal(context accspeccpi.Context) bool { + return false +} + +func (s *AccessSpec) GlobalAccessSpec(ctx accspeccpi.Context) accspeccpi.AccessSpec { + return nil +} + +func (s *AccessSpec) GetMimeType() string { + return "" +} + +func (s *AccessSpec) AccessMethod(access accspeccpi.ComponentVersionAccess) (accspeccpi.AccessMethod, error) { + return accspeccpi.AccessMethodForImplementation(&accessMethod{spec: s}, nil) +} + +//////////////////////////////////////////////////////////////////////////////// + +type accessMethod struct { + spec *AccessSpec +} + +var _ accspeccpi.AccessMethodImpl = (*accessMethod)(nil) + +func (_ *accessMethod) IsLocal() bool { + return false +} + +func (m *accessMethod) GetKind() string { + return Type +} + +func (m *accessMethod) AccessSpec() accspeccpi.AccessSpec { + return m.spec +} + +func (m *accessMethod) Close() error { + return nil +} + +func (m *accessMethod) Get() ([]byte, error) { + return nil, errors.ErrNotSupported("access") +} + +func (m *accessMethod) Reader() (io.ReadCloser, error) { + return nil, errors.ErrNotSupported("access") +} + +func (m *accessMethod) MimeType() string { + return "" +} diff --git a/pkg/contexts/ocm/accessmethods/npm/README.md b/api/ocm/extensions/accessmethods/npm/README.md similarity index 100% rename from pkg/contexts/ocm/accessmethods/npm/README.md rename to api/ocm/extensions/accessmethods/npm/README.md diff --git a/api/ocm/extensions/accessmethods/npm/cli.go b/api/ocm/extensions/accessmethods/npm/cli.go new file mode 100644 index 000000000..d2299149c --- /dev/null +++ b/api/ocm/extensions/accessmethods/npm/cli.go @@ -0,0 +1,42 @@ +package npm + +import ( + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + "ocm.software/ocm/api/utils/cobrautils/flagsets" +) + +func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { + return flagsets.NewConfigOptionTypeSetHandler( + Type, AddConfig, + options.RegistryOption, + options.PackageOption, + options.VersionOption, + ) +} + +func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { + flagsets.AddFieldByOptionP(opts, options.RegistryOption, config, "registry") + flagsets.AddFieldByOptionP(opts, options.PackageOption, config, "package") + flagsets.AddFieldByOptionP(opts, options.VersionOption, config, "version") + return nil +} + +var usage = ` +This method implements the access of an NPM package in an NPM registry. +` + +var formatV1 = ` +The type specific specification fields are: + +- **registry** *string* + + Base URL of the NPM registry. + +- **package** *string* + + The name of the NPM package + +- **version** *string* + + The version name of the NPM package +` diff --git a/api/ocm/extensions/accessmethods/npm/method.go b/api/ocm/extensions/accessmethods/npm/method.go new file mode 100644 index 000000000..8131fb851 --- /dev/null +++ b/api/ocm/extensions/accessmethods/npm/method.go @@ -0,0 +1,228 @@ +package npm + +import ( + "bytes" + "context" + "crypto" + "encoding/json" + "fmt" + "io" + "net/http" + "path" + "strings" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/tech/npm" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/iotools" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/api/utils/runtime" +) + +// Type is the access type of NPM registry. +const ( + Type = "npm" + 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 NPM registry. +type AccessSpec struct { + runtime.ObjectVersionedType `json:",inline"` + + // Registry is the base URL of the NPM registry + Registry string `json:"registry"` + // Package is the name of NPM package + Package string `json:"package"` + // Version of the NPM package. + Version string `json:"version"` +} + +var _ accspeccpi.AccessSpec = (*AccessSpec)(nil) + +// New creates a new NPM registry access spec version v1. +func New(registry, pkg, version string) *AccessSpec { + return &AccessSpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(Type), + Registry: registry, + Package: pkg, + Version: version, + } +} + +func (a *AccessSpec) Describe(_ accspeccpi.Context) string { + return fmt.Sprintf("NPM package %s:%s in registry %s", a.Package, a.Version, a.Registry) +} + +func (_ *AccessSpec) IsLocal(accspeccpi.Context) bool { + return false +} + +func (a *AccessSpec) GlobalAccessSpec(_ accspeccpi.Context) accspeccpi.AccessSpec { + return a +} + +func (a *AccessSpec) GetReferenceHint(_ accspeccpi.ComponentVersionAccess) string { + return a.Package + ":" + a.Version +} + +func (_ *AccessSpec) GetType() string { + return Type +} + +func (a *AccessSpec) AccessMethod(c accspeccpi.ComponentVersionAccess) (accspeccpi.AccessMethod, error) { + return accspeccpi.AccessMethodForImplementation(newMethod(c, a)) +} + +// PackageUrl returns the URL of the NPM package (Registry/Package). +func (a *AccessSpec) PackageUrl() string { + return strings.TrimSuffix(a.Registry, "/") + path.Join("/", a.Package) +} + +// PackageVersionUrl returns the URL of the NPM package-version (Registry/Package/Version). +func (a *AccessSpec) PackageVersionUrl() string { + return strings.TrimSuffix(a.Registry, "/") + path.Join("/", a.Package, a.Version) +} + +func (a *AccessSpec) GetPackageVersion(ctx accspeccpi.Context) (*npm.Version, error) { + r, err := reader(a, vfsattr.Get(ctx), ctx) + if err != nil { + return nil, err + } + defer r.Close() + buf, err := io.ReadAll(r) + if err != nil { + return nil, errors.Wrapf(err, "cannot get version metadata for %s", a.PackageVersionUrl()) + } + var version npm.Version + err = json.Unmarshal(buf, &version) + if err != nil || version.Dist.Tarball == "" { + // ugly fallback as workaround for https://github.com/sonatype/nexus-public/issues/224 + var project npm.Project + err = json.Unmarshal(buf, &project) // parse the complete project + if err != nil { + return nil, errors.Wrapf(err, "cannot unmarshal version metadata for %s", a.PackageVersionUrl()) + } + v, ok := project.Version[a.Version] // and pick only the specified version + if !ok { + return nil, errors.Newf("version '%s' doesn't exist", a.Version) + } + version = v + } + return &version, nil +} + +//////////////////////////////////////////////////////////////////////////////// + +func newMethod(c accspeccpi.ComponentVersionAccess, a *AccessSpec) (accspeccpi.AccessMethodImpl, error) { + factory := func() (blobaccess.BlobAccess, error) { + meta, err := a.GetPackageVersion(c.GetContext()) + if err != nil { + return nil, err + } + + f := func() (io.ReadCloser, error) { + return reader(a, vfsattr.Get(c.GetContext()), c.GetContext(), meta.Dist.Tarball) + } + if meta.Dist.Integrity != "" { + tf := f + f = func() (io.ReadCloser, error) { + r, err := tf() + if err != nil { + return nil, err + } + digest, err := iotools.DecodeBase64ToHex(meta.Dist.Integrity) + if err != nil { + return nil, err + } + return iotools.VerifyingReaderWithHash(r, crypto.SHA512, digest), nil + } + } + if meta.Dist.Shasum != "" { + tf := f + f = func() (io.ReadCloser, error) { + r, err := tf() + if err != nil { + return nil, err + } + return iotools.VerifyingReaderWithHash(r, crypto.SHA1, meta.Dist.Shasum), nil + } + } + acc := blobaccess.DataAccessForReaderFunction(f, meta.Dist.Tarball) + return accessobj.CachedBlobAccessForWriter(c.GetContext(), mime.MIME_TGZ, accessio.NewDataAccessWriter(acc)), nil + } + return accspeccpi.NewDefaultMethodImpl(c, a, "", mime.MIME_TGZ, factory), nil +} + +func reader(a *AccessSpec, fs vfs.FileSystem, ctx cpi.ContextProvider, tar ...string) (io.ReadCloser, error) { + url := a.PackageVersionUrl() + if len(tar) > 0 { + url = tar[0] + } + 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 = npm.BasicAuth(req, ctx, a.Registry, a.Package) + if err != nil { + return nil, err + } + c := &http.Client{} + resp, err := c.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode == http.StatusNotFound { + // maybe it's stupid Nexus - https://github.com/sonatype/nexus-public/issues/224? + url = a.PackageUrl() + req, err = http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil) + if err != nil { + return nil, err + } + err = npm.BasicAuth(req, ctx, a.Registry, a.Package) + if err != nil { + return nil, err + } + + // close body before overwriting to close any pending connections + resp.Body.Close() + resp, err = c.Do(req) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + } + + if resp.StatusCode != http.StatusOK { + defer resp.Body.Close() + buf := &bytes.Buffer{} + _, err = io.Copy(buf, io.LimitReader(resp.Body, 2000)) + if err != nil { + return nil, errors.Newf("version meta data request %s provides %s", url, resp.Status) + } + return nil, errors.Newf("version meta data request %s provides %s: %s", url, resp.Status, buf.String()) + } + content, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + return io.NopCloser(bytes.NewBuffer(content)), nil +} diff --git a/api/ocm/extensions/accessmethods/npm/method_test.go b/api/ocm/extensions/accessmethods/npm/method_test.go new file mode 100644 index 000000000..5bd3484f8 --- /dev/null +++ b/api/ocm/extensions/accessmethods/npm/method_test.go @@ -0,0 +1,83 @@ +package npm_test + +import ( + "crypto" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + . "ocm.software/ocm/api/helper/env" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/npm" + "ocm.software/ocm/api/utils/iotools" + "ocm.software/ocm/api/utils/mime" +) + +const ( + NPMPATH = "/testdata/registry" + FAILPATH = "/testdata/failregistry" +) + +var _ = Describe("Method", func() { + var env *Builder + var cv ocm.ComponentVersionAccess + + BeforeEach(func() { + env = NewBuilder(TestData()) + cv = &cpi.DummyComponentVersionAccess{env.OCMContext()} + }) + + AfterEach(func() { + env.Cleanup() + }) + + It("accesses artifact", func() { + acc := npm.New("file://"+NPMPATH, "yargs", "17.7.1") + // acc := npm.New("https://registry.npmjs.org", "yargs", "17.7.1") + + 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(65690))) + Expect(dr.Digest().String()).To(Equal("SHA-1:34a77645201d1a8fc5213ace787c220eabbd0967")) + }) + + It("detects digests mismatch", func() { + acc := npm.New("file://"+FAILPATH, "yargs", "17.7.1") + + m := Must(acc.AccessMethod(cv)) + defer m.Close() + _, err := m.Reader() + Expect(err).To(MatchError(ContainSubstring("SHA-1 digest mismatch: expected 44a77645201d1a8fc5213ace787c220eabbd0967, found 34a77645201d1a8fc5213ace787c220eabbd0967"))) + }) + + It("PackageUrl()", func() { + packageUrl := "https://registry.npmjs.org/yargs" + acc := npm.New("https://registry.npmjs.org", "yargs", "17.7.1") + Expect(acc.PackageUrl()).To(Equal(packageUrl)) + acc = npm.New("https://registry.npmjs.org/", "yargs", "17.7.1") + Expect(acc.PackageUrl()).To(Equal(packageUrl)) + }) + + It("PackageVersionUrl()", func() { + packageVersionUrl := "https://registry.npmjs.org/yargs/17.7.1" + acc := npm.New("https://registry.npmjs.org", "yargs", "17.7.1") + Expect(acc.PackageVersionUrl()).To(Equal(packageVersionUrl)) + acc = npm.New("https://registry.npmjs.org/", "yargs", "17.7.1") + Expect(acc.PackageVersionUrl()).To(Equal(packageVersionUrl)) + }) +}) diff --git a/pkg/contexts/ocm/accessmethods/npm/suite_test.go b/api/ocm/extensions/accessmethods/npm/suite_test.go similarity index 100% rename from pkg/contexts/ocm/accessmethods/npm/suite_test.go rename to api/ocm/extensions/accessmethods/npm/suite_test.go diff --git a/pkg/contexts/ocm/accessmethods/npm/testdata/content/yargs/yargs-17.7.1.tgz b/api/ocm/extensions/accessmethods/npm/testdata/content/yargs/yargs-17.7.1.tgz similarity index 100% rename from pkg/contexts/ocm/accessmethods/npm/testdata/content/yargs/yargs-17.7.1.tgz rename to api/ocm/extensions/accessmethods/npm/testdata/content/yargs/yargs-17.7.1.tgz diff --git a/pkg/contexts/ocm/accessmethods/npm/testdata/failregistry/yargs/17.7.1 b/api/ocm/extensions/accessmethods/npm/testdata/failregistry/yargs/17.7.1 similarity index 100% rename from pkg/contexts/ocm/accessmethods/npm/testdata/failregistry/yargs/17.7.1 rename to api/ocm/extensions/accessmethods/npm/testdata/failregistry/yargs/17.7.1 diff --git a/pkg/contexts/ocm/accessmethods/npm/testdata/registry/yargs/17.7.1 b/api/ocm/extensions/accessmethods/npm/testdata/registry/yargs/17.7.1 similarity index 100% rename from pkg/contexts/ocm/accessmethods/npm/testdata/registry/yargs/17.7.1 rename to api/ocm/extensions/accessmethods/npm/testdata/registry/yargs/17.7.1 diff --git a/pkg/contexts/ocm/accessmethods/ociartifact/README.md b/api/ocm/extensions/accessmethods/ociartifact/README.md similarity index 100% rename from pkg/contexts/ocm/accessmethods/ociartifact/README.md rename to api/ocm/extensions/accessmethods/ociartifact/README.md diff --git a/api/ocm/extensions/accessmethods/ociartifact/cli.go b/api/ocm/extensions/accessmethods/ociartifact/cli.go new file mode 100644 index 000000000..bad0ab8d1 --- /dev/null +++ b/api/ocm/extensions/accessmethods/ociartifact/cli.go @@ -0,0 +1,32 @@ +package ociartifact + +import ( + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + "ocm.software/ocm/api/utils/cobrautils/flagsets" +) + +func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { + return flagsets.NewConfigOptionTypeSetHandler( + Type, AddConfig, + options.ReferenceOption, + ) +} + +func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { + flagsets.AddFieldByOptionP(opts, options.ReferenceOption, config, "imageReference") + return nil +} + +var usage = ` +This method implements the access of an OCI artifact stored in an OCI registry. +` + +var formatV1 = ` +The type specific specification fields are: + +- **imageReference** *string* + + OCI image/artifact reference following the possible docker schemes: + - <repo>/<artifact>:<digest>@<tag> + - [<port>]/<repo path>/<artifact>:<version>@<tag> +` diff --git a/api/ocm/extensions/accessmethods/ociartifact/logging.go b/api/ocm/extensions/accessmethods/ociartifact/logging.go new file mode 100644 index 000000000..8f2e5b19e --- /dev/null +++ b/api/ocm/extensions/accessmethods/ociartifact/logging.go @@ -0,0 +1,30 @@ +package ociartifact + +import ( + "github.com/mandelsoft/logging" + + "ocm.software/ocm/api/ocm/cpi" + ocmlog "ocm.software/ocm/api/utils/logging" +) + +var REALM = ocmlog.DefineSubRealm("access method ociArtifact", "accessmethod/ociartifact") + +type ContextProvider interface { + GetContext() cpi.Context +} + +func Logger(c ContextProvider, keyValuePairs ...interface{}) logging.Logger { + return c.GetContext().Logger(REALM).WithValues(keyValuePairs...) +} + +type localContextProvider struct { + cpi.ContextProvider +} + +func (l *localContextProvider) GetContext() cpi.Context { + return l.OCMContext() +} + +func WrapContextProvider(ctx cpi.ContextProvider) ContextProvider { + return &localContextProvider{ctx} +} diff --git a/api/ocm/extensions/accessmethods/ociartifact/method.go b/api/ocm/extensions/accessmethods/ociartifact/method.go new file mode 100644 index 000000000..2e6e616e5 --- /dev/null +++ b/api/ocm/extensions/accessmethods/ociartifact/method.go @@ -0,0 +1,362 @@ +package ociartifact + +import ( + "fmt" + "io" + "strings" + "sync" + + . "github.com/mandelsoft/goutils/finalizer" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/general" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/credentials" + ociidentity "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/oci/extensions/repositories/ocireg" + "ocm.software/ocm/api/oci/grammar" + ocmcpi "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/logging" + "ocm.software/ocm/api/utils/runtime" +) + +// Type is the access type of a oci registry. +const ( + Type = "ociArtifact" + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +const ( + LegacyType = "ociRegistry" + LegacyTypeV1 = LegacyType + 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()))) + + accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](LegacyType)) + accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](LegacyTypeV1)) +} + +func Is(spec accspeccpi.AccessSpec) bool { + return spec != nil && (spec.GetKind() == Type || spec.GetKind() == LegacyType) +} + +// AccessSpec describes the access for a oci registry. +type AccessSpec struct { + runtime.ObjectVersionedType `json:",inline"` + + // ImageReference is the actual reference to the oci image repository and tag. + ImageReference string `json:"imageReference"` +} + +var ( + _ accspeccpi.AccessSpec = (*AccessSpec)(nil) + _ accspeccpi.HintProvider = (*AccessSpec)(nil) + _ blobaccess.DigestSource = (*AccessSpec)(nil) +) + +// New creates a new oci registry access spec version v1. +func New(ref string) *AccessSpec { + return &AccessSpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(Type), + ImageReference: ref, + } +} + +func (a *AccessSpec) Describe(ctx accspeccpi.Context) string { + return fmt.Sprintf("OCI artifact %s", a.ImageReference) +} + +func (_ *AccessSpec) IsLocal(accspeccpi.Context) bool { + return false +} + +func (a *AccessSpec) Digest() digest.Digest { + ref, err := oci.ParseRef(a.ImageReference) + if err != nil || ref.Digest == nil { + return "" + } + return *ref.Digest +} + +func (a *AccessSpec) GlobalAccessSpec(ctx accspeccpi.Context) accspeccpi.AccessSpec { + return a +} + +func (a *AccessSpec) GetReferenceHint(cv accspeccpi.ComponentVersionAccess) string { + ref, err := oci.ParseRef(a.ImageReference) + if err != nil { + return "" + } + hint := ref.Repository + r := cv.Repository() + if r != nil { + prefix := ocmcpi.RepositoryPrefix(cv.Repository().GetSpecification()) + if strings.HasPrefix(hint, prefix+grammar.RepositorySeparator) { + // try to keep hint identical, even across intermediate + // artifact globalizations + hint = hint[len(prefix)+1:] + } + } + if ref.Tag != nil { + hint += grammar.TagSeparator + *ref.Tag + } + return hint +} + +func (a *AccessSpec) GetOCIReference(cv accspeccpi.ComponentVersionAccess) (string, error) { + return a.ImageReference, nil +} + +func (_ *AccessSpec) GetType() string { + return Type +} + +func (a *AccessSpec) AccessMethod(c accspeccpi.ComponentVersionAccess) (accspeccpi.AccessMethod, error) { + return NewMethod(c.GetContext(), a, a.ImageReference) +} + +//////////////////////////////////////////////////////////////////////////////// + +type AccessMethodImpl = *accessMethod + +type accessMethod struct { + lock sync.Mutex + ctx accspeccpi.Context + spec accspeccpi.AccessSpec + reference string + + finalizer Finalizer + err error + + id credentials.ConsumerIdentity + ref *oci.RefSpec + mime string + digest digest.Digest + art oci.ArtifactAccess + + repo oci.Repository + blob artifactset.ArtifactBlob +} + +var ( + _ accspeccpi.AccessMethodImpl = (*accessMethod)(nil) + _ blobaccess.DigestSource = (*accessMethod)(nil) + _ accspeccpi.DigestSource = (*accessMethod)(nil) + _ credentials.ConsumerIdentityProvider = (*accessMethod)(nil) +) + +func NewMethod(ctx accspeccpi.ContextProvider, a accspeccpi.AccessSpec, ref string, repo ...oci.Repository) (accspeccpi.AccessMethod, error) { + m := &accessMethod{ + spec: a, + reference: ref, + ctx: ctx.OCMContext(), + } + return accspeccpi.AccessMethodForImplementation(m, m.eval(general.Optional(repo...))) +} + +func (_ *accessMethod) IsLocal() bool { + return false +} + +func (m *accessMethod) GetOCIReference(cv accspeccpi.ComponentVersionAccess) (string, error) { + return m.reference, nil +} + +func (m *accessMethod) GetKind() string { + return m.spec.GetKind() +} + +func (m *accessMethod) AccessSpec() accspeccpi.AccessSpec { + return m.spec +} + +func (m *accessMethod) Cache() { + m.lock.Lock() + ref := m.ref + m.lock.Unlock() + if ref == nil { + return + } + logger := Logger(WrapContextProvider(m.ctx)) + logger.Info("cache artifact blob", "ref", m.reference) + + _, m.err = m.getBlob() + + m.finalizer.Finalize() +} + +func (m *accessMethod) Close() error { + m.lock.Lock() + defer m.lock.Unlock() + + list := errors.ErrorList{} + + if m.blob != nil { + list.Add(m.blob.Close()) + } + m.blob = nil + m.art = nil + m.ref = nil + list.Add(m.finalizer.Finalize()) + return list.Result() +} + +func (m *accessMethod) eval(relto oci.Repository) error { + var ( + err error + ref oci.RefSpec + ) + + if relto == nil { + ref, err = oci.ParseRef(m.reference) + if err != nil { + return err + } + ocictx := m.ctx.OCIContext() + spec := ocictx.GetAlias(ref.Host) + if spec == nil { + spec = ocireg.NewRepositorySpec(ref.Host) + } + repo, err := ocictx.RepositoryForSpec(spec) + if err != nil { + return err + } + m.finalizer.Close(repo, "repository for accessing %s", m.reference) + m.repo = repo + } else { + repo, err := relto.Dup() + if err != nil { + return err + } + m.finalizer.Close(repo) + art, err := oci.ParseArt(m.reference) + if err != nil { + return err + } + ref = oci.RefSpec{ + UniformRepositorySpec: *repo.GetSpecification().UniformRepositorySpec(), + ArtSpec: art, + } + m.repo = repo + } + + m.ref = &ref + m.id = credentials.GetProvidedConsumerId(m.repo, credentials.StringUsageContext(ref.Repository)) + return nil +} + +func (m *accessMethod) GetArtifact() (oci.ArtifactAccess, *oci.RefSpec, error) { + m.lock.Lock() + defer m.lock.Unlock() + + err := m.getArtifact() + if err != nil { + return nil, nil, m.err + } + art := m.art + if art != nil { + art, err = art.Dup() + if err != nil { + return nil, nil, err + } + } + return art, m.ref, err +} + +func (m *accessMethod) getArtifact() error { + if m.art == nil && m.err == nil && m.ref != nil { + art, err := m.repo.LookupArtifact(m.ref.Repository, m.ref.Version()) + m.finalizer.Close(art, "artifact for accessing %s", m.reference) + m.art, m.err = art, err + if art != nil { + m.mime = artdesc.ToContentMediaType(m.art.GetDescriptor().MimeType()) + artifactset.SynthesizedBlobFormat + m.digest = art.Digest() + } + } + return m.err +} + +func (m *accessMethod) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { + m.lock.Lock() + defer m.lock.Unlock() + + return m.id +} + +func (m *accessMethod) GetIdentityMatcher() string { + return ociidentity.CONSUMER_TYPE +} + +func (m *accessMethod) Digest() digest.Digest { + d, _ := m.GetDigest() + return d +} + +func (m *accessMethod) GetDigest() (digest.Digest, error) { + m.lock.Lock() + defer m.lock.Unlock() + + err := m.getArtifact() + return m.digest, err +} + +func (m *accessMethod) Get() ([]byte, error) { + blob, err := m.getBlob() + if err != nil { + return nil, err + } + return blob.Get() +} + +func (m *accessMethod) Reader() (io.ReadCloser, error) { + b, err := m.getBlob() + if err != nil { + return nil, err + } + r, err := b.Reader() + if err != nil { + return nil, err + } + return r, nil +} + +func (m *accessMethod) MimeType() string { + if m.mime == "" { + m.lock.Lock() + defer m.lock.Unlock() + m.getArtifact() + } + return m.mime +} + +func (m *accessMethod) getBlob() (artifactset.ArtifactBlob, error) { + m.lock.Lock() + defer m.lock.Unlock() + + if m.blob != nil || m.err != nil { + return m.blob, m.err + } + + err := m.getArtifact() + if err != nil { + return nil, err + } + logger := Logger(WrapContextProvider(m.ctx)) + logger.Info("synthesize artifact blob", "ref", m.reference) + m.blob, err = artifactset.SynthesizeArtifactBlobForArtifact(m.art, m.ref.Version()) + logger.Info("synthesize artifact blob done", "ref", m.reference, "error", logging.ErrorMessage(err)) + if err != nil { + m.err = err + return nil, err + } + return m.blob, nil +} diff --git a/api/ocm/extensions/accessmethods/ociartifact/method_test.go b/api/ocm/extensions/accessmethods/ociartifact/method_test.go new file mode 100644 index 000000000..7aa9d88ee --- /dev/null +++ b/api/ocm/extensions/accessmethods/ociartifact/method_test.go @@ -0,0 +1,50 @@ +package ociartifact_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + . "ocm.software/ocm/api/oci/testhelper" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +const ( + OCIPATH = "/tmp/oci" + OCIHOST = "alias" +) + +var _ = Describe("Method", func() { + var env *Builder + + BeforeEach(func() { + env = NewBuilder() + }) + + AfterEach(func() { + env.Cleanup() + }) + + It("accesses artifact", func() { + env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { + OCIManifest1(env) + }) + + FakeOCIRepo(env, OCIPATH, OCIHOST) + + spec := ociartifact.New(oci.StandardOCIRef(OCIHOST+".alias", OCINAMESPACE, OCIVERSION)) + + m, err := spec.AccessMethod(&cpi.DummyComponentVersionAccess{env.OCMContext()}) + Expect(err).To(Succeed()) + + // no credentials required for CTF as fake OCI registry. + Expect(credentials.GetProvidedConsumerId(m)).To(BeNil()) + Expect(accspeccpi.GetAccessMethodImplementation(m).(blobaccess.DigestSource).Digest().String()).To(Equal("sha256:0c4abdb72cf59cb4b77f4aacb4775f9f546ebc3face189b2224a966c8826ca9f")) + }) +}) diff --git a/pkg/contexts/ocm/accessmethods/ociartifact/suite_test.go b/api/ocm/extensions/accessmethods/ociartifact/suite_test.go similarity index 100% rename from pkg/contexts/ocm/accessmethods/ociartifact/suite_test.go rename to api/ocm/extensions/accessmethods/ociartifact/suite_test.go diff --git a/api/ocm/extensions/accessmethods/ociartifact/utils.go b/api/ocm/extensions/accessmethods/ociartifact/utils.go new file mode 100644 index 000000000..55ba75689 --- /dev/null +++ b/api/ocm/extensions/accessmethods/ociartifact/utils.go @@ -0,0 +1,73 @@ +package ociartifact + +import ( + "fmt" + "strings" + + "ocm.software/ocm/api/oci/grammar" + "ocm.software/ocm/api/ocm/cpi" + common "ocm.software/ocm/api/utils/misc" +) + +// OCIArtifactReferenceProvider should be implemented by +// access specs providing access to globally retrievable +// OCI artifacts. +type OCIArtifactReferenceProvider interface { + // GetOCIReference returns the externally usable OCI reference. + // The component version miggt be nil. If it is required to + // determine the ref, an appropriate error has to be returned. + GetOCIReference(cv cpi.ComponentVersionAccess) (string, error) +} + +func GetOCIArtifactReference(ctx cpi.Context, spec cpi.AccessSpec, cv cpi.ComponentVersionAccess) (string, error) { + for spec != nil { + eff, err := ctx.AccessSpecForSpec(spec) + if err != nil { + return "", err + } + if p, ok := eff.(OCIArtifactReferenceProvider); ok { + ref, err := p.GetOCIReference(cv) + if ref != "" || err != nil { + return ref, err + } + } + spec = cpi.GlobalAccess(spec, ctx) + if spec == eff { + spec = nil + } + } + return "", nil +} + +//////////////////////////////////////////////////////////////////////////////// + +func Hint(nv common.NameVersion, locator, repo, version string) string { + if i := strings.LastIndex(version, "@"); i >= 0 { + version = version[:i] // remove digest + } + repository := repoName(nv, locator) + if repo != "" { + if strings.HasPrefix(repo, grammar.RepositorySeparator) { + repository = repo[1:] + } else { + repository = repoName(nv, repo) + } + } + if repository != "" && version != "" { + if !strings.Contains(repository, ":") { + repository = fmt.Sprintf("%s:%s", repository, version) + } + } + return repository +} + +func repoName(nv common.NameVersion, locator string) string { + if nv.GetName() == "" { + return locator + } else { + if locator == "" { + return nv.GetName() + } + return fmt.Sprintf("%s/%s", nv.GetName(), locator) + } +} diff --git a/pkg/contexts/ocm/accessmethods/ociblob/README.md b/api/ocm/extensions/accessmethods/ociblob/README.md similarity index 100% rename from pkg/contexts/ocm/accessmethods/ociblob/README.md rename to api/ocm/extensions/accessmethods/ociblob/README.md diff --git a/api/ocm/extensions/accessmethods/ociblob/cli.go b/api/ocm/extensions/accessmethods/ociblob/cli.go new file mode 100644 index 000000000..f0a7c6007 --- /dev/null +++ b/api/ocm/extensions/accessmethods/ociblob/cli.go @@ -0,0 +1,48 @@ +package ociblob + +import ( + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + "ocm.software/ocm/api/utils/cobrautils/flagsets" +) + +func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { + return flagsets.NewConfigOptionTypeSetHandler( + Type, AddConfig, + options.ReferenceOption, + options.MediatypeOption, + options.SizeOption, + options.DigestOption, + ) +} + +func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { + flagsets.AddFieldByOptionP(opts, options.ReferenceOption, config, "ref") + flagsets.AddFieldByOptionP(opts, options.MediatypeOption, config, "mediaType") + flagsets.AddFieldByOptionP(opts, options.SizeOption, config, "size") + flagsets.AddFieldByOptionP(opts, options.DigestOption, config, "digest") + return nil +} + +var usage = ` +This method implements the access of an OCI blob stored in an OCI repository. +` + +var formatV1 = ` +The type specific specification fields are: + +- **imageReference** *string* + + OCI repository reference (this artifact name used to store the blob). + +- **mediaType** *string* + + The media type of the blob + +- **digest** *string* + + The digest of the blob used to access the blob in the OCI repository. + +- **size** *integer* + + The size of the blob +` diff --git a/api/ocm/extensions/accessmethods/ociblob/method.go b/api/ocm/extensions/accessmethods/ociblob/method.go new file mode 100644 index 000000000..698227fab --- /dev/null +++ b/api/ocm/extensions/accessmethods/ociblob/method.go @@ -0,0 +1,193 @@ +package ociblob + +import ( + "fmt" + "io" + "sync" + + "github.com/mandelsoft/goutils/errors" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/credentials" + ociidentity "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/extensions/repositories/ocireg" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/runtime" +) + +// Type is the access type for a blob in an OCI repository. +const ( + Type = "ociBlob" + 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()))) +} + +// New creates a new OCIBlob accessor. +func New(repository string, digest digest.Digest, mediaType string, size int64) *AccessSpec { + return &AccessSpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(Type), + Reference: repository, + MediaType: mediaType, + Digest: digest, + Size: size, + } +} + +// AccessSpec describes the access for a oci registry. +type AccessSpec struct { + runtime.ObjectVersionedType `json:",inline"` + + // Reference is the oci reference to the OCI repository + Reference string `json:"ref"` + + // MediaType is the media type of the object this schema refers to. + MediaType string `json:"mediaType,omitempty"` + + // Digest is the digest of the targeted content. + Digest digest.Digest `json:"digest"` + + // Size specifies the size in bytes of the blob. + Size int64 `json:"size"` +} + +var _ accspeccpi.AccessSpec = (*AccessSpec)(nil) + +func (a *AccessSpec) Describe(ctx accspeccpi.Context) string { + return fmt.Sprintf("OCI blob %s in repository %s", a.Digest, a.Reference) +} + +func (s *AccessSpec) IsLocal(context accspeccpi.Context) bool { + return false +} + +func (s *AccessSpec) GlobalAccessSpec(ctx accspeccpi.Context) accspeccpi.AccessSpec { + return s +} + +func (s *AccessSpec) GetMimeType() string { + return s.MediaType +} + +func (s *AccessSpec) AccessMethod(access accspeccpi.ComponentVersionAccess) (accspeccpi.AccessMethod, error) { + return accspeccpi.AccessMethodForImplementation(&accessMethod{comp: access, spec: s}, nil) +} + +//////////////////////////////////////////////////////////////////////////////// + +// TODO add cache + +type accessMethod struct { + lock sync.Mutex + blob blobaccess.BlobAccess + comp accspeccpi.ComponentVersionAccess + spec *AccessSpec +} + +var _ accspeccpi.AccessMethodImpl = (*accessMethod)(nil) + +func (_ *accessMethod) IsLocal() bool { + return false +} + +func (m *accessMethod) GetKind() string { + return Type +} + +func (m *accessMethod) AccessSpec() accspeccpi.AccessSpec { + return m.spec +} + +func (m *accessMethod) Close() error { + var err error + m.lock.Lock() + defer m.lock.Unlock() + + if m.blob != nil { + err = m.blob.Close() + m.blob = nil + } + return err +} + +func (m *accessMethod) Get() ([]byte, error) { + return blobaccess.BlobData(m.getBlob()) +} + +func (m *accessMethod) Reader() (io.ReadCloser, error) { + return blobaccess.BlobReader(m.getBlob()) +} + +func (m *accessMethod) MimeType() string { + return m.spec.MediaType +} + +func (m *accessMethod) getBlob() (blobaccess.BlobAccess, error) { + m.lock.Lock() + defer m.lock.Unlock() + + if m.blob != nil { + return m.blob, nil + } + ref, err := oci.ParseRef(m.spec.Reference) + if err != nil { + return nil, err + } + if ref.Tag != nil || ref.Digest != nil { + return nil, errors.ErrInvalid("oci repository", m.spec.Reference) + } + ocictx := m.comp.GetContext().OCIContext() + spec := ocictx.GetAlias(ref.Host) + if spec == nil { + spec = ocireg.NewRepositorySpec(ref.Host) + } + ocirepo, err := m.comp.GetContext().OCIContext().RepositoryForSpec(spec) + if err != nil { + return nil, err + } + ns, err := ocirepo.LookupNamespace(ref.Repository) + if err != nil { + return nil, err + } + size, acc, err := ns.GetBlobData(m.spec.Digest) + if err != nil { + return nil, err + } + if m.spec.Size == blobaccess.BLOB_UNKNOWN_SIZE { + m.spec.Size = size + } else if size != blobaccess.BLOB_UNKNOWN_SIZE { + return nil, errors.Newf("blob size mismatch %d != %d", size, m.spec.Size) + } + m.blob = blobaccess.ForDataAccess(m.spec.Digest, m.spec.Size, m.spec.MediaType, acc) + return m.blob, nil +} + +func (m *accessMethod) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { + m.lock.Lock() + defer m.lock.Unlock() + + ref, err := oci.ParseRef(m.spec.Reference) + if err != nil { + return nil + } + + ocictx := m.comp.GetContext().OCIContext() + spec := ocictx.GetAlias(ref.Host) + if spec == nil { + spec = ocireg.NewRepositorySpec(ref.Host) + } + ocirepo, err := m.comp.GetContext().OCIContext().RepositoryForSpec(spec) + if err != nil { + return nil + } + return credentials.GetProvidedConsumerId(ocirepo, credentials.StringUsageContext(ref.Repository)) +} + +func (m *accessMethod) GetIdentityMatcher() string { + return ociidentity.CONSUMER_TYPE +} diff --git a/api/ocm/extensions/accessmethods/ociblob/method_test.go b/api/ocm/extensions/accessmethods/ociblob/method_test.go new file mode 100644 index 000000000..a13dab729 --- /dev/null +++ b/api/ocm/extensions/accessmethods/ociblob/method_test.go @@ -0,0 +1,50 @@ +package ociblob_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + . "ocm.software/ocm/api/oci/testhelper" + + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/grammar" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociblob" + "ocm.software/ocm/api/utils/accessio" +) + +const ( + OCIPATH = "/tmp/oci" + OCIHOST = "alias" +) + +var _ = Describe("Method", func() { + var env *Builder + + BeforeEach(func() { + env = NewBuilder() + }) + + AfterEach(func() { + env.Cleanup() + }) + + It("accesses artifact", func() { + var desc *artdesc.Descriptor + env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { + desc = OCIManifest1(env) + }) + + FakeOCIRepo(env, OCIPATH, OCIHOST) + + spec := ociblob.New(OCIHOST+".alias"+grammar.RepositorySeparator+OCINAMESPACE, desc.Digest, "", -1) + + m, err := spec.AccessMethod(&cpi.DummyComponentVersionAccess{env.OCMContext()}) + Expect(err).To(Succeed()) + + blob, err := m.Get() + Expect(err).To(Succeed()) + + Expect(string(blob)).To(Equal("manifestlayer")) + }) +}) diff --git a/pkg/contexts/ocm/accessmethods/ociblob/suite_test.go b/api/ocm/extensions/accessmethods/ociblob/suite_test.go similarity index 100% rename from pkg/contexts/ocm/accessmethods/ociblob/suite_test.go rename to api/ocm/extensions/accessmethods/ociblob/suite_test.go diff --git a/pkg/contexts/ocm/accessmethods/options/doc.go b/api/ocm/extensions/accessmethods/options/doc.go similarity index 100% rename from pkg/contexts/ocm/accessmethods/options/doc.go rename to api/ocm/extensions/accessmethods/options/doc.go diff --git a/pkg/contexts/ocm/accessmethods/options/init.go b/api/ocm/extensions/accessmethods/options/init.go similarity index 100% rename from pkg/contexts/ocm/accessmethods/options/init.go rename to api/ocm/extensions/accessmethods/options/init.go diff --git a/api/ocm/extensions/accessmethods/options/registry.go b/api/ocm/extensions/accessmethods/options/registry.go new file mode 100644 index 000000000..f45c5dbb1 --- /dev/null +++ b/api/ocm/extensions/accessmethods/options/registry.go @@ -0,0 +1,105 @@ +package options + +import ( + "sync" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/utils" +) + +const ( + KIND_OPTIONTYPE = "option type" + KIND_OPTION = "option" +) + +type OptionTypeCreator func(name string, description string) OptionType + +type ValueTypeInfo struct { + OptionTypeCreator + Description string +} + +func (i ValueTypeInfo) GetDescription() string { + return i.Description +} + +type Registry = *registry + +var DefaultRegistry = New() + +type registry struct { + lock sync.RWMutex + valueTypes map[string]ValueTypeInfo + optionTypes map[string]OptionType +} + +func New() Registry { + return ®istry{ + valueTypes: map[string]ValueTypeInfo{}, + optionTypes: map[string]OptionType{}, + } +} + +func (r *registry) RegisterOptionType(t OptionType) { + r.lock.Lock() + defer r.lock.Unlock() + r.optionTypes[t.GetName()] = t +} + +func (r *registry) RegisterValueType(name string, c OptionTypeCreator, desc string) { + r.lock.Lock() + defer r.lock.Unlock() + r.valueTypes[name] = ValueTypeInfo{OptionTypeCreator: c, Description: desc} +} + +func (r *registry) GetValueType(name string) *ValueTypeInfo { + r.lock.RLock() + defer r.lock.RUnlock() + if t, ok := r.valueTypes[name]; ok { + return &t + } + return nil +} + +func (r *registry) GetOptionType(name string) OptionType { + r.lock.RLock() + defer r.lock.RUnlock() + return r.optionTypes[name] +} + +func (r *registry) CreateOptionType(typ, name, desc string) (OptionType, error) { + r.lock.RLock() + defer r.lock.RUnlock() + t, ok := r.valueTypes[typ] + if !ok { + return nil, errors.ErrUnknown(KIND_OPTIONTYPE, typ) + } + + n := t.OptionTypeCreator(name, desc) + o := r.optionTypes[name] + if o != nil { + if o.ValueType() != n.ValueType() { + return nil, errors.ErrAlreadyExists(KIND_OPTION, name) + } + return o, nil + } + return n, nil +} + +func (r *registry) Usage() string { + r.lock.RLock() + defer r.lock.RUnlock() + + tinfo := utils.FormatMap("", r.valueTypes) + oinfo := utils.FormatMap("", r.optionTypes) + + return ` +The following predefined option types can be used: + +` + oinfo + ` + +The following predefined value types are supported: + +` + tinfo +} diff --git a/pkg/contexts/ocm/accessmethods/options/registry_test.go b/api/ocm/extensions/accessmethods/options/registry_test.go similarity index 100% rename from pkg/contexts/ocm/accessmethods/options/registry_test.go rename to api/ocm/extensions/accessmethods/options/registry_test.go diff --git a/pkg/contexts/ocm/accessmethods/options/standard.go b/api/ocm/extensions/accessmethods/options/standard.go similarity index 100% rename from pkg/contexts/ocm/accessmethods/options/standard.go rename to api/ocm/extensions/accessmethods/options/standard.go diff --git a/pkg/contexts/ocm/accessmethods/options/suite_test.go b/api/ocm/extensions/accessmethods/options/suite_test.go similarity index 100% rename from pkg/contexts/ocm/accessmethods/options/suite_test.go rename to api/ocm/extensions/accessmethods/options/suite_test.go diff --git a/api/ocm/extensions/accessmethods/options/types.go b/api/ocm/extensions/accessmethods/options/types.go new file mode 100644 index 000000000..1788c3b80 --- /dev/null +++ b/api/ocm/extensions/accessmethods/options/types.go @@ -0,0 +1,118 @@ +package options + +import ( + "fmt" + + "ocm.software/ocm/api/utils/cobrautils/flagsets" +) + +type OptionType interface { + flagsets.ConfigOptionType + ValueType() string + GetDescriptionText() string +} + +type base = flagsets.ConfigOptionType + +type option struct { + base + valueType string +} + +func (o *option) Equal(t flagsets.ConfigOptionType) bool { + if ot, ok := t.(*option); ok { + return o.valueType == ot.valueType && o.GetName() == ot.GetName() + } + return false +} + +func (o *option) ValueType() string { + return o.valueType +} + +func (o *option) GetDescription() string { + return fmt.Sprintf("[*%s*] %s", o.ValueType(), o.base.GetDescription()) +} + +func (o *option) GetDescriptionText() string { + return o.base.GetDescription() +} + +//////////////////////////////////////////////////////////////////////////////// + +func NewStringOptionType(name, desc string) OptionType { + return &option{ + base: flagsets.NewStringOptionType(name, desc), + valueType: TYPE_STRING, + } +} + +func NewStringArrayOptionType(name, desc string) OptionType { + return &option{ + base: flagsets.NewStringArrayOptionType(name, desc), + valueType: TYPE_STRINGARRAY, + } +} + +func NewIntOptionType(name, desc string) OptionType { + return &option{ + base: flagsets.NewIntOptionType(name, desc), + valueType: TYPE_INT, + } +} + +func NewBoolOptionType(name, desc string) OptionType { + return &option{ + base: flagsets.NewBoolOptionType(name, desc), + valueType: TYPE_BOOL, + } +} + +func NewYAMLOptionType(name, desc string) OptionType { + return &option{ + base: flagsets.NewYAMLOptionType(name, desc), + valueType: TYPE_YAML, + } +} + +func NewValueMapYAMLOptionType(name, desc string) OptionType { + return &option{ + base: flagsets.NewValueMapYAMLOptionType(name, desc), + valueType: TYPE_STRINGMAPYAML, + } +} + +func NewValueMapOptionType(name, desc string) OptionType { + return &option{ + base: flagsets.NewValueMapOptionType(name, desc), + valueType: TYPE_STRING2YAML, + } +} + +func NewStringMapOptionType(name, desc string) OptionType { + return &option{ + base: flagsets.NewStringMapOptionType(name, desc), + valueType: TYPE_STRING2STRING, + } +} + +func NewStringSliceMapOptionType(name, desc string) OptionType { + return &option{ + base: flagsets.NewStringSliceMapOptionType(name, desc), + valueType: TYPE_STRING2STRINGSLICE, + } +} + +func NewStringSliceMapColonOptionType(name, desc string) OptionType { + return &option{ + base: flagsets.NewStringSliceMapColonOptionType(name, desc), + valueType: TYPE_STRINGCOLONSTRINGSLICE, + } +} + +func NewBytesOptionType(name, desc string) OptionType { + return &option{ + base: flagsets.NewBytesOptionType(name, desc), + valueType: TYPE_BYTES, + } +} diff --git a/api/ocm/extensions/accessmethods/plugin/cmd_test.go b/api/ocm/extensions/accessmethods/plugin/cmd_test.go new file mode 100644 index 000000000..f7d588f12 --- /dev/null +++ b/api/ocm/extensions/accessmethods/plugin/cmd_test.go @@ -0,0 +1,79 @@ +package plugin_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/env" + . "ocm.software/ocm/api/ocm/plugin/testutils" + + "github.com/mandelsoft/goutils/sliceutils" + "github.com/mandelsoft/goutils/transformer" + "github.com/spf13/pflag" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + "ocm.software/ocm/api/ocm/plugin/plugins" + "ocm.software/ocm/api/ocm/plugin/registration" + "ocm.software/ocm/api/utils/cobrautils/flagsets" +) + +const ( + CA = "/tmp/ca" + VERSION = "v1" +) + +var _ = Describe("Add with new access method", func() { + var env *Environment + var ctx ocm.Context + var registry plugins.Set + var plugins TempPluginDir + + BeforeEach(func() { + env = NewEnvironment(TestData()) + ctx = env.OCMContext() + plugins, registry = Must2(ConfigureTestPlugins2(env, "testdata")) + Expect(registration.RegisterExtensions(ctx)).To(Succeed()) + p := registry.Get("test") + Expect(p).NotTo(BeNil()) + }) + + AfterEach(func() { + plugins.Cleanup() + env.Cleanup() + }) + + It("handles resource options", func() { + at := ctx.AccessMethods().GetType("test") + Expect(at).NotTo(BeNil()) + + h := at.ConfigOptionTypeSetHandler() + Expect(h).NotTo(BeNil()) + Expect(h.GetName()).To(Equal("test")) + + ot := h.OptionTypes() + Expect(len(ot)).To(Equal(2)) + + opts := h.CreateOptions() + Expect(sliceutils.Transform(opts.Options(), transformer.GetName[flagsets.Option, string])).To(ConsistOf( + "mediaType", "accessPath")) + + fs := &pflag.FlagSet{} + fs.SortFlags = true + opts.AddFlags(fs) + + Expect("\n" + fs.FlagUsages()).To(Equal(` + --accessPath string file path + --mediaType string media type for artifact blob representation +`)) + + MustBeSuccessful(fs.Parse([]string{"--accessPath", "filepath", "--" + options.MediatypeOption.GetName(), "yaml"})) + + cfg := flagsets.Config{} + MustBeSuccessful(h.ApplyConfig(opts, cfg)) + Expect(cfg).To(YAMLEqual(` +mediaType: yaml +path: filepath +`)) + }) +}) diff --git a/pkg/contexts/ocm/accessmethods/plugin/doc.go b/api/ocm/extensions/accessmethods/plugin/doc.go similarity index 100% rename from pkg/contexts/ocm/accessmethods/plugin/doc.go rename to api/ocm/extensions/accessmethods/plugin/doc.go diff --git a/api/ocm/extensions/accessmethods/plugin/method.go b/api/ocm/extensions/accessmethods/plugin/method.go new file mode 100644 index 000000000..95f8e32ee --- /dev/null +++ b/api/ocm/extensions/accessmethods/plugin/method.go @@ -0,0 +1,144 @@ +package plugin + +import ( + "encoding/json" + "io" + "sync" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/identity/hostpath" + "ocm.software/ocm/api/ocm" + cpi "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/plugin" + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/runtime" +) + +type AccessSpec struct { + runtime.UnstructuredVersionedTypedObject `json:",inline"` + handler *PluginHandler +} + +var ( + _ cpi.AccessSpec = &AccessSpec{} + _ cpi.HintProvider = &AccessSpec{} +) + +func (s *AccessSpec) AccessMethod(cv cpi.ComponentVersionAccess) (cpi.AccessMethod, error) { + return s.handler.AccessMethod(s, cv) +} + +func (s *AccessSpec) Describe(ctx cpi.Context) string { + return s.handler.Describe(s, ctx) +} + +func (_ *AccessSpec) IsLocal(cpi.Context) bool { + return false +} + +func (s *AccessSpec) GlobalAccessSpec(cpi.Context) cpi.AccessSpec { + return s +} + +func (s *AccessSpec) GetMimeType() string { + return s.handler.GetMimeType(s) +} + +func (s *AccessSpec) GetReferenceHint(cv cpi.ComponentVersionAccess) string { + return s.handler.GetReferenceHint(s, cv) +} + +func (s *AccessSpec) Handler() *PluginHandler { + return s.handler +} + +//////////////////////////////////////////////////////////////////////////////// + +type accessMethod struct { + lock sync.Mutex + blob blobaccess.BlobAccess + ctx ocm.Context + + handler *PluginHandler + spec *AccessSpec + info *ppi.AccessSpecInfo + creds json.RawMessage +} + +var _ cpi.AccessMethodImpl = (*accessMethod)(nil) + +func newMethod(p *PluginHandler, spec *AccessSpec, ctx ocm.Context, info *ppi.AccessSpecInfo, creds json.RawMessage) *accessMethod { + return &accessMethod{ + ctx: ctx, + handler: p, + spec: spec, + info: info, + creds: creds, + } +} + +func (_ *accessMethod) IsLocal() bool { + return false +} + +func (m *accessMethod) GetKind() string { + return m.spec.GetKind() +} + +func (m *accessMethod) AccessSpec() cpi.AccessSpec { + return m.spec +} + +func (m *accessMethod) Close() error { + var err error + m.lock.Lock() + defer m.lock.Unlock() + if m.blob != nil { + err = m.blob.Close() + m.blob = nil + } + return err +} + +func (m *accessMethod) Get() ([]byte, error) { + return blobaccess.BlobData(m.getBlob()) +} + +func (m *accessMethod) Reader() (io.ReadCloser, error) { + return blobaccess.BlobReader(m.getBlob()) +} + +func (m *accessMethod) MimeType() string { + return m.info.MediaType +} + +func (m *accessMethod) getBlob() (blobaccess.BlobAccess, error) { + m.lock.Lock() + defer m.lock.Unlock() + + if m.blob != nil { + return m.blob, nil + } + + spec, err := json.Marshal(m.spec) + if err != nil { + return nil, errors.Wrapf(err, "cannot marshal access spec") + } + m.blob = accessobj.CachedBlobAccessForWriter(m.ctx, m.MimeType(), plugin.NewAccessDataWriter(m.handler.plug, m.creds, spec)) + return m.blob, nil +} + +func (m *accessMethod) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { + if len(m.info.ConsumerId) == 0 { + return nil + } + return m.info.ConsumerId +} + +func (m *accessMethod) GetIdentityMatcher() string { + return hostpath.IDENTITY_TYPE +} diff --git a/api/ocm/extensions/accessmethods/plugin/method_test.go b/api/ocm/extensions/accessmethods/plugin/method_test.go new file mode 100644 index 000000000..42f891928 --- /dev/null +++ b/api/ocm/extensions/accessmethods/plugin/method_test.go @@ -0,0 +1,84 @@ +//go:build unix + +package plugin_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + . "ocm.software/ocm/api/ocm/plugin/testutils" + + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/plugin/plugins" + "ocm.software/ocm/api/ocm/plugin/registration" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" +) + +const ( + ARCH = "ctf" + COMP = "github.com/mandelsoft/comp" + VERS = "1.0.0" + PROVIDER = "mandelsoft" +) + +var _ = Describe("setup plugin cache", func() { + var ctx ocm.Context + var registry plugins.Set + var env *Builder + var plugins TempPluginDir + + var accessSpec ocm.AccessSpec + + BeforeEach(func() { + var err error + + accessSpec, err = ocm.NewGenericAccessSpec(` +type: test +someattr: value +`) + Expect(err).To(Succeed()) + + env = NewBuilder(nil) + ctx = env.OCMContext() + plugins, registry = Must2(ConfigureTestPlugins2(env, "testdata")) + Expect(registration.RegisterExtensions(ctx)).To(Succeed()) + p := registry.Get("test") + Expect(p).NotTo(BeNil()) + }) + + AfterEach(func() { + plugins.Cleanup() + env.Cleanup() + }) + + It("registers access methods", func() { + env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { + env.Component(COMP, func() { + env.Version(VERS, func() { + env.Provider(PROVIDER) + env.Resource("testdata", VERS, "PlainText", metav1.ExternalRelation, func() { + env.Access(accessSpec) + }) + }) + }) + }) + + repo := Must(ctf.Open(ctx, accessobj.ACC_READONLY, ARCH, 0, env)) + defer Close(repo) + + cv := Must(repo.LookupComponentVersion(COMP, VERS)) + defer Close(cv) + + r := Must(cv.GetResourceByIndex(0)) + + m := Must(r.AccessMethod()) + Expect(m.MimeType()).To(Equal("plain/text")) + + data := Must(m.Get()) + Expect(string(data)).To(Equal("test content\n{\"someattr\":\"value\",\"type\":\"test\"}\n")) + }) +}) diff --git a/api/ocm/extensions/accessmethods/plugin/plugin.go b/api/ocm/extensions/accessmethods/plugin/plugin.go new file mode 100644 index 000000000..a6465a722 --- /dev/null +++ b/api/ocm/extensions/accessmethods/plugin/plugin.go @@ -0,0 +1,134 @@ +package plugin + +import ( + "bytes" + "encoding/json" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/identity/hostpath" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/plugin" + "ocm.software/ocm/api/ocm/plugin/descriptor" + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/utils/errkind" +) + +type plug = plugin.Plugin + +// PluginHandler is a shared object between the AccessMethod implementation and the AccessSpec implementation. The +// object knows the actual plugin and can therefore forward the method calls to corresponding cli commands. +type PluginHandler struct { + plug + + // cached info + info *ppi.AccessSpecInfo + err error + orig []byte +} + +func NewPluginHandler(p plugin.Plugin) *PluginHandler { + return &PluginHandler{plug: p} +} + +func (p *PluginHandler) Info(spec *AccessSpec) (*ppi.AccessSpecInfo, error) { + if p.info != nil || p.err != nil { + raw, err := spec.UnstructuredVersionedTypedObject.GetRaw() + if err != nil { + return nil, errors.Wrapf(err, "cannot marshal access specification") + } + if bytes.Equal(raw, p.orig) { + return p.info, p.err + } + } + p.info, p.err = p.Validate(spec) + return p.info, p.err +} + +func (p *PluginHandler) AccessMethod(spec *AccessSpec, cv cpi.ComponentVersionAccess) (cpi.AccessMethod, error) { + mspec := p.GetAccessMethodDescriptor(spec.GetKind(), spec.GetVersion()) + if mspec == nil { + return nil, errors.ErrNotFound(errkind.KIND_ACCESSMETHOD, spec.GetType(), descriptor.KIND_PLUGIN, p.Name()) + } + + creddata, err := p.getCredentialData(spec, cv) + if err != nil { + return nil, err + } + + info, err := p.Info(spec) + if err != nil { + return nil, err + } + return accspeccpi.AccessMethodForImplementation(newMethod(p, spec, cv.GetContext(), info, creddata), nil) +} + +func (p *PluginHandler) getCredentialData(spec *AccessSpec, cv cpi.ComponentVersionAccess) (json.RawMessage, error) { + info, err := p.Info(spec) + if err != nil { + return nil, err + } + + var creds credentials.Credentials + if len(info.ConsumerId) > 0 { + creds, err = credentials.CredentialsForConsumer(cv.GetContext(), info.ConsumerId, hostpath.IdentityMatcher(info.ConsumerId.Type())) + if err != nil { + return nil, err + } + } + + var creddata json.RawMessage + if creds != nil { + creddata, err = json.Marshal(creds) + if err != nil { + return nil, err + } + } + return creddata, nil +} + +func (p *PluginHandler) Describe(spec *AccessSpec, ctx cpi.Context) string { + mspec := p.GetAccessMethodDescriptor(spec.GetKind(), spec.GetVersion()) + if mspec == nil { + return "unknown type " + spec.GetType() + } + info, err := p.Info(spec) + if err != nil { + return err.Error() + } + return info.Short +} + +func (p *PluginHandler) GetMimeType(spec *AccessSpec) string { + mspec := p.GetAccessMethodDescriptor(spec.GetKind(), spec.GetVersion()) + if mspec == nil { + return "unknown type " + spec.GetType() + } + info, err := p.Info(spec) + if err != nil { + return "" + } + return info.Short +} + +func (p *PluginHandler) GetReferenceHint(spec *AccessSpec, cv cpi.ComponentVersionAccess) string { + mspec := p.GetAccessMethodDescriptor(spec.GetKind(), spec.GetVersion()) + if mspec == nil { + return "unknown type " + spec.GetType() + } + info, err := p.Info(spec) + if err != nil { + return "" + } + return info.Hint +} + +func (p *PluginHandler) Validate(spec *AccessSpec) (*ppi.AccessSpecInfo, error) { + data, err := spec.GetRaw() + if err != nil { + return nil, err + } + return p.plug.ValidateAccessMethod(data) +} diff --git a/pkg/contexts/ocm/accessmethods/plugin/suite_test.go b/api/ocm/extensions/accessmethods/plugin/suite_test.go similarity index 100% rename from pkg/contexts/ocm/accessmethods/plugin/suite_test.go rename to api/ocm/extensions/accessmethods/plugin/suite_test.go diff --git a/pkg/contexts/ocm/accessmethods/plugin/testdata/test b/api/ocm/extensions/accessmethods/plugin/testdata/test similarity index 100% rename from pkg/contexts/ocm/accessmethods/plugin/testdata/test rename to api/ocm/extensions/accessmethods/plugin/testdata/test diff --git a/api/ocm/extensions/accessmethods/plugin/type.go b/api/ocm/extensions/accessmethods/plugin/type.go new file mode 100644 index 000000000..9f9638594 --- /dev/null +++ b/api/ocm/extensions/accessmethods/plugin/type.go @@ -0,0 +1,69 @@ +package plugin + +import ( + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + "ocm.software/ocm/api/ocm/plugin" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/api/utils/runtime" +) + +type accessType struct { + accspeccpi.AccessType + plug plugin.Plugin + cliopts flagsets.ConfigOptionTypeSet +} + +var _ accspeccpi.AccessType = (*accessType)(nil) + +func NewType(name string, p plugin.Plugin, desc *plugin.AccessMethodDescriptor) accspeccpi.AccessType { + format := desc.Format + if format != "" { + format = "\n" + format + } + + t := &accessType{ + plug: p, + } + + cfghdlr := flagsets.NewConfigOptionTypeSetHandler(name, t.AddConfig) + for _, o := range desc.CLIOptions { + var opt flagsets.ConfigOptionType + if o.Type == "" { + opt = options.DefaultRegistry.GetOptionType(o.Name) + if opt == nil { + p.Context().Logger(plugin.TAG).Warn("unknown option", "plugin", p.Name(), "accessmethod", name, "option", o.Name) + } + } else { + var err error + opt, err = options.DefaultRegistry.CreateOptionType(o.Type, o.Name, o.Description) + if err != nil { + p.Context().Logger(plugin.TAG).Warn("invalid option", "plugin", p.Name(), "accessmethod", name, "option", o.Name, "error", err.Error()) + } + } + if opt != nil { + cfghdlr.AddOptionType(opt) + } + } + aopts := []accspeccpi.AccessSpecTypeOption{accspeccpi.WithDescription(desc.Description), accspeccpi.WithFormatSpec(format)} + if cfghdlr.Size() > 0 { + aopts = append(aopts, accspeccpi.WithConfigHandler(cfghdlr)) + t.cliopts = cfghdlr + } + t.AccessType = accspeccpi.NewAccessSpecType[*AccessSpec](name, aopts...) + return t +} + +func (t *accessType) Decode(data []byte, unmarshaler runtime.Unmarshaler) (accspeccpi.AccessSpec, error) { + spec, err := t.AccessType.Decode(data, unmarshaler) + if err != nil { + return nil, err + } + spec.(*AccessSpec).handler = NewPluginHandler(t.plug) + return spec, nil +} + +func (t *accessType) AddConfig(opts flagsets.ConfigOptions, cfg flagsets.Config) error { + opts = opts.FilterBy(t.cliopts.HasOptionType) + return t.plug.ComposeAccessMethod(t.GetType(), opts, cfg) +} diff --git a/pkg/contexts/ocm/accessmethods/relativeociref/README.md b/api/ocm/extensions/accessmethods/relativeociref/README.md similarity index 100% rename from pkg/contexts/ocm/accessmethods/relativeociref/README.md rename to api/ocm/extensions/accessmethods/relativeociref/README.md diff --git a/api/ocm/extensions/accessmethods/relativeociref/method.go b/api/ocm/extensions/accessmethods/relativeociref/method.go new file mode 100644 index 000000000..69a28664a --- /dev/null +++ b/api/ocm/extensions/accessmethods/relativeociref/method.go @@ -0,0 +1,88 @@ +package relativeociref + +import ( + "fmt" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + "ocm.software/ocm/api/ocm/internal" + "ocm.software/ocm/api/utils/runtime" +) + +// Type describes the access of an OCI artifact stored as OCI artifact in the OCI +// registry hosting the actual component version. +const ( + Type = "relativeOciReference" + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +func init() { + accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](Type)) + accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](TypeV1)) +} + +var _ accspeccpi.HintProvider = (*AccessSpec)(nil) + +// New creates a new localFilesystemBlob accessor. +func New(ref string) *AccessSpec { + return &AccessSpec{ + ObjectVersionedType: runtime.NewVersionedObjectType(Type), + Reference: ref, + } +} + +// AccessSpec describes the access of an OCI artifact stored as OCI artifact in +// the OCI registry hosting the actual component version. +type AccessSpec struct { + runtime.ObjectVersionedType `json:",inline"` + // Reference is the OCI repository name and version separated by a colon. + Reference string `json:"reference"` +} + +func (a *AccessSpec) Describe(context accspeccpi.Context) string { + return fmt.Sprintf("local OCI artifact %s", a.Reference) +} + +func (a *AccessSpec) IsLocal(context accspeccpi.Context) bool { + return true +} + +func (a *AccessSpec) GlobalAccessSpec(context accspeccpi.Context) accspeccpi.AccessSpec { + return nil +} + +func (a *AccessSpec) AccessMethod(access accspeccpi.ComponentVersionAccess) (accspeccpi.AccessMethod, error) { + return access.AccessMethod(a) +} + +func (a *AccessSpec) GetDigest() (string, bool) { + ref, err := oci.ParseRef(a.Reference) + if err != nil { + return "", true + } + if ref.Digest != nil { + return ref.Digest.String(), true + } + return "", false +} + +func (a *AccessSpec) GetReferenceHint(cv internal.ComponentVersionAccess) string { + return a.Reference +} + +func (a *AccessSpec) GetOCIReference(cv accspeccpi.ComponentVersionAccess) (string, error) { + if cv == nil { + return "", fmt.Errorf("component version required to determine OCI reference") + } + m, err := a.AccessMethod(cv) + if err != nil { + return "", err + } + defer m.Close() + + if o, ok := accspeccpi.GetAccessMethodImplementation(m).(ociartifact.OCIArtifactReferenceProvider); ok { + return o.GetOCIReference(nil) + } + return "", nil +} diff --git a/api/ocm/extensions/accessmethods/relativeociref/method_test.go b/api/ocm/extensions/accessmethods/relativeociref/method_test.go new file mode 100644 index 000000000..4634480fc --- /dev/null +++ b/api/ocm/extensions/accessmethods/relativeociref/method_test.go @@ -0,0 +1,75 @@ +package relativeociref_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + . "ocm.software/ocm/api/oci/testhelper" + + "github.com/mandelsoft/goutils/finalizer" + + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/relativeociref" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + utils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +const ( + OCIPATH = "/tmp/oci" + OCIHOST = "alias" +) + +const ( + COMP = "acme.org/compo" + COMPVERS = "v1.0.0" + RES = "ref" +) + +var _ = Describe("Method", func() { + var env *Builder + + BeforeEach(func() { + env = NewBuilder() + }) + + AfterEach(func() { + env.Cleanup() + }) + + It("accesses artifact", func() { + var finalize finalizer.Finalizer + defer Defer(finalize.Finalize) + + env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { + OCIManifest1(env) + }) + FakeOCIRepo(env, OCIPATH, OCIHOST) + + env.OCMCommonTransport(OCIPATH, accessio.FormatDirectory, func() { + env.ComponentVersion(COMP, COMPVERS, func() { + env.Resource(RES, COMPVERS, "testtyp", v1.LocalRelation, func() { + env.Access(relativeociref.New(OCINAMESPACE + ":" + OCIVERSION)) + }) + }) + }) + + repo := Must(ctf.Open(env, accessobj.ACC_READONLY, OCIPATH, 0, env)) + finalize.Close(repo) + vers := Must(repo.LookupComponentVersion(COMP, COMPVERS)) + finalize.Close(vers) + res := Must(vers.GetResourceByIndex(0)) + m := Must(res.AccessMethod()) + finalize.With(func() error { + return m.Close() + }) + data := Must(m.Get()) + Expect(len(data)).To(Equal(628)) + Expect(accspeccpi.GetAccessMethodImplementation(m).(blobaccess.DigestSource).Digest().String()).To(Equal("sha256:0c4abdb72cf59cb4b77f4aacb4775f9f546ebc3face189b2224a966c8826ca9f")) + Expect(utils.GetOCIArtifactRef(env, res)).To(Equal("ocm/value:v2.0")) + }) +}) diff --git a/pkg/contexts/ocm/accessmethods/relativeociref/suite_test.go b/api/ocm/extensions/accessmethods/relativeociref/suite_test.go similarity index 100% rename from pkg/contexts/ocm/accessmethods/relativeociref/suite_test.go rename to api/ocm/extensions/accessmethods/relativeociref/suite_test.go diff --git a/api/ocm/extensions/accessmethods/relativeociref/transfer_test.go b/api/ocm/extensions/accessmethods/relativeociref/transfer_test.go new file mode 100644 index 000000000..acdcd3a93 --- /dev/null +++ b/api/ocm/extensions/accessmethods/relativeociref/transfer_test.go @@ -0,0 +1,149 @@ +package relativeociref_test + +import ( + "encoding/json" + "fmt" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + . "ocm.software/ocm/api/oci/testhelper" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + ocictf "ocm.software/ocm/api/oci/extensions/repositories/ctf" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/relativeociref" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/attrs/keepblobattr" + storagecontext "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/oci" + "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/oci/ocirepo" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/tools/transfer" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" +) + +const OUT = "/tmp/res" + +func FakeOCIRegBaseFunction(ctx *storagecontext.StorageContext) string { + return "baseurl.io" +} + +var _ = Describe("Transfer handler", func() { + var env *Builder + var ldesc *artdesc.Descriptor + + BeforeEach(func() { + env = NewBuilder() + + env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { + ldesc = OCIManifest1(env) + }) + + env.OCMCommonTransport(OCIPATH, accessio.FormatDirectory, func() { + env.ComponentVersion(COMP, COMPVERS, func() { + env.Resource("artifact", "", resourcetypes.OCI_IMAGE, metav1.LocalRelation, func() { + env.Access( + relativeociref.New(OCINAMESPACE + ":" + OCIVERSION), + ) + }) + }) + }) + }) + + AfterEach(func() { + env.Cleanup() + }) + + It("it should copy an image by value to a ctf file", func() { + src, err := ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, OCIPATH, 0, env) + Expect(err).To(Succeed()) + cv, err := src.LookupComponentVersion(COMP, COMPVERS) + Expect(err).To(Succeed()) + tgt, err := ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env) + Expect(err).To(Succeed()) + defer tgt.Close() + opts := &standard.Options{} + opts.SetResourcesByValue(true) + handler := standard.NewDefaultHandler(opts) + // handler, err := standard.New(standard.ResourcesByValue()) + Expect(err).To(Succeed()) + err = transfer.TransferVersion(nil, nil, cv, tgt, handler) + Expect(err).To(Succeed()) + Expect(env.DirExists(OUT)).To(BeTrue()) + + list, err := tgt.ComponentLister().GetComponents("", true) + Expect(err).To(Succeed()) + Expect(list).To(Equal([]string{COMP})) + comp, err := tgt.LookupComponentVersion(COMP, COMPVERS) + Expect(err).To(Succeed()) + Expect(len(comp.GetDescriptor().Resources)).To(Equal(1)) + Expect(comp.GetDescriptor().Resources[0].Access.GetType()).To(Equal(localblob.Type)) + data, err := json.Marshal(comp.GetDescriptor().Resources[0].Access) + Expect(err).To(Succeed()) + + fmt.Printf("%s\n", string(data)) + hash := HashManifest1(artifactset.DefaultArtifactSetDescriptorFileName) + Expect(string(data)).To(StringEqualWithContext(fmt.Sprintf(`{"localReference":"%s","mediaType":"application/vnd.oci.image.manifest.v1+tar+gzip","referenceName":"ocm/value:v2.0","type":"localBlob"}`, hash))) + + r, err := comp.GetResourceByIndex(0) + Expect(err).To(Succeed()) + meth, err := r.AccessMethod() + Expect(err).To(Succeed()) + defer meth.Close() + reader, err := meth.Reader() + Expect(err).To(Succeed()) + defer reader.Close() + set, err := artifactset.Open(accessobj.ACC_READONLY, "", 0, accessio.Reader(reader)) + Expect(err).To(Succeed()) + defer set.Close() + + _, blob, err := set.GetBlobData(ldesc.Digest) + Expect(err).To(Succeed()) + data, err = blob.Get() + Expect(err).To(Succeed()) + Expect(string(data)).To(Equal("manifestlayer")) + }) + + It("it should copy an image by value to an oci repo with uploader", func() { + env.OCMContext().BlobHandlers().Register(ocirepo.NewArtifactHandler(FakeOCIRegBaseFunction), + cpi.ForRepo(oci.CONTEXT_TYPE, ocictf.Type), cpi.ForMimeType(artdesc.ToContentMediaType(artdesc.MediaTypeImageManifest))) + keepblobattr.Set(env.OCMContext(), true) + + src, err := ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, OCIPATH, 0, env) + Expect(err).To(Succeed()) + cv, err := src.LookupComponentVersion(COMP, COMPVERS) + Expect(err).To(Succeed()) + + tgt := Must(ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) + defer tgt.Close() + opts := &standard.Options{} + opts.SetResourcesByValue(true) + handler := standard.NewDefaultHandler(opts) + // handler, err := standard.New(standard.ResourcesByValue()) + Expect(err).To(Succeed()) + err = transfer.TransferVersion(nil, nil, cv, tgt, handler) + Expect(err).To(Succeed()) + Expect(env.DirExists(OUT)).To(BeTrue()) + + list, err := tgt.ComponentLister().GetComponents("", true) + Expect(err).To(Succeed()) + Expect(list).To(Equal([]string{COMP})) + comp, err := tgt.LookupComponentVersion(COMP, COMPVERS) + Expect(err).To(Succeed()) + Expect(len(comp.GetDescriptor().Resources)).To(Equal(1)) + Expect(comp.GetDescriptor().Resources[0].Access.GetType()).To(Equal(localblob.Type)) + data, err := json.Marshal(comp.GetDescriptor().Resources[0].Access) + Expect(err).To(Succeed()) + + fmt.Printf("%s\n", string(data)) + hash := HashManifest1(artifactset.DefaultArtifactSetDescriptorFileName) + Expect(string(data)).To(StringEqualWithContext(fmt.Sprintf(`{"globalAccess":{"imageReference":"baseurl.io/ocm/value:v2.0","type":"ociArtifact"},"localReference":"%s","mediaType":"application/vnd.oci.image.manifest.v1+tar+gzip","referenceName":"ocm/value:v2.0","type":"localBlob"}`, hash))) + }) +}) diff --git a/pkg/contexts/ocm/accessmethods/s3/README.md b/api/ocm/extensions/accessmethods/s3/README.md similarity index 100% rename from pkg/contexts/ocm/accessmethods/s3/README.md rename to api/ocm/extensions/accessmethods/s3/README.md diff --git a/api/ocm/extensions/accessmethods/s3/cli.go b/api/ocm/extensions/accessmethods/s3/cli.go new file mode 100644 index 000000000..07ee17776 --- /dev/null +++ b/api/ocm/extensions/accessmethods/s3/cli.go @@ -0,0 +1,30 @@ +package s3 + +import ( + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + "ocm.software/ocm/api/utils/cobrautils/flagsets" +) + +func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { + return flagsets.NewConfigOptionTypeSetHandler( + Type, AddConfig, + options.RegionOption, + options.BucketOption, + options.ReferenceOption, + options.MediatypeOption, + options.VersionOption, + ) +} + +func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { + flagsets.AddFieldByOptionP(opts, options.ReferenceOption, config, "key") + flagsets.AddFieldByOptionP(opts, options.MediatypeOption, config, "mediaType") + flagsets.AddFieldByOptionP(opts, options.RegionOption, config, "region") + flagsets.AddFieldByOptionP(opts, options.BucketOption, config, "bucket") + flagsets.AddFieldByOptionP(opts, options.VersionOption, config, "version") + return nil +} + +var usage = ` +This method implements the access of a blob stored in an S3 bucket. +` diff --git a/api/ocm/extensions/accessmethods/s3/identity/identity.go b/api/ocm/extensions/accessmethods/s3/identity/identity.go new file mode 100644 index 000000000..327e2a398 --- /dev/null +++ b/api/ocm/extensions/accessmethods/s3/identity/identity.go @@ -0,0 +1,66 @@ +package identity + +import ( + "path" + "strings" + + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/credentials/identity/hostpath" + "ocm.software/ocm/api/utils/listformat" +) + +const CONSUMER_TYPE = "S3" + +// identity properties. +const ( + ID_HOSTNAME = hostpath.ID_HOSTNAME + ID_PORT = hostpath.ID_PORT + ID_PATHPREFIX = hostpath.ID_PATHPREFIX +) + +// credential properties. +const ( + ATTR_AWS_ACCESS_KEY_ID = "awsAccessKeyID" + ATTR_AWS_SECRET_ACCESS_KEY = "awsSecretAccessKey" + ATTR_TOKEN = cpi.ATTR_TOKEN +) + +const GITHUB = "github.com" + +var identityMatcher = hostpath.IdentityMatcher(CONSUMER_TYPE) + +func IdentityMatcher(pattern, cur, id cpi.ConsumerIdentity) bool { + return identityMatcher(pattern, cur, id) +} + +func init() { + attrs := listformat.FormatListElements("", listformat.StringElementDescriptionList{ + ATTR_AWS_ACCESS_KEY_ID, "AWS access key id", + ATTR_AWS_SECRET_ACCESS_KEY, "AWS secret for access key id", + ATTR_TOKEN, "AWS access token (alternatively)", + }) + cpi.RegisterStandardIdentity(CONSUMER_TYPE, identityMatcher, + `S3 credential matcher + +This matcher is a hostpath matcher.`, + attrs) +} + +func GetConsumerId(host, bucket, key, version string) cpi.ConsumerIdentity { + id := cpi.NewConsumerIdentity(CONSUMER_TYPE) + + parts := strings.Split(host, ":") + if parts[0] != "" { + id[ID_HOSTNAME] = parts[0] + } + if len(parts) > 1 { + id[ID_PORT] = parts[1] + } + id[ID_PATHPREFIX] = path.Join(bucket, key, version) + return id +} + +func GetCredentials(ctx cpi.ContextProvider, host, bucket, key, version string) (cpi.Credentials, error) { + id := GetConsumerId(host, bucket, key, version) + return cpi.CredentialsForConsumer(ctx.CredentialsContext(), id, identityMatcher) +} diff --git a/api/ocm/extensions/accessmethods/s3/ifce_test.go b/api/ocm/extensions/accessmethods/s3/ifce_test.go new file mode 100644 index 000000000..75631bc3d --- /dev/null +++ b/api/ocm/extensions/accessmethods/s3/ifce_test.go @@ -0,0 +1,9 @@ +package s3 + +import ( + "ocm.software/ocm/api/ocm/cpi/accspeccpi" +) + +func Versions() accspeccpi.AccessTypeVersionScheme { + return versions +} diff --git a/api/ocm/extensions/accessmethods/s3/method.go b/api/ocm/extensions/accessmethods/s3/method.go new file mode 100644 index 000000000..728ae81d6 --- /dev/null +++ b/api/ocm/extensions/accessmethods/s3/method.go @@ -0,0 +1,175 @@ +package s3 + +import ( + "fmt" + + . "github.com/mandelsoft/goutils/exception" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/identity/hostpath" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/s3/identity" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessio/downloader" + "ocm.software/ocm/api/utils/accessio/downloader/s3" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/api/utils/runtime" +) + +// Type is the access type of S3 registry. +const ( + Type = "s3" + + LegacyType = "S3" + LegacyTypeV1 = LegacyType + runtime.VersionSeparator + "v1" +) + +var versions = accspeccpi.NewAccessTypeVersionScheme(Type).WithKindAliases(LegacyType) + +var formats = accspeccpi.NewAccessSpecFormatVersionRegistry() + +func init() { + formats.Register(Type, runtime.NewConvertedVersion[accspeccpi.AccessSpec, *AccessSpec, *AccessSpecV1](&converterV1{})) + formats.Register(LegacyType, runtime.NewConvertedVersion[accspeccpi.AccessSpec, *AccessSpec, *AccessSpecV1](&converterV1{})) + + initV1() + initV2() + + anon := accspeccpi.MustNewAccessSpecMultiFormatVersion(Type, formats) + Must(versions.Register(accspeccpi.NewAccessSpecTypeByFormatVersion(Type, anon, accspeccpi.WithDescription(usage), accspeccpi.WithConfigHandler(ConfigHandler())))) + Must(versions.Register(accspeccpi.NewAccessSpecTypeByFormatVersion(LegacyType, anon, accspeccpi.WithDescription(usage)))) + accspeccpi.RegisterAccessTypeVersions(versions) +} + +// AccessSpec describes the access for a S3 registry. +type AccessSpec struct { + runtime.InternalVersionedTypedObject[accspeccpi.AccessSpec] + + // Region needs to be set even though buckets are global. + // We can't assume that there is a default region setting sitting somewhere. + // +optional + Region string + // Bucket where the s3 object is located. + Bucket string + // Key of the object to look for. This value will be used together with Bucket and Version to form an identity. + Key string + // Version of the object. + // +optional + Version string + // MediaType defines the mime type of the object to download. + // +optional + MediaType string + downloader downloader.Downloader +} + +var _ accspeccpi.AccessSpec = (*AccessSpec)(nil) + +// New creates a new GitHub registry access spec version v1. +func New(region, bucket, key, version, mediaType string, downloader ...downloader.Downloader) *AccessSpec { + return &AccessSpec{ + InternalVersionedTypedObject: runtime.NewInternalVersionedTypedObject[accspeccpi.AccessSpec](versions, Type), + Region: region, + Bucket: bucket, + Key: key, + Version: version, + MediaType: mediaType, + downloader: utils.Optional(downloader...), + } +} + +func (a AccessSpec) MarshalJSON() ([]byte, error) { + return runtime.MarshalVersionedTypedObject(&a) +} + +func (a *AccessSpec) Describe(ctx accspeccpi.Context) string { + return fmt.Sprintf("S3 key %s in bucket %s", a.Key, a.Bucket) +} + +func (_ *AccessSpec) IsLocal(accspeccpi.Context) bool { + return false +} + +func (a *AccessSpec) GlobalAccessSpec(ctx accspeccpi.Context) accspeccpi.AccessSpec { + return a +} + +func (a *AccessSpec) AccessMethod(c accspeccpi.ComponentVersionAccess) (accspeccpi.AccessMethod, error) { + return accspeccpi.AccessMethodForImplementation(newMethod(c, a)) +} + +//////////////////////////////////////////////////////////////////////////////// + +type accessMethod struct { + blobaccess.BlobAccess + + comp accspeccpi.ComponentVersionAccess + spec *AccessSpec +} + +var _ accspeccpi.AccessMethodImpl = (*accessMethod)(nil) + +func newMethod(c accspeccpi.ComponentVersionAccess, a *AccessSpec) (*accessMethod, error) { + creds, err := getCreds(a, c.GetContext().CredentialsContext()) + if err != nil { + return nil, fmt.Errorf("failed to get creds: %w", err) + } + + var ( + accessKeyID string + accessSecret string + ) + if creds != nil { + accessKeyID = creds.GetProperty(identity.ATTR_AWS_ACCESS_KEY_ID) + accessSecret = creds.GetProperty(identity.ATTR_AWS_SECRET_ACCESS_KEY) + } + var awsCreds *s3.AWSCreds + if accessKeyID != "" { + awsCreds = &s3.AWSCreds{ + AccessKeyID: accessKeyID, + AccessSecret: accessSecret, + } + } + d := a.downloader + if d == nil { + d = s3.NewDownloader(a.Region, a.Bucket, a.Key, a.Version, awsCreds) + } + w := accessio.NewWriteAtWriter(d.Download) + // don't change the spec, leave it empty. + mediaType := a.MediaType + if mediaType == "" { + mediaType = mime.MIME_OCTET + } + cacheBlobAccess := accessobj.CachedBlobAccessForWriter(c.GetContext(), mediaType, w) + return &accessMethod{ + spec: a, + comp: c, + BlobAccess: cacheBlobAccess, + }, nil +} + +func getCreds(a *AccessSpec, cctx credentials.Context) (credentials.Credentials, error) { + return identity.GetCredentials(cctx, "", a.Bucket, a.Key, a.Version) +} + +func (_ *accessMethod) IsLocal() bool { + return false +} + +func (m *accessMethod) GetKind() string { + return Type +} + +func (m *accessMethod) AccessSpec() accspeccpi.AccessSpec { + return m.spec +} + +func (m *accessMethod) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { + return identity.GetConsumerId("", m.spec.Bucket, m.spec.Key, m.spec.Version) +} + +func (m *accessMethod) GetIdentityMatcher() string { + return hostpath.IDENTITY_TYPE +} diff --git a/api/ocm/extensions/accessmethods/s3/method_test.go b/api/ocm/extensions/accessmethods/s3/method_test.go new file mode 100644 index 000000000..d8bd556aa --- /dev/null +++ b/api/ocm/extensions/accessmethods/s3/method_test.go @@ -0,0 +1,200 @@ +package s3_test + +import ( + "encoding/json" + "fmt" + "io" + "os" + "reflect" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + + "github.com/mandelsoft/filepath/pkg/filepath" + "github.com/mandelsoft/goutils/general" + "github.com/mandelsoft/vfs/pkg/osfs" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/datacontext/attrs/tmpcache" + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/s3" + "ocm.software/ocm/api/ocm/extensions/accessmethods/s3/identity" + "ocm.software/ocm/api/utils/accessio/downloader" +) + +type mockDownloader struct { + expected []byte + err error +} + +func (m *mockDownloader) Download(w io.WriterAt) error { + if _, err := w.WriteAt(m.expected, 0); err != nil { + return fmt.Errorf("failed to write to mock writer: %w", err) + } + return m.err +} + +func checkMarshal(spec *s3.AccessSpec, typ string, fmt string) { + if typ != "" { + spec.SetType(typ) + } + data := MustWithOffset(1, Calling(json.Marshal(spec))) + ExpectWithOffset(1, string(data)).To(Equal(fmt)) + + n := MustWithOffset(1, Calling(ocm.DefaultContext().AccessSpecForConfig(data, nil))) + Expect(reflect.TypeOf(n)).To(Equal(reflect.TypeOf(spec))) + Expect(n.GetType()).To(Equal(general.Conditional(typ == "", s3.Type, typ))) + data2 := Must(json.Marshal(n)) + ExpectWithOffset(1, string(data2)).To(StringEqualWithContext(string(data))) +} + +func checkDecode(spec *s3.AccessSpec, typ string, fmt string) { + if typ != "" { + spec.SetType(typ) + } + data := MustWithOffset(1, Calling(json.Marshal(spec))) + + n := MustWithOffset(1, Calling(s3.Versions().Decode([]byte(fmt), nil))) + Expect(reflect.TypeOf(n)).To(Equal(reflect.TypeOf(spec))) + + data2 := Must(json.Marshal(n)) + ExpectWithOffset(1, string(data2)).To(StringEqualWithContext(string(data))) +} + +var _ = Describe("Method", func() { + Context("specification", func() { + var spec *s3.AccessSpec + + BeforeEach(func() { + spec = s3.New( + "region", + "bucket", + "key", + "version", + "tar/gz", + ) + }) + + It("serializes", func() { + checkMarshal(spec, "", "{\"type\":\"s3\",\"region\":\"region\",\"bucket\":\"bucket\",\"key\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") + checkMarshal(spec, s3.TypeV1, "{\"type\":\"s3/v1\",\"region\":\"region\",\"bucket\":\"bucket\",\"key\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") + checkMarshal(spec, s3.TypeV2, "{\"type\":\"s3/v2\",\"region\":\"region\",\"bucketName\":\"bucket\",\"objectKey\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") + checkMarshal(spec, s3.LegacyType, "{\"type\":\"S3\",\"region\":\"region\",\"bucket\":\"bucket\",\"key\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") + checkMarshal(spec, s3.LegacyTypeV1, "{\"type\":\"S3/v1\",\"region\":\"region\",\"bucket\":\"bucket\",\"key\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") + }) + + It("deserializes versioned", func() { + checkDecode(spec, s3.TypeV1, "{\"type\":\"s3/v1\",\"region\":\"region\",\"bucket\":\"bucket\",\"key\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") + checkDecode(spec, s3.TypeV2, "{\"type\":\"s3/v2\",\"region\":\"region\",\"bucketName\":\"bucket\",\"objectKey\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") + + checkDecode(spec, s3.LegacyTypeV1, "{\"type\":\"S3/v1\",\"region\":\"region\",\"bucket\":\"bucket\",\"key\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") + checkDecode(spec, s3.LegacyTypeV2, "{\"type\":\"S3/v2\",\"region\":\"region\",\"bucketName\":\"bucket\",\"objectKey\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") + }) + + It("deserializes anonymous", func() { + checkDecode(spec, s3.Type, "{\"type\":\"s3\",\"region\":\"region\",\"bucket\":\"bucket\",\"key\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") + checkDecode(spec, s3.Type, "{\"type\":\"s3\",\"region\":\"region\",\"bucketName\":\"bucket\",\"objectKey\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") + + checkDecode(spec, s3.LegacyType, "{\"type\":\"S3\",\"region\":\"region\",\"bucket\":\"bucket\",\"key\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") + checkDecode(spec, s3.LegacyType, "{\"type\":\"S3\",\"region\":\"region\",\"bucketName\":\"bucket\",\"objectKey\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") + }) + }) + + Context("accessmethod", func() { + var ( + env *Builder + accessSpec *s3.AccessSpec + downloader downloader.Downloader + expectedContent []byte + err error + mcc ocm.Context + fs vfs.FileSystem + ctx datacontext.Context + ) + + BeforeEach(func() { + expectedContent, err = os.ReadFile(filepath.Join("testdata", "repo.tar.gz")) + Expect(err).ToNot(HaveOccurred()) + env = NewBuilder() + downloader = &mockDownloader{ + expected: expectedContent, + } + accessSpec = s3.New( + "region", + "bucket", + "key", + "version", + "tar/gz", + downloader, + ) + fs, err = osfs.NewTempFileSystem() + Expect(err).To(Succeed()) + ctx = datacontext.New(nil) + vfsattr.Set(ctx, fs) + tmpcache.Set(ctx, &tmpcache.Attribute{Path: "/tmp", Filesystem: fs}) + mcc = ocm.New(datacontext.MODE_INITIAL) + mcc.CredentialsContext().SetCredentialsForConsumer(credentials.ConsumerIdentity{credentials.ID_TYPE: identity.CONSUMER_TYPE}, credentials.DirectCredentials{ + "accessKeyID": "accessKeyID", + "accessSecret": "accessSecret", + }) + }) + + AfterEach(func() { + env.Cleanup() + vfs.Cleanup(fs) + }) + + It("provides comsumer id", func() { + m, err := accessSpec.AccessMethod(&cpi.DummyComponentVersionAccess{Context: env.OCMContext()}) + Expect(err).ToNot(HaveOccurred()) + Expect(credentials.GetProvidedConsumerId(m)).To(Equal(credentials.NewConsumerIdentity(identity.CONSUMER_TYPE, + identity.ID_PATHPREFIX, "bucket/key/version"))) + }) + + It("downloads s3 objects", func() { + m, err := accessSpec.AccessMethod(&mockComponentVersionAccess{context: mcc}) + Expect(err).ToNot(HaveOccurred()) + defer Close(m, "method") + blob, err := m.Get() + Expect(err).ToNot(HaveOccurred()) + Expect(blob).To(Equal(expectedContent)) + }) + + When("the downloader fails to download the bucket object", func() { + BeforeEach(func() { + downloader = &mockDownloader{ + err: fmt.Errorf("object not found"), + } + accessSpec = s3.New( + "region", + "bucket", + "key", + "version", + "tar/gz", + downloader, + ) + }) + It("errors", func() { + m, err := accessSpec.AccessMethod(&mockComponentVersionAccess{context: mcc}) + Expect(err).ToNot(HaveOccurred()) + _, err = m.Get() + Expect(err).To(MatchError(ContainSubstring("object not found"))) + }) + }) + }) +}) + +type mockComponentVersionAccess struct { + ocm.ComponentVersionAccess + context ocm.Context +} + +func (m *mockComponentVersionAccess) GetContext() ocm.Context { + return m.context +} diff --git a/pkg/contexts/ocm/accessmethods/s3/suite_test.go b/api/ocm/extensions/accessmethods/s3/suite_test.go similarity index 100% rename from pkg/contexts/ocm/accessmethods/s3/suite_test.go rename to api/ocm/extensions/accessmethods/s3/suite_test.go diff --git a/pkg/contexts/ocm/accessmethods/s3/testdata/repo.tar.gz b/api/ocm/extensions/accessmethods/s3/testdata/repo.tar.gz similarity index 100% rename from pkg/contexts/ocm/accessmethods/s3/testdata/repo.tar.gz rename to api/ocm/extensions/accessmethods/s3/testdata/repo.tar.gz diff --git a/pkg/contexts/ocm/accessmethods/s3/v1.go b/api/ocm/extensions/accessmethods/s3/v1.go similarity index 93% rename from pkg/contexts/ocm/accessmethods/s3/v1.go rename to api/ocm/extensions/accessmethods/s3/v1.go index 174c9a520..5fae5f556 100644 --- a/pkg/contexts/ocm/accessmethods/s3/v1.go +++ b/api/ocm/extensions/accessmethods/s3/v1.go @@ -3,9 +3,9 @@ package s3 import ( . "github.com/mandelsoft/goutils/exception" - "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/runtime" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/utils/runtime" ) const TypeV1 = Type + runtime.VersionSeparator + "v1" diff --git a/pkg/contexts/ocm/accessmethods/s3/v2.go b/api/ocm/extensions/accessmethods/s3/v2.go similarity index 93% rename from pkg/contexts/ocm/accessmethods/s3/v2.go rename to api/ocm/extensions/accessmethods/s3/v2.go index 84a377d68..fa13cfb7e 100644 --- a/pkg/contexts/ocm/accessmethods/s3/v2.go +++ b/api/ocm/extensions/accessmethods/s3/v2.go @@ -3,9 +3,9 @@ package s3 import ( . "github.com/mandelsoft/goutils/exception" - "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/runtime" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/utils/runtime" ) const TypeV2 = Type + runtime.VersionSeparator + "v2" diff --git a/api/ocm/extensions/accessmethods/wget/cli.go b/api/ocm/extensions/accessmethods/wget/cli.go new file mode 100644 index 000000000..3badac96a --- /dev/null +++ b/api/ocm/extensions/accessmethods/wget/cli.go @@ -0,0 +1,71 @@ +package wget + +import ( + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/api/utils/mime" +) + +func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { + return flagsets.NewConfigOptionTypeSetHandler( + Type, AddConfig, + options.URLOption, + options.MediatypeOption, + options.HTTPHeaderOption, + options.HTTPVerbOption, + options.HTTPBodyOption, + options.HTTPRedirectOption, + ) +} + +func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { + flagsets.AddFieldByOptionP(opts, options.URLOption, config, "url") + flagsets.AddFieldByOptionP(opts, options.MediatypeOption, config, "mediaType") + flagsets.AddFieldByOptionP(opts, options.HTTPHeaderOption, config, "header") + flagsets.AddFieldByOptionP(opts, options.HTTPVerbOption, config, "verb") + flagsets.AddFieldByOptionP(opts, options.HTTPBodyOption, config, "body") + flagsets.AddFieldByOptionP(opts, options.HTTPRedirectOption, config, "noredirect") + return nil +} + +var usage = ` +This method implements access to resources stored on an http server. +` + +var formatV1 = ` +The url is the url pointing to the http endpoint from which a resource is +downloaded. The mimeType can be used to specify the MIME type of the +resource. + +This blob type specification supports the following fields: +- **url** *string* + +This REQUIRED property describes the url from which the resource is to be +downloaded. + +- **mediaType** *string* + +This OPTIONAL property describes the media type of the resource to be +downloaded. If omitted, ocm tries to read the mediaType from the Content-Type header +of the http response. If the mediaType cannot be set from the Content-Type header as well, +ocm tries to deduct the mediaType from the URL. If that is not possible either, the default +media type is defaulted to ` + mime.MIME_OCTET + `. + +- **header** *map[string][]string* + +This OPTIONAL property describes the http headers to be set in the http request to the server. + +- **verb** *string* + +This OPTIONAL property describes the http verb (also known as http request method) for the http +request. If omitted, the http verb is defaulted to GET. + +- **body** *[]byte* + +This OPTIONAL property describes the http body to be included in the request. + +- **noredirect** *bool* + +This OPTIONAL property describes whether http redirects should be disabled. If omitted, +it is defaulted to false (so, per default, redirects are enabled). +` diff --git a/api/ocm/extensions/accessmethods/wget/logging.go b/api/ocm/extensions/accessmethods/wget/logging.go new file mode 100644 index 000000000..d78ec6a41 --- /dev/null +++ b/api/ocm/extensions/accessmethods/wget/logging.go @@ -0,0 +1,18 @@ +package wget + +import ( + "github.com/mandelsoft/logging" + + "ocm.software/ocm/api/ocm/cpi" + ocmlog "ocm.software/ocm/api/utils/logging" +) + +var REALM = ocmlog.DefineSubRealm("access method for wget", "accessmethod/wget") + +type ContextProvider interface { + GetContext() cpi.Context +} + +func Logger(c ContextProvider, keyValuePairs ...interface{}) logging.Logger { + return c.GetContext().Logger(REALM).WithValues(keyValuePairs...) +} diff --git a/api/ocm/extensions/accessmethods/wget/method.go b/api/ocm/extensions/accessmethods/wget/method.go new file mode 100644 index 000000000..c30b1992b --- /dev/null +++ b/api/ocm/extensions/accessmethods/wget/method.go @@ -0,0 +1,176 @@ +package wget + +import ( + "fmt" + "io" + "sync" + + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/builtin/wget/identity" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/wget" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/api/utils/runtime" +) + +// Type is the access type for a blob on an http server . +const ( + Type = "wget" + 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()))) +} + +func Is(spec accspeccpi.AccessSpec) bool { + return spec != nil && spec.GetKind() == Type +} + +// New creates a new WGET accessor for http resources. +func New(url string, opts ...Option) *AccessSpec { + eff := optionutils.EvalOptions(opts...) + + return &AccessSpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(Type), + URL: url, + MediaType: eff.MimeType, + Header: eff.Header, + Verb: eff.Verb, + Body: eff.Body, + NoRedirect: optionutils.AsValue(eff.NoRedirect), + } +} + +// AccessSpec describes the access for files on HTTP and HTTPS servers. +type AccessSpec struct { + runtime.ObjectVersionedType `json:",inline"` + + // URLs to the files on a server + URL string `json:"URL"` + // MediaType is the media type of the object represented by the blob + MediaType string `json:"mediaType"` + // Header to be passed in the http request + Header map[string][]string `json:"header"` + // Verb is the http verb to be used for the request + Verb string `json:"verb"` + // Body is the body to be included in the http request + Body io.Reader `json:"body"` + // NoRedirect allows to disable redirects + NoRedirect bool `json:"noRedirect"` +} + +var _ accspeccpi.AccessSpec = (*AccessSpec)(nil) + +func (a *AccessSpec) Describe(ctx accspeccpi.Context) string { + return fmt.Sprintf("Files from %s", a.URL) +} + +func (a *AccessSpec) IsLocal(ctx accspeccpi.Context) bool { + return false +} + +func (a *AccessSpec) GlobalAccessSpec(ctx accspeccpi.Context) accspeccpi.AccessSpec { + return a +} + +func (a *AccessSpec) AccessMethod(access accspeccpi.ComponentVersionAccess) (accspeccpi.AccessMethod, error) { + return accspeccpi.AccessMethodForImplementation(&accessMethod{comp: access, spec: a}, nil) +} + +/////////////////// + +func (a *AccessSpec) GetURL() string { + return a.URL +} + +//////////////////////////////////////////////////////////////////////////////// + +type accessMethod struct { + lock sync.Mutex + blob blobaccess.BlobAccess + comp accspeccpi.ComponentVersionAccess + spec *AccessSpec +} + +var _ accspeccpi.AccessMethodImpl = (*accessMethod)(nil) + +func (_ *accessMethod) IsLocal() bool { + return false +} + +func (m *accessMethod) GetKind() string { + return Type +} + +func (m *accessMethod) AccessSpec() accspeccpi.AccessSpec { + return m.spec +} + +func (m *accessMethod) Get() ([]byte, error) { + return blobaccess.BlobData(m.getBlob()) +} + +func (m *accessMethod) Reader() (io.ReadCloser, error) { + return blobaccess.BlobReader(m.getBlob()) +} + +func (m *accessMethod) MimeType() string { + if m.spec.MediaType != "" { + return m.spec.MediaType + } + blob, err := m.getBlob() + if err != nil { + return mime.MIME_OCTET + } + return blob.MimeType() +} + +func (m *accessMethod) getBlob() (blobaccess.BlobAccess, error) { + m.lock.Lock() + defer m.lock.Unlock() + + if m.blob != nil { + return m.blob, nil + } + + blob, err := wget.BlobAccess(m.spec.URL, + wget.WithMimeType(m.spec.MediaType), + wget.WithCredentialContext(m.comp.GetContext()), + wget.WithLoggingContext(m.comp.GetContext()), + wget.WithHeader(m.spec.Header), + wget.WithVerb(m.spec.Verb), + wget.WithBody(m.spec.Body), + wget.WithNoRedirect(m.spec.NoRedirect)) + if err != nil { + return nil, err + } + + m.blob = blob + return m.blob, nil +} + +func (m *accessMethod) Close() error { + m.lock.Lock() + defer m.lock.Unlock() + + var err error + if m.blob != nil { + err = m.blob.Close() + m.blob = nil + } + + return err +} + +func (m *accessMethod) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { + return identity.GetConsumerId(m.spec.URL) +} + +func (m *accessMethod) GetIdentityMatcher() string { + return identity.CONSUMER_TYPE +} diff --git a/api/ocm/extensions/accessmethods/wget/method_test.go b/api/ocm/extensions/accessmethods/wget/method_test.go new file mode 100644 index 000000000..282c5ba8d --- /dev/null +++ b/api/ocm/extensions/accessmethods/wget/method_test.go @@ -0,0 +1,409 @@ +package wget_test + +import ( + "bytes" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "fmt" + "io" + "net/http" + "strings" + "time" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/ocm/extensions/accessmethods/wget" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/builtin/wget/identity" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/api/tech/signing/signutils" + "ocm.software/ocm/api/utils/mime" +) + +var ( + caCert *x509.Certificate + caPriv *rsa.PrivateKey + caPub *rsa.PublicKey + caPEM []byte + + serverCert *x509.Certificate + serverPriv *rsa.PrivateKey + serverPub *rsa.PublicKey + serverPEM []byte + + httpsServerClientAuth http.Server + httpsServer http.Server + httpServer http.Server +) + +const ( + HTTP_PORT = ":18080" + HTTPS_PORT = ":1443" + HTTPS_PORT_WITH_CLIENT_AUTH = ":2443" + + HTTP_HOST = "http://localhost" + HTTP_PORT + HTTPS_HOST = "https://localhost" + HTTPS_PORT + HTTPS_HOST_WITH_CLIENT_AUTH = "https://localhost" + HTTPS_PORT_WITH_CLIENT_AUTH + + TO_MEMORY = "/tomemory" + TO_FILE = "/tofile" + BASIC_LOGIN = "/basic-login" + BEARER_LOGIN = "/bearer-login" + ECHO_HEADERS = "/headers" + ECHO_BODY = "/body" + ECHO_METHOD = "/method" + CONTENT_TYPE = " /content-type" + DOT_EXT = "/somefile.tar" + REDIRECT = "/redirect" + + USERNAME = "user" + PASSWORD = "password" + TOKEN = "token" + + CONTENT = "hello world" + NOREDIRECT_CONTENT = "noredirect" +) + +var _ = BeforeSuite(func() { + // setup certificate authority + _capriv, _capub := Must2(rsa.Handler{}.CreateKeyPair()) + caPriv = _capriv.(*rsa.PrivateKey) + caPub = _capub.(*rsa.PublicKey) + + caSpec := &signutils.Specification{ + Subject: *signutils.CommonName("caCert-authority"), + Validity: 10 * time.Minute, + CAPrivateKey: caPriv, + IsCA: true, + Usages: []interface{}{x509.KeyUsageDigitalSignature}, + } + + caCert, caPEM = Must2(signutils.CreateCertificate(caSpec)) + + // use certificate authority to create httpsServer certificate + _serverPriv, _serverPub := Must2(rsa.Handler{}.CreateKeyPair()) + serverPriv = _serverPriv.(*rsa.PrivateKey) + serverPub = _serverPub.(*rsa.PublicKey) + + serverSpec := &signutils.Specification{ + IsCA: false, + Subject: pkix.Name{CommonName: "localhost"}, + Validity: 10 * time.Minute, + RootCAs: caCert, + CAChain: caCert, + CAPrivateKey: caPriv, + PublicKey: serverPub, + Usages: []interface{}{x509.ExtKeyUsageServerAuth}, + Hosts: []string{"localhost", "127.0.0.1"}, + } + + serverCert, serverPEM = Must2(signutils.CreateCertificate(serverSpec)) + + // setup tls configuration for the httpsServer for https with the corresponding certs and keys + serverPrivPEM := Must(rsa.KeyData(serverPriv)) + serverTlsCert := Must(tls.X509KeyPair(serverPEM, serverPrivPEM)) + + // ca's used by the server to validate client certificates + clientCaCertPool := x509.NewCertPool() + clientCaCertPool.AddCert(caCert) + + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{serverTlsCert}, + } + + tlsConfigClientAuth := &tls.Config{ + Certificates: []tls.Certificate{serverTlsCert}, + ClientAuth: tls.RequireAndVerifyClientCert, + ClientCAs: clientCaCertPool, + } + + // configure test routes + mux := http.NewServeMux() + mux.HandleFunc(TO_MEMORY, func(writer http.ResponseWriter, request *http.Request) { + n, err := writer.Write([]byte(CONTENT)) + _, _ = n, err + }) + mux.HandleFunc(BASIC_LOGIN, func(writer http.ResponseWriter, request *http.Request) { + username, password, ok := request.BasicAuth() + if !ok { + n, err := writer.Write([]byte(`failure`)) + _, _ = n, err + } + if username != "" && password != "" { + res := fmt.Sprintf("%s:%s", username, password) + n, err := writer.Write([]byte(res)) + _, _ = n, err + } else { + n, err := writer.Write([]byte(`failure`)) + _, _ = n, err + } + }) + mux.HandleFunc(BEARER_LOGIN, func(writer http.ResponseWriter, request *http.Request) { + auth := request.Header.Get("Authorization") + if auth == "" { + n, err := writer.Write([]byte(`failure`)) + _, _ = n, err + } else { + bearer, ok := strings.CutPrefix(auth, "Bearer ") + if !ok { + n, err := writer.Write([]byte(`failure`)) + _, _ = n, err + } else { + n, err := writer.Write([]byte(bearer)) + _, _ = n, err + } + } + }) + mux.HandleFunc(ECHO_HEADERS, func(writer http.ResponseWriter, request *http.Request) { + err := request.Header.Write(writer) + _ = err + }) + mux.HandleFunc(ECHO_BODY, func(writer http.ResponseWriter, request *http.Request) { + b, err := io.ReadAll(request.Body) + _, err = writer.Write(b) + _ = err + }) + mux.HandleFunc(ECHO_METHOD, func(writer http.ResponseWriter, request *http.Request) { + _, err := writer.Write([]byte(request.Method)) + _ = err + }) + mux.HandleFunc(CONTENT_TYPE, func(writer http.ResponseWriter, request *http.Request) { + writer.Header().Set("Content-Type", mime.MIME_TEXT) + }) + mux.HandleFunc(DOT_EXT, func(writer http.ResponseWriter, request *http.Request) {}) + mux.HandleFunc(REDIRECT, func(writer http.ResponseWriter, request *http.Request) { + writer.Header().Set("Location", TO_MEMORY) + writer.WriteHeader(307) + writer.Write([]byte(NOREDIRECT_CONTENT)) + }) + + // setup an https and an http httpsServer + httpsServerClientAuth := &http.Server{ + Addr: HTTPS_PORT_WITH_CLIENT_AUTH, + TLSConfig: tlsConfigClientAuth, + Handler: mux, + } + + httpsServer := &http.Server{ + Addr: HTTPS_PORT, + TLSConfig: tlsConfig, + Handler: mux, + } + + httpServer := &http.Server{ + Addr: HTTP_PORT, + Handler: mux, + } + + go func() { + MustBeSuccessful(httpsServerClientAuth.ListenAndServeTLS("", "")) + }() + + go func() { + MustBeSuccessful(httpsServer.ListenAndServeTLS("", "")) + }() + + go func() { + MustBeSuccessful(httpServer.ListenAndServe()) + }() +}) + +var _ = AfterSuite(func() { + MustBeSuccessful(httpsServerClientAuth.Close()) + MustBeSuccessful(httpsServer.Close()) + MustBeSuccessful(httpServer.Close()) +}) + +var _ = Describe("wget access method", func() { + It("access content on http server", func() { + url := HTTP_HOST + TO_MEMORY + spec := New(url) + + ctx := ocm.DefaultContext() + m := Must(spec.AccessMethod(&cpi.DummyComponentVersionAccess{ctx})) + defer Close(m, "method") + + b := Must(m.Get()) + Expect(string(b)).To(Equal(CONTENT)) + }) + + It("access content on https server", func() { + url := HTTPS_HOST + TO_MEMORY + spec := New(url) + + ctx := ocm.DefaultContext() + ctx.CredentialsContext().SetCredentialsForConsumer(identity.GetConsumerId(url), credentials.DirectCredentials{ + identity.ATTR_CERTIFICATE_AUTHORITY: string(caPEM), + }) + m := Must(spec.AccessMethod(&cpi.DummyComponentVersionAccess{ctx})) + defer Close(m, "method") + + b := Must(m.Get()) + Expect(string(b)).To(Equal(CONTENT)) + }) + + It("access content on https server with client authentication", func() { + // create a client certificate + _clientPriv, _clientPub := Must2(rsa.Handler{}.CreateKeyPair()) + clientPriv := _clientPriv.(*rsa.PrivateKey) + clientPrivData := Must(rsa.KeyData(clientPriv)) + clientPub := _clientPub.(*rsa.PublicKey) + + clientSpec := &signutils.Specification{ + IsCA: false, + Subject: pkix.Name{CommonName: "localhost"}, + Validity: 10 * time.Minute, + RootCAs: caCert, + CAChain: caCert, + CAPrivateKey: caPriv, + PublicKey: clientPub, + Usages: []interface{}{x509.ExtKeyUsageClientAuth}, + Hosts: []string{"localhost", "127.0.0.1"}, + } + + _, clientPEM := Must2(signutils.CreateCertificate(clientSpec)) + + // Request + url := HTTPS_HOST_WITH_CLIENT_AUTH + TO_MEMORY + spec := New(url) + + ctx := ocm.DefaultContext() + ctx.CredentialsContext().SetCredentialsForConsumer(identity.GetConsumerId(url), credentials.DirectCredentials{ + identity.ATTR_CERTIFICATE_AUTHORITY: string(caPEM), + identity.ATTR_CERTIFICATE: string(clientPEM), + identity.ATTR_PRIVATE_KEY: string(clientPrivData), + }) + m := Must(spec.AccessMethod(&cpi.DummyComponentVersionAccess{ctx})) + defer Close(m, "method") + + b := Must(m.Get()) + Expect(string(b)).To(Equal(CONTENT)) + }) + + It("check that username and password are passed correctly", func() { + url := HTTP_HOST + BASIC_LOGIN + spec := New(url) + + ctx := ocm.DefaultContext() + ctx.CredentialsContext().SetCredentialsForConsumer(identity.GetConsumerId(url), credentials.DirectCredentials{ + identity.ATTR_USERNAME: USERNAME, + identity.ATTR_PASSWORD: PASSWORD, + }) + m := Must(spec.AccessMethod(&cpi.DummyComponentVersionAccess{ctx})) + defer Close(m, "method") + + b := Must(m.Get()) + Expect(string(b)).To(Equal(USERNAME + ":" + PASSWORD)) + }) + + It("check that bearer token is passed correctly", func() { + url := HTTP_HOST + BEARER_LOGIN + spec := New(url) + + ctx := ocm.DefaultContext() + ctx.CredentialsContext().SetCredentialsForConsumer(identity.GetConsumerId(url), credentials.DirectCredentials{ + identity.ATTR_IDENTITY_TOKEN: TOKEN, + }) + m := Must(spec.AccessMethod(&cpi.DummyComponentVersionAccess{ctx})) + defer Close(m, "method") + + b := Must(m.Get()) + Expect(string(b)).To(Equal(TOKEN)) + }) + + It("check that basic auth is merged correctly with other provided headers", func() { + url := HTTP_HOST + ECHO_HEADERS + headers := map[string][]string{"Content-Type": {"text/plain"}} + spec := New(url, WithHeader(headers)) + + ctx := ocm.DefaultContext() + ctx.CredentialsContext().SetCredentialsForConsumer(identity.GetConsumerId(url), credentials.DirectCredentials{ + identity.ATTR_USERNAME: USERNAME, + identity.ATTR_PASSWORD: PASSWORD, + }) + m := Must(spec.AccessMethod(&cpi.DummyComponentVersionAccess{ctx})) + defer Close(m, "method") + + b := Must(m.Get()) + + Expect(strings.Contains(string(b), "Content-Type: text/plain")).To(BeTrue()) + Expect(strings.Contains(string(b), "Authorization: Basic")).To(BeTrue()) + }) + + It("check detect mime type based on content-type response header", func() { + url := HTTP_HOST + ECHO_HEADERS + spec := New(url) + + ctx := ocm.DefaultContext() + m := Must(spec.AccessMethod(&cpi.DummyComponentVersionAccess{ctx})) + defer Close(m, "method") + + Expect(m.MimeType()).To(Equal(mime.MIME_TEXT)) + }) + + It("check deduction of mime type based on url", func() { + url := HTTP_HOST + DOT_EXT + spec := New(url) + + ctx := ocm.DefaultContext() + m := Must(spec.AccessMethod(&cpi.DummyComponentVersionAccess{ctx})) + defer Close(m, "method") + + Expect(m.MimeType()).To(Equal("application/x-tar")) + }) + + It("check passing an http body", func() { + url := HTTP_HOST + ECHO_BODY + + content := `hello world` + spec := New(url, WithBody(bytes.NewReader([]byte(content)))) + + ctx := ocm.DefaultContext() + m := Must(spec.AccessMethod(&cpi.DummyComponentVersionAccess{ctx})) + defer Close(m, "method") + + b := Must(m.Get()) + + Expect(string(b)).To(Equal(content)) + }) + + It("check passing an http verb", func() { + url := HTTP_HOST + ECHO_METHOD + + method := http.MethodPost + spec := New(url, WithVerb(method)) + + ctx := ocm.DefaultContext() + m := Must(spec.AccessMethod(&cpi.DummyComponentVersionAccess{ctx})) + defer Close(m, "method") + + b := Must(m.Get()) + + Expect(string(b)).To(Equal(method)) + }) + + It("check noredirect behavior", func() { + url := HTTP_HOST + REDIRECT + + redirectSpec := New(url, WithNoRedirect(false)) + noredirectSpec := New(url, WithNoRedirect(true)) + + ctx := ocm.DefaultContext() + redirectMethod := Must(redirectSpec.AccessMethod(&cpi.DummyComponentVersionAccess{ctx})) + defer Close(redirectMethod, "redirectmethod") + + noredirectMethod := Must(noredirectSpec.AccessMethod(&cpi.DummyComponentVersionAccess{ctx})) + defer Close(noredirectMethod, "noredirectmethod") + + redirectContent := Must(redirectMethod.Get()) + Expect(string(redirectContent)).To(Equal(CONTENT)) + + noredirectContent := Must(noredirectMethod.Get()) + Expect(string(noredirectContent)).To(Equal(NOREDIRECT_CONTENT)) + }) +}) diff --git a/api/ocm/extensions/accessmethods/wget/options.go b/api/ocm/extensions/accessmethods/wget/options.go new file mode 100644 index 000000000..fd0cf46f7 --- /dev/null +++ b/api/ocm/extensions/accessmethods/wget/options.go @@ -0,0 +1,33 @@ +package wget + +import ( + "io" + "net/http" + + "ocm.software/ocm/api/utils/blobaccess/wget" +) + +type ( + Options = wget.Options + Option = wget.Option +) + +func WithMimeType(mime string) Option { + return wget.WithMimeType(mime) +} + +func WithHeader(h http.Header) Option { + return wget.WithHeader(h) +} + +func WithVerb(v string) Option { + return wget.WithVerb(v) +} + +func WithBody(v io.Reader) Option { + return wget.WithBody(v) +} + +func WithNoRedirect(r ...bool) Option { + return wget.WithNoRedirect(r...) +} diff --git a/pkg/contexts/ocm/accessmethods/wget/suite_test.go b/api/ocm/extensions/accessmethods/wget/suite_test.go similarity index 100% rename from pkg/contexts/ocm/accessmethods/wget/suite_test.go rename to api/ocm/extensions/accessmethods/wget/suite_test.go diff --git a/api/ocm/extensions/actionhandler/init.go b/api/ocm/extensions/actionhandler/init.go new file mode 100644 index 000000000..cc3c41922 --- /dev/null +++ b/api/ocm/extensions/actionhandler/init.go @@ -0,0 +1,5 @@ +package actionhandler + +import ( + _ "ocm.software/ocm/api/ocm/extensions/actionhandler/plugin" +) diff --git a/api/ocm/extensions/actionhandler/plugin/action_test.go b/api/ocm/extensions/actionhandler/plugin/action_test.go new file mode 100644 index 000000000..f9158954f --- /dev/null +++ b/api/ocm/extensions/actionhandler/plugin/action_test.go @@ -0,0 +1,87 @@ +//go:build unix + +package plugin_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + . "ocm.software/ocm/api/ocm/plugin/testutils" + + "ocm.software/ocm/api/datacontext/action/handlers" + oci_repository_prepare "ocm.software/ocm/api/oci/extensions/actions/oci-repository-prepare" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/actionhandler/plugin" + "ocm.software/ocm/api/ocm/plugin/plugins" + "ocm.software/ocm/api/ocm/plugin/registration" +) + +const PLUGIN = "test" + +var _ = Describe("plugin action handler", func() { + var ctx ocm.Context + var registry plugins.Set + var env *Builder + var plugins TempPluginDir + + BeforeEach(func() { + env = NewBuilder(nil) + ctx = env.OCMContext() + plugins, registry = Must2(ConfigureTestPlugins2(env, "testdata")) + p := registry.Get("action") + Expect(p).NotTo(BeNil()) + }) + + AfterEach(func() { + plugins.Cleanup() + env.Cleanup() + }) + + It("executes with no plugin registration", func() { + result := Must(oci_repository_prepare.Execute(ctx.GetActions(), "ghcr.io", "mandelsoft", nil)) + Expect(result).To(BeNil()) + }) + + It("executes with no handler", func() { + registration.RegisterExtensions(ctx) + result := Must(oci_repository_prepare.Execute(ctx.GetActions(), "mandelsoft.org", "mandelsoft", nil)) + Expect(result).To(BeNil()) + }) + + It("used default registration", func() { + registration.RegisterExtensions(ctx) + opts := handlers.NewOptions(handlers.ForAction(oci_repository_prepare.Type), handlers.ForAction("test"), handlers.ForSelectors("mandelsoft.org")) + MustFailWithMessage(plugin.RegisterActionHandler(ctx.AttributesContext(), "action", ctx, opts), "action \"test\" is unknown for plugin action") + }) + + It("uses default registration", func() { + registration.RegisterExtensions(ctx) + result := Must(oci_repository_prepare.Execute(ctx.GetActions(), "ghcr.io", "mandelsoft", nil)) + Expect(result).NotTo(BeNil()) + Expect(result.Message).To(Equal("all good")) + }) + + It("uses default pattern registration", func() { + registration.RegisterExtensions(ctx) + result := Must(oci_repository_prepare.Execute(ctx.GetActions(), "xyz.dkr.ecr.us-west-2.amazonaws.com", "mandelsoft", nil)) + Expect(result).NotTo(BeNil()) + Expect(result.Message).To(Equal("all good")) + }) + + It("executes action for dynamic registration", func() { + registration.RegisterExtensions(ctx) + opts := handlers.NewOptions(handlers.ForAction(oci_repository_prepare.Type), handlers.ForAction(oci_repository_prepare.Type), handlers.ForSelectors("mandelsoft.org")) + MustBeSuccessful(plugin.RegisterActionHandler(ctx.AttributesContext(), "action", ctx, opts)) + + result := Must(oci_repository_prepare.Execute(ctx.GetActions(), "mandelsoft.org", "mandelsoft", nil)) + Expect(result.Message).To(Equal("all good")) + }) + + It("executed action after abstract registration", func() { + registration.RegisterExtensions(ctx) + opts := handlers.NewOptions(handlers.ForAction(oci_repository_prepare.Type), handlers.ForAction(oci_repository_prepare.Type), handlers.ForSelectors("mandelsoft.org")) + ok := Must(ctx.GetActions().RegisterByName("plugin/action", ctx.OCIContext(), ctx, opts)) + Expect(ok).To(BeTrue()) + }) +}) diff --git a/pkg/contexts/ocm/actionhandler/plugin/actionhandler.go b/api/ocm/extensions/actionhandler/plugin/actionhandler.go similarity index 76% rename from pkg/contexts/ocm/actionhandler/plugin/actionhandler.go rename to api/ocm/extensions/actionhandler/plugin/actionhandler.go index 453137c5e..b2127bcfa 100644 --- a/pkg/contexts/ocm/actionhandler/plugin/actionhandler.go +++ b/api/ocm/extensions/actionhandler/plugin/actionhandler.go @@ -5,10 +5,10 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/action" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/action/handlers" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin" + "ocm.software/ocm/api/datacontext/action" + "ocm.software/ocm/api/datacontext/action/handlers" + "ocm.software/ocm/api/ocm/plugin" + common "ocm.software/ocm/api/utils/misc" ) // pluginHandler delegates action to a plugin based handler. diff --git a/api/ocm/extensions/actionhandler/plugin/registration.go b/api/ocm/extensions/actionhandler/plugin/registration.go new file mode 100644 index 000000000..8aa4b9728 --- /dev/null +++ b/api/ocm/extensions/actionhandler/plugin/registration.go @@ -0,0 +1,92 @@ +package plugin + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/datacontext/action/handlers" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/attrs/plugincacheattr" + "ocm.software/ocm/api/ocm/plugin" + "ocm.software/ocm/api/utils/registrations" +) + +func init() { + handlers.DefaultRegistry().RegisterRegistrationHandler("plugin", &RegistrationHandler{}) +} + +type RegistrationHandler struct{} + +var _ handlers.HandlerRegistrationHandler = (*RegistrationHandler)(nil) + +func (r *RegistrationHandler) RegisterByName(handler string, target handlers.Target, config handlers.HandlerConfig, olist ...handlers.Option) (bool, error) { + path := cpi.NewNamePath(handler) + + if config == nil { + return true, fmt.Errorf("config required") + } + + ctx, ok := config.(cpi.Context) + if !ok { + return true, fmt.Errorf("expected ocm.Context as config but found: %T", config) + } + if len(path) != 1 { + return true, fmt.Errorf("plugin handler must be of the form ") + } + + opts := handlers.NewOptions(olist...) + name := path[0] + err := RegisterActionHandler(target, name, ctx, opts) + return true, err +} + +func RegisterActionHandler(target handlers.Target, pname string, ctx ocm.Context, opts *handlers.Options) error { + set := plugincacheattr.Get(ctx) + if set == nil { + return errors.ErrUnknown(plugin.KIND_PLUGIN, pname) + } + + p := set.Get(pname) + if p == nil { + return errors.ErrUnknown(plugin.KIND_PLUGIN, pname) + } + + h, err := New(p, opts.Action) + if err != nil { + return err + } + return target.GetActions().Register(h, opts) +} + +func (r *RegistrationHandler) GetHandlers(target handlers.Target) registrations.HandlerInfos { + infos := registrations.HandlerInfos{} + + ctx := ocm.DefaultContext() + if c, ok := target.(ocm.ContextProvider); ok { + ctx = c.OCMContext() + } + + set := plugincacheattr.Get(ctx) + if set == nil { + return infos + } + + for _, name := range set.PluginNames() { + for _, a := range set.Get(name).GetDescriptor().Actions { + d := target.GetActions().GetActionTypes().GetAction(a.GetName()) + short := "" + if d != nil { + short = d.Description() + } + i := registrations.HandlerInfo{ + Name: name + "/" + a.GetName(), + ShortDesc: short, + Description: a.GetDescription(), + } + infos = append(infos, i) + } + } + return infos +} diff --git a/pkg/contexts/ocm/actionhandler/plugin/suite_test.go b/api/ocm/extensions/actionhandler/plugin/suite_test.go similarity index 100% rename from pkg/contexts/ocm/actionhandler/plugin/suite_test.go rename to api/ocm/extensions/actionhandler/plugin/suite_test.go diff --git a/pkg/contexts/ocm/actionhandler/plugin/testdata/action b/api/ocm/extensions/actionhandler/plugin/testdata/action similarity index 100% rename from pkg/contexts/ocm/actionhandler/plugin/testdata/action rename to api/ocm/extensions/actionhandler/plugin/testdata/action diff --git a/pkg/contexts/ocm/resourcetypes/const.go b/api/ocm/extensions/artifacttypes/const.go similarity index 100% rename from pkg/contexts/ocm/resourcetypes/const.go rename to api/ocm/extensions/artifacttypes/const.go diff --git a/api/ocm/extensions/attrs/compatattr/attr.go b/api/ocm/extensions/attrs/compatattr/attr.go new file mode 100644 index 000000000..63e42fc6e --- /dev/null +++ b/api/ocm/extensions/attrs/compatattr/attr.go @@ -0,0 +1,57 @@ +package compatattr + +import ( + "fmt" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ATTR_KEY = "github.com/mandelsoft/ocm/compat" + ATTR_SHORT = "compat" +) + +func init() { + datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) +} + +type AttributeType struct{} + +func (a AttributeType) Name() string { + return ATTR_KEY +} + +func (a AttributeType) Description() string { + return ` +*bool* +Compatibility mode: Avoid generic local access methods and prefer type specific ones. +` +} + +func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { + if _, ok := v.(bool); !ok { + return nil, fmt.Errorf("boolean required") + } + return marshaller.Marshal(v) +} + +func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { + var value bool + err := unmarshaller.Unmarshal(data, &value) + return value, err +} + +//////////////////////////////////////////////////////////////////////////////// + +func Get(ctx datacontext.Context) bool { + a := ctx.GetAttributes().GetAttribute(ATTR_KEY) + if a == nil { + return false + } + return a.(bool) +} + +func Set(ctx datacontext.Context, flag bool) error { + return ctx.GetAttributes().SetAttribute(ATTR_KEY, flag) +} diff --git a/api/ocm/extensions/attrs/compatattr/attr_test.go b/api/ocm/extensions/attrs/compatattr/attr_test.go new file mode 100644 index 000000000..f16fa8452 --- /dev/null +++ b/api/ocm/extensions/attrs/compatattr/attr_test.go @@ -0,0 +1,41 @@ +package compatattr_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/ocm" + me "ocm.software/ocm/api/ocm/extensions/attrs/compatattr" + "ocm.software/ocm/api/utils/runtime" +) + +var _ = Describe("attribute", func() { + var ctx ocm.Context + var cfgctx config.Context + + BeforeEach(func() { + cfgctx = config.WithSharedAttributes(datacontext.New(nil)).New() + credctx := credentials.WithConfigs(cfgctx).New() + ocictx := oci.WithCredentials(credctx).New() + ctx = ocm.WithOCIRepositories(ocictx).New() + }) + It("local setting", func() { + Expect(me.Get(ctx)).To(BeFalse()) + Expect(me.Set(ctx, true)).To(Succeed()) + Expect(me.Get(ctx)).To(BeTrue()) + }) + + It("global setting", func() { + Expect(me.Get(cfgctx)).To(BeFalse()) + Expect(me.Set(ctx, true)).To(Succeed()) + Expect(me.Get(ctx)).To(BeTrue()) + }) + + It("parses string", func() { + Expect(me.AttributeType{}.Decode([]byte("true"), runtime.DefaultJSONEncoding)).To(BeTrue()) + }) +}) diff --git a/pkg/contexts/ocm/attrs/compatattr/suite_test.go b/api/ocm/extensions/attrs/compatattr/suite_test.go similarity index 100% rename from pkg/contexts/ocm/attrs/compatattr/suite_test.go rename to api/ocm/extensions/attrs/compatattr/suite_test.go diff --git a/api/ocm/extensions/attrs/compositionmodeattr/attr.go b/api/ocm/extensions/attrs/compositionmodeattr/attr.go new file mode 100644 index 000000000..c50fdd801 --- /dev/null +++ b/api/ocm/extensions/attrs/compositionmodeattr/attr.go @@ -0,0 +1,70 @@ +package compositionmodeattr + +import ( + "fmt" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/utils/runtime" +) + +// UseCompositionMode enables the support of the new Composition mode for +// Component versions. It disabled the direct write-through and update-on-close +// to the underlying repository. Instead, an explicit call to AddVersion call +// s required to persist a composed change on a new as well as queried +// component version object. +const UseCompositionMode = false + +const ( + ATTR_KEY = "ocm.software/compositionmode" + ATTR_SHORT = "compositionmode" +) + +func init() { + datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) +} + +type AttributeType struct{} + +func (a AttributeType) Name() string { + return ATTR_KEY +} + +func (a AttributeType) Description() string { + return ` +*bool* (default: ` + fmt.Sprintf("%t", UseCompositionMode) + ` +Composition mode decouples a component version provided by a repository +implemention from the backened persistence. Added local blobs will +and other changes witll not be forwarded to the backend repository until +an AddVersion is called on the component. +If composition mode is disabled blobs will directly be forwarded to +the backend and descriptor updated will be persisted on AddVersion +or closing a provided existing component version. +` +} + +func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { + if _, ok := v.(bool); !ok { + return nil, fmt.Errorf("boolean required") + } + return marshaller.Marshal(v) +} + +func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { + var value bool + err := unmarshaller.Unmarshal(data, &value) + return value, err +} + +//////////////////////////////////////////////////////////////////////////////// + +func Get(ctx datacontext.Context) bool { + a := ctx.GetAttributes().GetAttribute(ATTR_KEY) + if a == nil { + return UseCompositionMode + } + return a.(bool) +} + +func Set(ctx datacontext.Context, flag bool) error { + return ctx.GetAttributes().SetAttribute(ATTR_KEY, flag) +} diff --git a/api/ocm/extensions/attrs/compositionmodeattr/attr_test.go b/api/ocm/extensions/attrs/compositionmodeattr/attr_test.go new file mode 100644 index 000000000..429c79e07 --- /dev/null +++ b/api/ocm/extensions/attrs/compositionmodeattr/attr_test.go @@ -0,0 +1,45 @@ +package compositionmodeattr_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/ocm" + me "ocm.software/ocm/api/ocm/extensions/attrs/compositionmodeattr" + "ocm.software/ocm/api/utils/runtime" +) + +var _ = Describe("attribute", func() { + var ctx ocm.Context + var cfgctx config.Context + + BeforeEach(func() { + cfgctx = config.WithSharedAttributes(datacontext.New(nil)).New() + credctx := credentials.WithConfigs(cfgctx).New() + ocictx := oci.WithCredentials(credctx).New() + ctx = ocm.WithOCIRepositories(ocictx).New() + }) + It("local setting", func() { + Expect(me.Get(ctx)).To(Equal(me.UseCompositionMode)) + Expect(me.Set(ctx, true)).To(Succeed()) + Expect(me.Get(ctx)).To(BeTrue()) + Expect(me.Set(ctx, false)).To(Succeed()) + Expect(me.Get(ctx)).To(BeFalse()) + }) + + It("global setting", func() { + Expect(me.Get(cfgctx)).To(Equal(me.UseCompositionMode)) + Expect(me.Set(cfgctx, true)).To(Succeed()) + Expect(me.Get(cfgctx)).To(BeTrue()) + Expect(me.Set(cfgctx, false)).To(Succeed()) + Expect(me.Get(cfgctx)).To(BeFalse()) + }) + + It("parses string", func() { + Expect(me.AttributeType{}.Decode([]byte("true"), runtime.DefaultJSONEncoding)).To(BeTrue()) + }) +}) diff --git a/pkg/contexts/ocm/attrs/compositionmodeattr/suite_test.go b/api/ocm/extensions/attrs/compositionmodeattr/suite_test.go similarity index 100% rename from pkg/contexts/ocm/attrs/compositionmodeattr/suite_test.go rename to api/ocm/extensions/attrs/compositionmodeattr/suite_test.go diff --git a/api/ocm/extensions/attrs/hashattr/attr.go b/api/ocm/extensions/attrs/hashattr/attr.go new file mode 100644 index 000000000..910c429e1 --- /dev/null +++ b/api/ocm/extensions/attrs/hashattr/attr.go @@ -0,0 +1,109 @@ +package hashattr + +import ( + "fmt" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" + ocm "ocm.software/ocm/api/ocm/types" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/hasher/sha256" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/listformat" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ATTR_KEY = "github.com/mandelsoft/ocm/hasher" + ATTR_SHORT = "hasher" +) + +type ( + Context = ocm.Context + ContextProvider = ocm.ContextProvider + Hasher = ocm.Hasher +) + +func init() { + datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}) +} + +type AttributeType struct{} + +var ( + _ datacontext.AttributeType = (*AttributeType)(nil) + _ datacontext.Converter = (*AttributeType)(nil) +) + +func (a AttributeType) Name() string { + return ATTR_KEY +} + +func (a AttributeType) Description() string { + return ` +*JSON* +Preferred hash algorithm to calculate resource digests. The following +digesters are supported: +` + listformat.FormatList(sha256.Algorithm, signing.DefaultRegistry().HasherNames()...) +} + +func (a AttributeType) Convert(v interface{}) (interface{}, error) { + switch s := v.(type) { + case string: + return &Attribute{ + s, + }, nil + case *Attribute: + return v, nil + } + return nil, fmt.Errorf("digest algorithm name or hash attribute required") +} + +func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { + switch s := v.(type) { + case string: + return []byte(s), nil + case *Attribute: + return []byte(s.DefaultHasher), nil + } + return nil, fmt.Errorf("digest algorithm name required") +} + +func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { + var value string + err := unmarshaller.Unmarshal(data, &value) + if err != nil { + return nil, err + } + return &Attribute{ + value, + }, nil +} + +//////////////////////////////////////////////////////////////////////////////// + +type Attribute struct { + DefaultHasher string +} + +func (a *Attribute) GetHasher(ctx ContextProvider, names ...string) Hasher { + name := utils.Optional(names...) + if name != "" { + return signingattr.Get(ctx).GetHasher(name) + } + return signingattr.Get(ctx).GetHasher(a.DefaultHasher) +} + +func Get(ctx ContextProvider) *Attribute { + a := ctx.OCMContext().GetAttributes().GetAttribute(ATTR_KEY) + if a == nil { + return &Attribute{ + sha256.Algorithm, + } + } + return a.(*Attribute) +} + +func Set(ctx ContextProvider, hasher string) error { + return ctx.OCMContext().GetAttributes().SetAttribute(ATTR_KEY, hasher) +} diff --git a/api/ocm/extensions/attrs/hashattr/attr_test.go b/api/ocm/extensions/attrs/hashattr/attr_test.go new file mode 100644 index 000000000..0ddb4229c --- /dev/null +++ b/api/ocm/extensions/attrs/hashattr/attr_test.go @@ -0,0 +1,59 @@ +package hashattr_test + +import ( + "encoding/json" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/attrs/hashattr" + "ocm.software/ocm/api/tech/signing/hasher/sha512" + "ocm.software/ocm/api/utils/runtime" +) + +const NAME = "test" + +var _ = Describe("attribute", func() { + var cfgctx config.Context + var ocmctx ocm.Context + + BeforeEach(func() { + ocmctx = ocm.New(datacontext.MODE_EXTENDED) + cfgctx = ocmctx.ConfigContext() + }) + + It("marshal/unmarshal", func() { + cfg := hashattr.New(sha512.Algorithm) + data := Must(json.Marshal(cfg)) + + r := &hashattr.Config{} + Expect(json.Unmarshal(data, r)).To(Succeed()) + Expect(r).To(Equal(cfg)) + }) + + It("decode", func() { + attr := &hashattr.Attribute{ + DefaultHasher: sha512.Algorithm, + } + + r := Must(hashattr.AttributeType{}.Decode([]byte(sha512.Algorithm), runtime.DefaultYAMLEncoding)) + Expect(r).To(Equal(attr)) + }) + + It("applies string", func() { + MustBeSuccessful(cfgctx.GetAttributes().SetAttribute(hashattr.ATTR_KEY, sha512.Algorithm)) + attr := hashattr.Get(ocmctx) + Expect(attr.GetHasher(ocmctx)).To(Equal(sha512.Handler{})) + }) + + It("applies config", func() { + cfg := hashattr.New(sha512.Algorithm) + + MustBeSuccessful(cfgctx.ApplyConfig(cfg, "from test")) + Expect(hashattr.Get(ocmctx).GetHasher(ocmctx)).To(Equal(sha512.Handler{})) + }) +}) diff --git a/api/ocm/extensions/attrs/hashattr/config.go b/api/ocm/extensions/attrs/hashattr/config.go new file mode 100644 index 000000000..4dbca80bb --- /dev/null +++ b/api/ocm/extensions/attrs/hashattr/config.go @@ -0,0 +1,54 @@ +package hashattr + +import ( + "github.com/mandelsoft/goutils/errors" + + cfgcpi "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/hasher/sha256" + "ocm.software/ocm/api/utils/listformat" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ConfigType = "hasher" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX + ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" +) + +func init() { + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1, usage)) +} + +// Config describes a memory based repository interface. +type Config struct { + runtime.ObjectVersionedType `json:",inline"` + HashAlgorithm string `json:"hashAlgorithm"` +} + +// New creates a new memory ConfigSpec. +func New(algo string) *Config { + return &Config{ + ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), + HashAlgorithm: algo, + } +} + +func (a *Config) GetType() string { + return ConfigType +} + +func (a *Config) ApplyTo(ctx cfgcpi.Context, target interface{}) error { + t, ok := target.(Context) + if !ok { + return cfgcpi.ErrNoContext(ConfigType) + } + return errors.Wrapf(t.GetAttributes().SetAttribute(ATTR_KEY, a.HashAlgorithm), "applying config failed") +} + +var usage = ` +The config type ` + ConfigType + ` can be used to define +the default hash algorithm used to calculate digests for resources. +It supports the field hashAlgorithm, with one of the following +values: +` + listformat.FormatList(sha256.Algorithm, signing.DefaultRegistry().HasherNames()...) diff --git a/pkg/contexts/ocm/attrs/hashattr/suite_test.go b/api/ocm/extensions/attrs/hashattr/suite_test.go similarity index 100% rename from pkg/contexts/ocm/attrs/hashattr/suite_test.go rename to api/ocm/extensions/attrs/hashattr/suite_test.go diff --git a/api/ocm/extensions/attrs/init.go b/api/ocm/extensions/attrs/init.go new file mode 100644 index 000000000..283af4105 --- /dev/null +++ b/api/ocm/extensions/attrs/init.go @@ -0,0 +1,12 @@ +package attrs + +import ( + _ "ocm.software/ocm/api/ocm/extensions/attrs/compatattr" + _ "ocm.software/ocm/api/ocm/extensions/attrs/hashattr" + _ "ocm.software/ocm/api/ocm/extensions/attrs/keepblobattr" + _ "ocm.software/ocm/api/ocm/extensions/attrs/mapocirepoattr" + _ "ocm.software/ocm/api/ocm/extensions/attrs/ociuploadattr" + _ "ocm.software/ocm/api/ocm/extensions/attrs/plugincacheattr" + _ "ocm.software/ocm/api/ocm/extensions/attrs/plugindirattr" + _ "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" +) diff --git a/api/ocm/extensions/attrs/keepblobattr/attr.go b/api/ocm/extensions/attrs/keepblobattr/attr.go new file mode 100644 index 000000000..78475b05b --- /dev/null +++ b/api/ocm/extensions/attrs/keepblobattr/attr.go @@ -0,0 +1,61 @@ +package keepblobattr + +import ( + "fmt" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ATTR_KEY = "github.com/mandelsoft/ocm/keeplocalblob" + ATTR_SHORT = "keeplocalblob" +) + +func init() { + datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) +} + +type AttributeType struct{} + +func (a AttributeType) Name() string { + return ATTR_KEY +} + +func (a AttributeType) Description() string { + return ` +*bool* +Keep local blobs when importing OCI artifacts to OCI registries from localBlob +access methods. By default, they will be expanded to OCI artifacts with the +access method ociRegistry. If this option is set to true, they will be stored +as local blobs, also. The access method will still be localBlob but with a nested +ociRegistry access method for describing the global access. +` +} + +func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { + if _, ok := v.(bool); !ok { + return nil, fmt.Errorf("boolean required") + } + return marshaller.Marshal(v) +} + +func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { + var value bool + err := unmarshaller.Unmarshal(data, &value) + return value, err +} + +//////////////////////////////////////////////////////////////////////////////// + +func Get(ctx datacontext.Context) bool { + a := ctx.GetAttributes().GetAttribute(ATTR_KEY) + if a == nil { + return false + } + return a.(bool) +} + +func Set(ctx datacontext.Context, flag bool) error { + return ctx.GetAttributes().SetAttribute(ATTR_KEY, flag) +} diff --git a/api/ocm/extensions/attrs/keepblobattr/attr_test.go b/api/ocm/extensions/attrs/keepblobattr/attr_test.go new file mode 100644 index 000000000..c67e83271 --- /dev/null +++ b/api/ocm/extensions/attrs/keepblobattr/attr_test.go @@ -0,0 +1,41 @@ +package keepblobattr_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/ocm" + me "ocm.software/ocm/api/ocm/extensions/attrs/keepblobattr" + "ocm.software/ocm/api/utils/runtime" +) + +var _ = Describe("attribute", func() { + var ctx ocm.Context + var cfgctx config.Context + + BeforeEach(func() { + cfgctx = config.WithSharedAttributes(datacontext.New(nil)).New() + credctx := credentials.WithConfigs(cfgctx).New() + ocictx := oci.WithCredentials(credctx).New() + ctx = ocm.WithOCIRepositories(ocictx).New() + }) + It("local setting", func() { + Expect(me.Get(ctx)).To(BeFalse()) + Expect(me.Set(ctx, true)).To(Succeed()) + Expect(me.Get(ctx)).To(BeTrue()) + }) + + It("global setting", func() { + Expect(me.Get(cfgctx)).To(BeFalse()) + Expect(me.Set(ctx, true)).To(Succeed()) + Expect(me.Get(ctx)).To(BeTrue()) + }) + + It("parses string", func() { + Expect(me.AttributeType{}.Decode([]byte("true"), runtime.DefaultJSONEncoding)).To(BeTrue()) + }) +}) diff --git a/pkg/contexts/ocm/attrs/keepblobattr/suite_test.go b/api/ocm/extensions/attrs/keepblobattr/suite_test.go similarity index 100% rename from pkg/contexts/ocm/attrs/keepblobattr/suite_test.go rename to api/ocm/extensions/attrs/keepblobattr/suite_test.go diff --git a/api/ocm/extensions/attrs/mapocirepoattr/attr.go b/api/ocm/extensions/attrs/mapocirepoattr/attr.go new file mode 100644 index 000000000..3f5c0ad53 --- /dev/null +++ b/api/ocm/extensions/attrs/mapocirepoattr/attr.go @@ -0,0 +1,249 @@ +package mapocirepoattr + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "strings" + + "github.com/mandelsoft/goutils/errors" + "golang.org/x/exp/maps" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/oci/grammar" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ATTR_KEY = "github.com/mandelsoft/ocm/mapocirepo" + ATTR_SHORT = "mapocirepo" +) + +func init() { + datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) +} + +type AttributeType struct{} + +func (a AttributeType) Name() string { + return ATTR_KEY +} + +func (a AttributeType) Description() string { + return ` +*bool|YAML* +When uploading an OCI artifact blob to an OCI based OCM repository and the +artifact is uploaded as OCI artifact, the repository path part is shortened, +either by hashing all but the last repository name part or by executing +some prefix based name mappings. + +If a boolean is given the short hash or none mode is enabled. +The YAML flavor uses the following fields: +- *mode* *string*: hash, shortHash, prefixMapping + or none. If unset, no mapping is done. +- *prefixMappings*: *map[string]string* repository path prefix mapping. +- *prefix*: *string* repository prefix to use (replaces potential sub path of OCM repo). + or none. +- *prefixMapping*: *map[string]string* repository path prefix mapping. + +Notes: + +- The mapping only occurs in transfer commands and only when transferring to OCI registries (e.g. + when transferring to a CTF archive this option will be ignored). +- The mapping only happens for local resources. When external image references are transferred (with + option --copy-resources) the mapping will be ignored. +- The mapping in mode prefixMapping requires a full prefix of the composed final name. + Partial matches are not supported. The host name of the target will be skipped. +- The artifact name of the component-descriptor is not mapped. +- If the mapping is provided on the command line it must be JSON format and needs to be properly + escaped (see example below). + +Example: + +Assume a component named github.com/my_org/myexamplewithalongname and a chart name +echo in the Charts.yaml of the chart archive. The following input to a +resource.yaml creates a component version: + +
+name: mychart
+type: helmChart
+input:
+  type: helm
+  path: charts/mychart.tgz
+---
+name: myimage
+type: ociImage
+version: 0.1.0
+input:
+  type: ociImage
+  repository: ocm/ocm.software/ocmcli/ocmcli-image
+  path: ghcr.io/acme/ocm/ocm.software/ocmcli/ocmcli-image:0.1.0
+
+ +The following command: + +
+ocm "-X mapocirepo={\"mode\":\"mapping\",\"prefixMappings\":{\"acme/github.com/my_org/myexamplewithalongname/ocm/ocm.software/ocmcli\":\"acme/cli\", \"acme/github.com/my_org/myexamplewithalongnameabc123\":\"acme/mychart\"}}" transfer ctf -f --copy-resources ./ctf ghcr.io/acme
+
+ +will result in the following artifacts in ghcr.io/my_org: + +
+mychart/echo
+cli/ocmcli-image
+
+ +Note that the host name part of the transfer target ghcr.io/acme is excluded from the +prefix but the path acme is considered. + +The same using a config file .ocmconfig: +
+type: generic.config.ocm.software/v1
+configurations:
+...
+- type: attributes.config.ocm.software
+  attributes:
+	...
+	mapocirepo:
+	  mode: mapping
+	  prefixMappings:
+	    acme/github.com/my\_org/myexamplewithalongname/ocm/ocm.software/ocmcli: acme/cli
+		acme/github.com/my\_org/myexamplewithalongnameabc123: acme/mychart
+
+ +
+ocm transfer ca -f --copy-resources ./ca ghcr.io/acme
+
+` +} + +func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { + if _, ok := v.(bool); ok { + return marshaller.Marshal(&Attribute{Mode: ShortHashMode}) + } + + if _, ok := v.(*Attribute); ok { + return marshaller.Marshal(v) + } + + return nil, fmt.Errorf("boolean or attribute struct required") +} + +func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { + var value bool + attr := &Attribute{} + + err := unmarshaller.Unmarshal(data, attr) + if err == nil { + switch attr.Mode { + case "": + case NoneMode: + case HashMode: + case ShortHashMode: + case MappingMode: + default: + return nil, errors.ErrInvalid("mode", attr.Mode) + } + return attr, nil + } + + err = unmarshaller.Unmarshal(data, &value) + if err == nil { + if value { + attr.Mode = ShortHashMode + } else { + attr.Mode = NoneMode + } + attr.PrefixMappings = map[string]string{} + return attr, nil + } + + return value, err +} + +//////////////////////////////////////////////////////////////////////////////// + +const ( + NoneMode = "none" + HashMode = "hash" + ShortHashMode = "shortHash" + MappingMode = "mapping" +) + +type Attribute struct { + Mode string `json:"mode"` + Always bool `json:"always,omitempty"` + Prefix *string `json:"prefix,omitempty"` + PrefixMappings map[string]string `json:"prefixMappings,omitempty"` +} + +func (a *Attribute) Map(name string) string { + if len(a.PrefixMappings) == 0 { + a.PrefixMappings = map[string]string{} + } + switch a.Mode { + case "", NoneMode: + return name + case HashMode, ShortHashMode: + return a.Hash(name, a.Mode == ShortHashMode) + case MappingMode: + return a.MapPrefix(name) + } + return name +} + +func (a *Attribute) MapPrefix(name string) string { + keys := utils.StringMapKeys(a.PrefixMappings) + for i := range keys { + k := keys[len(keys)-i-1] + if strings.HasPrefix(name, k+grammar.RepositorySeparator) { + name = a.PrefixMappings[k] + name[len(k):] + break + } + } + return name +} + +func (a *Attribute) Hash(name string, short bool) string { + if idx := strings.LastIndex(name, grammar.RepositorySeparator); idx > 0 { + prefix := name[:idx] + sum := sha256.Sum256([]byte(prefix)) + n := hex.EncodeToString(sum[:]) + if short { + n = n[:8] + } + n += grammar.RepositorySeparator + name[idx+1:] + if a.Always || len(n) < len(name) { + name = n + } + } + return name +} + +func (a *Attribute) Copy() *Attribute { + n := *a + n.PrefixMappings = maps.Clone(n.PrefixMappings) + return &n +} + +//////////////////////////////////////////////////////////////////////////////// + +func Get(ctx datacontext.Context) *Attribute { + a := ctx.GetAttributes().GetAttribute(ATTR_KEY) + if a == nil { + return &Attribute{Mode: NoneMode} + } + if b, ok := a.(bool); ok { + if b { + return &Attribute{Mode: ShortHashMode} + } else { + return &Attribute{Mode: NoneMode} + } + } + return a.(*Attribute).Copy() +} + +func Set(ctx datacontext.Context, a *Attribute) error { + return ctx.GetAttributes().SetAttribute(ATTR_KEY, a) +} diff --git a/api/ocm/extensions/attrs/mapocirepoattr/attr_test.go b/api/ocm/extensions/attrs/mapocirepoattr/attr_test.go new file mode 100644 index 000000000..377f1ee8f --- /dev/null +++ b/api/ocm/extensions/attrs/mapocirepoattr/attr_test.go @@ -0,0 +1,35 @@ +package mapocirepoattr_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/datacontext" + me "ocm.software/ocm/api/ocm/extensions/attrs/mapocirepoattr" +) + +var _ = Describe("attribute", func() { + var ctx datacontext.Context + + BeforeEach(func() { + ctx = datacontext.New(nil) + }) + + It("set bool", func() { + Expect(me.Get(ctx)).To(Equal(&me.Attribute{Mode: me.NoneMode})) + ctx.GetAttributes().SetAttribute(me.ATTR_KEY, true) + a := me.Get(ctx) + Expect(a).To(Equal(&me.Attribute{Mode: me.ShortHashMode})) + hash := "5afa3f0f1b63d64422e7f93e2d9792b7c1f3b4462a931d80b25703f7e6fc79c2" + Expect(a.Map("very-long-path/with-many-path-segments/and-really-longer-than-a-hash/artifact")).To(Equal(hash[:8] + "/artifact")) + }) + + It("set attr", func() { + ctx.GetAttributes().SetAttribute(me.ATTR_KEY, &me.Attribute{Mode: me.MappingMode, PrefixMappings: map[string]string{"a": "b", "a/b": "c"}}) + a := me.Get(ctx) + Expect(a).To(Equal(&me.Attribute{Mode: me.MappingMode, PrefixMappings: map[string]string{"a": "b", "a/b": "c"}})) + Expect(a.Map("a/b/c")).To(Equal("c/c")) + Expect(a.Map("a/c")).To(Equal("b/c")) + Expect(a.Map("x/y")).To(Equal("x/y")) + }) +}) diff --git a/pkg/contexts/ocm/attrs/mapocirepoattr/suite_test.go b/api/ocm/extensions/attrs/mapocirepoattr/suite_test.go similarity index 100% rename from pkg/contexts/ocm/attrs/mapocirepoattr/suite_test.go rename to api/ocm/extensions/attrs/mapocirepoattr/suite_test.go diff --git a/api/ocm/extensions/attrs/ociuploadattr/attr.go b/api/ocm/extensions/attrs/ociuploadattr/attr.go new file mode 100644 index 000000000..eb3a07e31 --- /dev/null +++ b/api/ocm/extensions/attrs/ociuploadattr/attr.go @@ -0,0 +1,203 @@ +package ociuploadattr + +import ( + "bytes" + "fmt" + "strings" + "sync" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/oci" + ocicpi "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ATTR_KEY = "github.com/mandelsoft/ocm/ociuploadrepo" + ATTR_SHORT = "ociuploadrepo" +) + +func init() { + datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) +} + +type AttributeType struct{} + +func (a AttributeType) Name() string { + return ATTR_KEY +} + +func (a AttributeType) Description() string { + return ` +*oci base repository ref* +Upload local OCI artifact blobs to a dedicated repository. +` +} + +func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { + if _, ok := v.(*Attribute); !ok { + return nil, fmt.Errorf("OCI Upload Attribute structure required") + } + return marshaller.Marshal(v) +} + +func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { + var value Attribute + err := unmarshaller.Unmarshal(data, &value) + if err == nil { + if value.Repository != nil { + if value.Repository.GetType() == "" { + return nil, errors.ErrInvalidWrap(errors.Newf("missing repository type"), oci.KIND_OCI_REFERENCE, string(data)) + } + return &value, nil + } + if value.Ref == "" { + return nil, errors.ErrInvalidWrap(errors.Newf("missing repository or ref"), oci.KIND_OCI_REFERENCE, string(data)) + } + data = []byte(value.Ref) + } + ref, err := oci.ParseRef(string(data)) + if err != nil { + return nil, errors.ErrInvalidWrap(err, oci.KIND_OCI_REFERENCE, string(data)) + } + if ref.Tag != nil || ref.Digest != nil { + return nil, errors.ErrInvalidWrap(err, oci.KIND_OCI_REFERENCE, string(data)) + } + return &Attribute{Ref: strings.Trim(string(data), "\"")}, nil +} + +//////////////////////////////////////////////////////////////////////////////// + +type Attribute struct { + Ref string `json:"ociRef,omitempty"` + Repository *ocicpi.GenericRepositorySpec `json:"repository,omitempty"` + NamespacePrefix string `json:"namespacePrefix,omitempty"` + + lock sync.Mutex + ref *oci.RefSpec + spec []byte + + repo oci.Repository + prefix string +} + +func AttributeDescription() map[string]string { + return map[string]string{ + "ociRef": "an OCI repository reference", + "repository": "an OCI repository specification for the target OCI registry", + "namespacePrefix": "a namespace prefix used for the uploaded artifacts", + } +} + +func New(ref string) *Attribute { + return &Attribute{Ref: ref} +} + +func (a *Attribute) reset() { + a.repo = nil + a.prefix = "" + a.ref = nil + a.spec = nil +} + +func (a *Attribute) Close() error { + a.lock.Lock() + defer a.lock.Unlock() + if a.repo != nil { + defer a.reset() + return a.repo.Close() + } + return nil +} + +func (a *Attribute) GetInfo(ctx cpi.Context) (oci.Repository, *oci.UniformRepositorySpec, string, error) { + if a.Ref != "" { + return a.getByRef(ctx) + } + if a.Repository != nil { + return a.getBySpec(ctx) + } + return nil, nil, "", errors.ErrInvalid("ociuploadspec") +} + +func (a *Attribute) getBySpec(ctx cpi.Context) (oci.Repository, *oci.UniformRepositorySpec, string, error) { + data, err := a.Repository.MarshalJSON() + if err != nil { + return nil, nil, "", errors.Wrap(err, a.ref.String()) + } + + spec, err := a.Repository.Evaluate(ctx.OCIContext()) + if err != nil { + return nil, nil, "", errors.ErrInvalidWrap(err, oci.KIND_OCI_REFERENCE, string(data)) + } + + a.lock.Lock() + defer a.lock.Unlock() + + if a.spec == nil || bytes.Equal(a.spec, data) { + if a.repo != nil { + a.repo.Close() + a.reset() + } + + a.repo, err = ctx.OCIContext().RepositoryForSpec(spec) + if err != nil { + return nil, nil, "", err + } + + a.prefix = a.NamespacePrefix + a.spec = data + a.ref = &oci.RefSpec{UniformRepositorySpec: *spec.UniformRepositorySpec()} + ctx.Finalizer().Close(a) + } + return a.repo, &a.ref.UniformRepositorySpec, a.prefix, nil +} + +func (a *Attribute) getByRef(ctx cpi.Context) (oci.Repository, *oci.UniformRepositorySpec, string, error) { + ref, err := oci.ParseRef(a.Ref) + if err != nil { + return nil, nil, "", errors.ErrInvalidWrap(err, oci.KIND_OCI_REFERENCE, a.Ref) + } + if ref.Tag != nil || ref.Digest != nil { + return nil, nil, "", errors.ErrInvalidWrap(err, oci.KIND_OCI_REFERENCE, a.Ref) + } + + a.lock.Lock() + defer a.lock.Unlock() + if a.ref == nil || ref != *a.ref { + if a.repo != nil { + a.repo.Close() + a.reset() + } + + spec, err := ctx.OCIContext().MapUniformRepositorySpec(&ref.UniformRepositorySpec) + if err != nil { + return nil, nil, "", err + } + a.repo, err = ctx.OCIContext().RepositoryForSpec(spec) + if err != nil { + return nil, nil, "", err + } + a.prefix = ref.Repository + a.ref = &ref + ctx.Finalizer().Close(a) + } + return a.repo, &a.ref.UniformRepositorySpec, a.prefix, nil +} + +//////////////////////////////////////////////////////////////////////////////// + +func Get(ctx datacontext.Context) *Attribute { + a := ctx.GetAttributes().GetAttribute(ATTR_KEY) + if a == nil { + return nil + } + return a.(*Attribute) +} + +func Set(ctx datacontext.Context, attr *Attribute) error { + return ctx.GetAttributes().SetAttribute(ATTR_KEY, attr) +} diff --git a/api/ocm/extensions/attrs/ociuploadattr/attr_test.go b/api/ocm/extensions/attrs/ociuploadattr/attr_test.go new file mode 100644 index 000000000..2761ec3ca --- /dev/null +++ b/api/ocm/extensions/attrs/ociuploadattr/attr_test.go @@ -0,0 +1,58 @@ +package ociuploadattr_test + +import ( + "encoding/json" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/extensions/repositories/ocireg" + "ocm.software/ocm/api/ocm" + me "ocm.software/ocm/api/ocm/extensions/attrs/ociuploadattr" + "ocm.software/ocm/api/utils/runtime" +) + +var _ = Describe("attribute", func() { + var ctx ocm.Context + var cfgctx config.Context + + attr := &me.Attribute{Ref: "ref"} + + BeforeEach(func() { + cfgctx = config.WithSharedAttributes(datacontext.New(nil)).New() + credctx := credentials.WithConfigs(cfgctx).New() + ocictx := oci.WithCredentials(credctx).New() + ctx = ocm.WithOCIRepositories(ocictx).New() + }) + It("local setting", func() { + Expect(me.Get(ctx)).To(BeNil()) + Expect(me.Set(ctx, attr)).To(Succeed()) + Expect(me.Get(ctx)).To(BeIdenticalTo(attr)) + }) + + It("global setting", func() { + Expect(me.Get(cfgctx)).To(BeNil()) + Expect(me.Set(ctx, attr)).To(Succeed()) + Expect(me.Get(ctx)).To(BeIdenticalTo(attr)) + }) + + It("parses string", func() { + Expect(me.AttributeType{}.Decode([]byte("ref"), runtime.DefaultJSONEncoding)).To(Equal(&me.Attribute{Ref: "ref"})) + }) + + It("parses spec", func() { + spec, err := oci.ToGenericRepositorySpec(ocireg.NewRepositorySpec("ghcr.io")) + Expect(err).To(Succeed()) + attr := &me.Attribute{ + Repository: spec, + NamespacePrefix: "ref", + } + data, err := json.Marshal(attr) + Expect(err).To(Succeed()) + Expect(me.AttributeType{}.Decode(data, runtime.DefaultJSONEncoding)).To(Equal(attr)) + }) +}) diff --git a/pkg/contexts/ocm/attrs/ociuploadattr/suite_test.go b/api/ocm/extensions/attrs/ociuploadattr/suite_test.go similarity index 100% rename from pkg/contexts/ocm/attrs/ociuploadattr/suite_test.go rename to api/ocm/extensions/attrs/ociuploadattr/suite_test.go diff --git a/api/ocm/extensions/attrs/plugincacheattr/attr.go b/api/ocm/extensions/attrs/plugincacheattr/attr.go new file mode 100644 index 000000000..b737b14e5 --- /dev/null +++ b/api/ocm/extensions/attrs/plugincacheattr/attr.go @@ -0,0 +1,29 @@ +package plugincacheattr + +import ( + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/attrs/plugindirattr" + "ocm.software/ocm/api/ocm/plugin/cache" + "ocm.software/ocm/api/ocm/plugin/plugins" +) + +const ( + ATTR_KEY = "github.com/mandelsoft/ocm/plugins" +) + +//////////////////////////////////////////////////////////////////////////////// + +func Get(ctxp ocm.ContextProvider) plugins.Set { + ctx := ctxp.OCMContext() + path := plugindirattr.Get(ctx) + + // avoid dead lock reading attribute during attribute creation + return ctx.GetAttributes().GetOrCreateAttribute(ATTR_KEY, func(ctx datacontext.Context) interface{} { + return plugins.New(ctx.(ocm.Context), path) + }).(plugins.Set) +} + +func Set(ctx ocm.Context, cache cache.PluginDir) error { + return ctx.GetAttributes().SetAttribute(ATTR_KEY, cache) +} diff --git a/api/ocm/extensions/attrs/plugindirattr/attr.go b/api/ocm/extensions/attrs/plugindirattr/attr.go new file mode 100644 index 000000000..2c23455a1 --- /dev/null +++ b/api/ocm/extensions/attrs/plugindirattr/attr.go @@ -0,0 +1,77 @@ +package plugindirattr + +import ( + "fmt" + "os" + + "github.com/mandelsoft/filepath/pkg/filepath" + "github.com/mandelsoft/vfs/pkg/osfs" + "github.com/mandelsoft/vfs/pkg/vfs" + "github.com/modern-go/reflect2" + + "ocm.software/ocm/api/datacontext" + utils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ATTR_KEY = "github.com/mandelsoft/ocm/plugindir" + ATTR_SHORT = "plugindir" + + DEFAULT_PLUGIN_DIR = utils.DEFAULT_OCM_CONFIG_DIR + "/plugins" +) + +func init() { + datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) +} + +func DefaultDir(fs vfs.FileSystem) string { + home, _ := os.UserHomeDir() // use home if provided + if home != "" { + dir := filepath.Join(home, DEFAULT_PLUGIN_DIR) + if ok, err := vfs.DirExists(fs, dir); ok && err == nil { + return dir + } + } + return "" +} + +type AttributeType struct{} + +func (a AttributeType) Name() string { + return ATTR_KEY +} + +func (a AttributeType) Description() string { + return ` +*plugin directory* +Directory to look for OCM plugin executables. +` +} + +func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { + if _, ok := v.(string); !ok { + return nil, fmt.Errorf("directory path required") + } + return marshaller.Marshal(v) +} + +func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { + var value string + err := unmarshaller.Unmarshal(data, &value) + return value, err +} + +//////////////////////////////////////////////////////////////////////////////// + +func Get(ctx datacontext.Context) string { + a := ctx.GetAttributes().GetAttribute(ATTR_KEY) + if reflect2.IsNil(a) { + return DefaultDir(osfs.New()) + } + return a.(string) +} + +func Set(ctx datacontext.Context, path string) error { + return ctx.GetAttributes().SetAttribute(ATTR_KEY, path) +} diff --git a/api/ocm/extensions/attrs/plugindirattr/attr_test.go b/api/ocm/extensions/attrs/plugindirattr/attr_test.go new file mode 100644 index 000000000..311f4b805 --- /dev/null +++ b/api/ocm/extensions/attrs/plugindirattr/attr_test.go @@ -0,0 +1,26 @@ +package plugindirattr_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/datacontext" + me "ocm.software/ocm/api/ocm/extensions/attrs/plugindirattr" +) + +var _ = Describe("attribute", func() { + var ctx config.Context + + attr := "___test___" + + BeforeEach(func() { + ctx = config.WithSharedAttributes(datacontext.New(nil)).New() + }) + + It("local setting", func() { + Expect(me.Get(ctx)).NotTo(Equal(attr)) + Expect(me.Set(ctx, attr)).To(Succeed()) + Expect(me.Get(ctx)).To(BeIdenticalTo(attr)) + }) +}) diff --git a/pkg/contexts/ocm/attrs/plugindirattr/suite_test.go b/api/ocm/extensions/attrs/plugindirattr/suite_test.go similarity index 100% rename from pkg/contexts/ocm/attrs/plugindirattr/suite_test.go rename to api/ocm/extensions/attrs/plugindirattr/suite_test.go diff --git a/api/ocm/extensions/attrs/signingattr/attr.go b/api/ocm/extensions/attrs/signingattr/attr.go new file mode 100644 index 000000000..94a609305 --- /dev/null +++ b/api/ocm/extensions/attrs/signingattr/attr.go @@ -0,0 +1,102 @@ +package signingattr + +import ( + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/datacontext" + ocm "ocm.software/ocm/api/ocm/types" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ATTR_KEY = "github.com/mandelsoft/ocm/signing" + ATTR_SHORT = "signing" +) + +type ( + Context = ocm.Context + ContextProvider = ocm.ContextProvider +) + +func init() { + datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}) +} + +type AttributeType struct{} + +func (a AttributeType) Name() string { + return ATTR_KEY +} + +func (a AttributeType) Description() string { + return ` +*JSON* +Public and private Key settings given as JSON document with the following +format: + +
+{
+  "publicKeys"": [
+     "<provider>": {
+       "data": ""<base64>"
+     }
+  ],
+  "privateKeys"": [
+     "<provider>": {
+       "path": ""<file path>"
+     }
+  ]
+
+ +One of following data fields are possible: +- data: base64 encoded binary data +- stringdata: plain text data +- path: a file path to read the data from +` +} + +func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { + if _, ok := v.(signing.Registry); ok { + return nil, nil + } + return nil, errors.ErrNotSupported("encoding of key registry") +} + +func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { + var value Config + err := unmarshaller.Unmarshal(data, &value) + if err != nil { + return nil, err + } + value.SetType(ConfigType) + registry := signing.NewRegistry(signing.DefaultHandlerRegistry(), signing.DefaultKeyRegistry()) + value.ApplyToRegistry(registry) + return registry, err +} + +//////////////////////////////////////////////////////////////////////////////// + +func Get(ctx ContextProvider) signing.Registry { + a := ctx.OCMContext().GetAttributes().GetAttribute(ATTR_KEY) + if a == nil { + return signing.DefaultRegistry() + } + return a.(signing.Registry) +} + +func SetKeyRegistry(ctx ContextProvider, registry signing.KeyRegistry) error { + old := Get(ctx) + r := signing.NewRegistry(old.HandlerRegistry(), registry) + return ctx.OCMContext().GetAttributes().SetAttribute(ATTR_KEY, r) +} + +func SetHandlerRegistry(ctx ContextProvider, registry signing.HandlerRegistry) error { + old := Get(ctx) + r := signing.NewRegistry(registry, old.KeyRegistry()) + return ctx.OCMContext().GetAttributes().SetAttribute(ATTR_KEY, r) +} + +func Set(ctx ContextProvider, registry signing.Registry) error { + return ctx.OCMContext().GetAttributes().SetAttribute(ATTR_KEY, registry) +} diff --git a/api/ocm/extensions/attrs/signingattr/attr_test.go b/api/ocm/extensions/attrs/signingattr/attr_test.go new file mode 100644 index 000000000..8088b777d --- /dev/null +++ b/api/ocm/extensions/attrs/signingattr/attr_test.go @@ -0,0 +1,75 @@ +package signingattr_test + +import ( + "encoding/json" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/datacontext/attrs/rootcertsattr" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" +) + +const NAME = "test" + +var _ = Describe("attribute", func() { + var cfgctx config.Context + var ocmctx ocm.Context + + BeforeEach(func() { + ocmctx = ocm.New(datacontext.MODE_EXTENDED) + cfgctx = ocmctx.ConfigContext() + }) + + It("marshal/unmarshal", func() { + cfg := signingattr.New() + cfg.AddPublicKeyData(NAME, []byte("keydata")) + + data, err := json.Marshal(cfg) + Expect(err).To(Succeed()) + + r := &signingattr.Config{} + Expect(json.Unmarshal(data, r)).To(Succeed()) + Expect(r).To(Equal(cfg)) + }) + + It("applies public key", func() { + cfg := signingattr.New() + cfg.AddPublicKeyData(NAME, []byte("keydata")) + + Expect(cfgctx.ApplyConfig(cfg, "from test")).To(Succeed()) + Expect(signingattr.Get(ocmctx).GetPublicKey(NAME)).To(Equal([]byte("keydata"))) + }) + + It("applies root certificate", func() { + certdata := ` +-----BEGIN CERTIFICATE----- +MIIDBDCCAeygAwIBAgIQF+kRr0G+faDEAH5Y4P1J7DANBgkqhkiG9w0BAQsFADAc +MQwwCgYDVQQKEwNPQ00xDDAKBgNVBAMTA29jbTAeFw0yMzEyMjkxMDIyMzdaFw0y +NDEyMjgxMDIyMzdaMBwxDDAKBgNVBAoTA09DTTEMMAoGA1UEAxMDb2NtMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvpTQIQFNy23ygef3pshdeNjT7TME +kPEuqrqcF3KIX1cX16pHMQeU+VzXAFRj3xCy3LAM8ZzLsdHSwZDsIsGdg0nAbGjz ++USez/9TGC58ktr/84Kh0gHDE28YSVhsnNSrBJcWaBlYZz4Iy89O2Xc4jbK34Cwg +Si0ES+Ru1lxLD6FSLYLe43wCIjWRJRrMFcua6nI0P4MCpcKmTkXG2/xz80QSobI3 +z/isqOT54FKHW8DZZVlQMOxh+loeLksfEq7EYVkQoUWEV6xyR24TEpMGfxERgDre +l7lmx8nIFzRMXkot+P19XWfUBgqctVEiDF4DlRE+SvCZsNCrg7nQuC2AZQIDAQAB +o0IwQDAOBgNVHQ8BAf8EBAMCAqQwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +1iQqrWM/bCXMk+5c1bulfI5zlKcwDQYJKoZIhvcNAQELBQADggEBAAQO6lw6ePuX +E+NyhDYCulueMWHC7GRUKa1KpouFT2yM0BSQnP04VakTlwVO3w4w2KucSVVomHR3 +hTY9Ypx7iGLaqdXHmUZvx3uaTM5IXQKMMWL1LJsxAvuzucehgDlOnFBD91tKsr5o +VRvRU5ya0igBCnnGpFu7NuH3C9pgF01lrQ3EhUHuNeazxleaE3/uQWmAXfxFB4ci +gHMKSEk3HuYA1raDJFv4ihwO5pXHvlDhcW0C1oMG9lOCh8TXpVzzBDZiH1kWPWSs +gW9YBu7/p/22U4++X23RyaheGuysfRAMv9cTv+8T0J8NHaAmQz4/QHFXh+0/tQgU +EVQVGDF6KNU= +-----END CERTIFICATE----- +` + cfg := signingattr.New() + cfg.AddRootCertificateData([]byte(certdata)) + + Expect(cfgctx.ApplyConfig(cfg, "from test")).To(Succeed()) + Expect(rootcertsattr.Get(ocmctx).HasRootCertificates()).To(BeTrue()) + }) +}) diff --git a/api/ocm/extensions/attrs/signingattr/config.go b/api/ocm/extensions/attrs/signingattr/config.go new file mode 100644 index 000000000..128e42181 --- /dev/null +++ b/api/ocm/extensions/attrs/signingattr/config.go @@ -0,0 +1,296 @@ +package signingattr + +import ( + "crypto/x509/pkix" + "encoding/base64" + "encoding/json" + "encoding/pem" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/vfs/pkg/vfs" + "golang.org/x/exp/slices" + + cfgcpi "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/datacontext/attrs/rootcertsattr" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/signutils" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ConfigType = "keys" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX + ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" +) + +func init() { + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1, usage)) +} + +type Issuer struct { + CommonName string `json:"commonName,omitempty"` + Organization []string `json:"organization,omitempty"` + OrganizationalUnit []string `json:"organizationalUnit,omitempty"` + + Country []string `json:"country,omitempty"` + Locality []string `json:"locality,omitempty"` + Province []string `json:"province,omitempty"` + StreetAddress []string `json:"streetAddress,omitempty"` + PostalCode []string `json:"postalCode,omitempty"` +} + +func (i *Issuer) Get() *pkix.Name { + return &pkix.Name{ + CommonName: i.CommonName, + + Country: slices.Clone(i.Country), + Organization: slices.Clone(i.Organization), + OrganizationalUnit: slices.Clone(i.OrganizationalUnit), + Locality: slices.Clone(i.Locality), + Province: slices.Clone(i.Province), + StreetAddress: slices.Clone(i.StreetAddress), + PostalCode: slices.Clone(i.PostalCode), + } +} + +func (i *Issuer) Set(issuer *pkix.Name) { + i.CommonName = issuer.CommonName + + i.Country = slices.Clone(issuer.Country) + i.Organization = slices.Clone(issuer.Organization) + i.OrganizationalUnit = slices.Clone(issuer.OrganizationalUnit) + i.Locality = slices.Clone(issuer.Locality) + i.Province = slices.Clone(issuer.Province) + i.StreetAddress = slices.Clone(issuer.StreetAddress) + i.PostalCode = slices.Clone(issuer.PostalCode) +} + +type KeySpec = cfgcpi.ContentSpec + +// Config describes a memory based repository interface. +type Config struct { + runtime.ObjectVersionedType `json:",inline"` + PublicKeys map[string]KeySpec `json:"publicKeys,omitempty"` + PrivateKeys map[string]KeySpec `json:"privateKeys,omitempty"` + Issuers map[string]Issuer `json:"issuers,omitempty"` + RootCertificates []KeySpec `json:"rootCertificates,omitempty"` + TSAUrl string `json:"tsaURL,omitempty"` +} + +type RawData []byte + +var _ json.Unmarshaler = (*RawData)(nil) + +func (r RawData) MarshalJSON() ([]byte, error) { + return json.Marshal(base64.StdEncoding.EncodeToString(r)) +} + +func (r *RawData) UnmarshalJSON(data []byte) error { + var s string + err := json.Unmarshal(data, &s) + if err != nil { + return err + } + *r, err = base64.StdEncoding.DecodeString(s) + return err +} + +// New creates a new memory ConfigSpec. +func New() *Config { + return &Config{ + ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), + } +} + +func (a *Config) GetType() string { + return ConfigType +} + +func (a *Config) AddIssuer(name string, issuer *pkix.Name) { + var i Issuer + + i.Set(issuer) + if a.Issuers == nil { + a.Issuers = map[string]Issuer{} + } + a.Issuers[name] = i +} + +func (a *Config) addKey(set *map[string]KeySpec, name string, key interface{}, conv func(interface{}) *pem.Block) error { + if *set == nil { + *set = map[string]KeySpec{} + } + switch data := key.(type) { + case []byte: + (*set)[name] = KeySpec{Data: data} + case string: + (*set)[name] = KeySpec{StringData: data} + default: + if conv != nil { + block := conv(key) + if block == nil { + return errors.ErrUnknown("format") + } + (*set)[name] = KeySpec{Parsed: key, StringData: string(pem.EncodeToMemory(block))} + } else { + (*set)[name] = KeySpec{Parsed: key} + } + } + return nil +} + +func (a *Config) AddPublicKey(name string, key interface{}) error { + return a.addKey(&a.PublicKeys, name, key, func(key interface{}) *pem.Block { return signutils.PemBlockForPublicKey(key) }) +} + +func (a *Config) AddPrivateKey(name string, key interface{}) error { + return a.addKey(&a.PrivateKeys, name, key, signutils.PemBlockForPrivateKey) +} + +func (a *Config) addKeyFile(set *map[string]KeySpec, name, path string, fss ...vfs.FileSystem) { + var fs vfs.FileSystem + for _, fs = range fss { + if fs != nil { + break + } + } + if *set == nil { + *set = map[string]KeySpec{} + } + (*set)[name] = KeySpec{Path: path, FileSystem: fs} +} + +func (a *Config) AddPublicKeyFile(name, path string, fss ...vfs.FileSystem) { + a.addKeyFile(&a.PublicKeys, name, path, fss...) +} + +func (a *Config) AddPrivateKeyFile(name, path string, fss ...vfs.FileSystem) { + a.addKeyFile(&a.PrivateKeys, name, path, fss...) +} + +func (a *Config) AddRootCertificateFile(name string, fss ...vfs.FileSystem) { + a.RootCertificates = append(a.RootCertificates, KeySpec{Path: name, FileSystem: utils.Optional(fss...)}) +} + +func (a *Config) addKeyData(set *map[string]KeySpec, name string, data []byte) { + if *set == nil { + *set = map[string]KeySpec{} + } + (*set)[name] = KeySpec{Data: data} +} + +func (a *Config) AddPublicKeyData(name string, data []byte) { + a.addKeyData(&a.PublicKeys, name, data) +} + +func (a *Config) AddPrivateKeyData(name string, data []byte) { + a.addKeyData(&a.PrivateKeys, name, data) +} + +func (a *Config) AddRootCertificateData(data []byte) { + a.RootCertificates = append(a.RootCertificates, KeySpec{Data: data}) +} + +func (a *Config) AddRootCertificate(chain signutils.GenericCertificateChain) error { + certs, err := signutils.GetCertificateChain(chain, false) + if err != nil { + return err + } + a.RootCertificates = append(a.RootCertificates, KeySpec{Data: signutils.CertificateChainToPem(certs), Parsed: certs}) + return nil +} + +func (a *Config) ApplyTo(ctx cfgcpi.Context, target interface{}) error { + t, ok := target.(Context) + if !ok { + if t, ok := target.(datacontext.AttributesContext); ok { + if t.AttributesContext().IsAttributesContext() { + return errors.Wrapf(a.ApplyToRootCertsAttr(rootcertsattr.Get(t)), "applying config to certattr failed") + } + } + return cfgcpi.ErrNoContext(ConfigType) + } + return errors.Wrapf(a.ApplyToRegistry(Get(t)), "applying config failed") +} + +func (a *Config) ApplyToRootCertsAttr(attr *rootcertsattr.Attribute) error { + for i, k := range a.RootCertificates { + key, err := k.Get() + if err != nil { + return errors.Wrapf(err, "cannot get root certificate %d", i) + } + err = attr.RegisterRootCertificates(key) + if err != nil { + return errors.Wrapf(err, "invalid certificate %d", i) + } + } + return nil +} + +func (a *Config) ApplyToRegistry(registry signing.Registry) error { + for n, k := range a.PublicKeys { + key, err := k.Get() + if err != nil { + return errors.Wrapf(err, "cannot get public key %s", n) + } + registry.RegisterPublicKey(n, key) + } + for n, k := range a.PrivateKeys { + key, err := k.Get() + if err != nil { + return errors.Wrapf(err, "cannot get private key %s", n) + } + registry.RegisterPrivateKey(n, key) + } + for n, k := range a.Issuers { + registry.RegisterIssuer(n, k.Get()) + } + if a.TSAUrl != "" { + registry.SetTSAUrl(a.TSAUrl) + } + return nil +} + +const usage = ` +The config type ` + ConfigType + ` can be used to define +public and private keys. A key value might be given by one of the fields: +- path: path of file with key data +- data: base64 encoded binary data +- stringdata: data a string parsed by key handler + +
+    type: ` + ConfigType + `
+    privateKeys:
+       <name>:
+         path: <file path>
+       ...
+    publicKeys:
+       <name>:
+         data: <base64 encoded key representation>
+       ...
+    rootCertificates:
+      - path: <file path>
+
+    issuers:
+       <name>:
+         commonName: acme.org
+
+ +Issuers define an expected distinguished name for +public key certificates optionally provided together with +signatures. They support the following fields: +- commonName *string* +- organization *string array* +- organizationalUnit *string array* +- country *string array* +- locality *string array* +- province *string array* +- streetAddress *string array* +- postalCode *string array* + +At least the given values must be present in the certificate +to be accepted for a successful signature validation. + +` diff --git a/api/ocm/extensions/attrs/signingattr/setup.go b/api/ocm/extensions/attrs/signingattr/setup.go new file mode 100644 index 000000000..451141c0d --- /dev/null +++ b/api/ocm/extensions/attrs/signingattr/setup.go @@ -0,0 +1,27 @@ +package signingattr + +import ( + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/tech/signing" +) + +func init() { + datacontext.RegisterSetupHandler(datacontext.SetupHandlerFunction(setupContext)) +} + +func setupContext(mode datacontext.BuilderMode, ctx datacontext.Context) { + if octx, ok := ctx.(Context); ok { + switch mode { + case datacontext.MODE_SHARED: + fallthrough + case datacontext.MODE_DEFAULTED: + // do nothing, fallback to the default attribute lookup + case datacontext.MODE_EXTENDED: + Set(octx, signing.NewRegistry(signing.DefaultRegistry().HandlerRegistry(), signing.DefaultRegistry().KeyRegistry())) + case datacontext.MODE_CONFIGURED: + Set(octx, signing.DefaultRegistry().Copy()) + case datacontext.MODE_INITIAL: + Set(octx, signing.NewRegistry(nil, nil)) + } + } +} diff --git a/pkg/contexts/ocm/attrs/signingattr/suite_test.go b/api/ocm/extensions/attrs/signingattr/suite_test.go similarity index 100% rename from pkg/contexts/ocm/attrs/signingattr/suite_test.go rename to api/ocm/extensions/attrs/signingattr/suite_test.go diff --git a/api/ocm/extensions/blobhandler/config/type.go b/api/ocm/extensions/blobhandler/config/type.go new file mode 100644 index 000000000..076d8f974 --- /dev/null +++ b/api/ocm/extensions/blobhandler/config/type.go @@ -0,0 +1,92 @@ +package config + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/config" + cfgcpi "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/blobhandler" + "ocm.software/ocm/api/ocm/extensions/download" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ConfigType = "uploader.ocm" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX + ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" +) + +func init() { + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1, usage)) +} + +// Config describes a memory based config interface. +type Config struct { + runtime.ObjectVersionedType `json:",inline"` + Registrations []Registration `json:"registrations,omitempty"` +} + +type Registration struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` + blobhandler.HandlerOptions `json:",inline"` + Config blobhandler.HandlerConfig `json:"config,omitempty"` +} + +// New creates a new memory ConfigSpec. +func New() *Config { + return &Config{ + ObjectVersionedType: runtime.NewVersionedObjectType(ConfigType), + } +} + +func (a *Config) GetType() string { + return ConfigType +} + +func (a *Config) AddRegistration(hdlrs ...Registration) error { + for i, h := range hdlrs { + if h.Name == "" { + return fmt.Errorf("handler registration %d requires a name", i) + } + } + a.Registrations = append(a.Registrations, hdlrs...) + return nil +} + +func (a *Config) ApplyTo(ctx cfgcpi.Context, target interface{}) error { + t, ok := target.(cpi.Context) + if !ok { + return config.ErrNoContext(ConfigType) + } + reg := blobhandler.For(t) + for _, h := range a.Registrations { + accepted, err := reg.RegisterByName(h.Name, t, h.Config, &h.HandlerOptions) + if err != nil { + return errors.Wrapf(err, "registering upload handler %q[%s]", h.Name, h.Description) + } + if !accepted { + download.Logger(t).Info("no matching handler for configuration %q[%s]", h.Name, h.Description) + } + } + return nil +} + +const usage = ` +The config type ` + ConfigType + ` can be used to define a list +of pre-configured download handler registrations (see ocm ocm-downloadhandlers): + +
+    type: ` + ConfigType + `
+    descrition: "my standard download handler configuration"
+    handlers:
+      - name: oci/artifact
+        artifactType: ociImage
+        mimeType:
+        config: ...
+      ...
+
+` diff --git a/pkg/contexts/ocm/blobhandler/doc.go b/api/ocm/extensions/blobhandler/doc.go similarity index 100% rename from pkg/contexts/ocm/blobhandler/doc.go rename to api/ocm/extensions/blobhandler/doc.go diff --git a/api/ocm/extensions/blobhandler/handlers/generic/maven/blobhandler.go b/api/ocm/extensions/blobhandler/handlers/generic/maven/blobhandler.go new file mode 100644 index 000000000..b0cd19c80 --- /dev/null +++ b/api/ocm/extensions/blobhandler/handlers/generic/maven/blobhandler.go @@ -0,0 +1,131 @@ +package maven + +import ( + "crypto" + + "github.com/mandelsoft/goutils/finalizer" + "github.com/mandelsoft/goutils/ioutils" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/credentials/builtin/maven/identity" + "ocm.software/ocm/api/ocm/cpi" + access "ocm.software/ocm/api/ocm/extensions/accessmethods/maven" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/tech/maven" + mavenblob "ocm.software/ocm/api/utils/blobaccess/maven" + "ocm.software/ocm/api/utils/iotools" + "ocm.software/ocm/api/utils/logging" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/api/utils/tarutils" +) + +const BlobHandlerName = "ocm/" + resourcetypes.MAVEN_PACKAGE + +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) + + if hint == "" { + log.Warn("maven package hint is empty, skipping upload") + return nil, nil + } + // check conditions + if b.spec == nil { + return nil, nil + } + mimeType := blob.MimeType() + if resourcetypes.MAVEN_PACKAGE != 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.IsPackage() { + 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 := ioutils.NewDupReadCloser(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/api/ocm/extensions/blobhandler/handlers/generic/maven/blobhandler_test.go b/api/ocm/extensions/blobhandler/handlers/generic/maven/blobhandler_test.go new file mode 100644 index 000000000..c6147027c --- /dev/null +++ b/api/ocm/extensions/blobhandler/handlers/generic/maven/blobhandler_test.go @@ -0,0 +1,104 @@ +package maven_test + +import ( + "encoding/json" + "os" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + + "github.com/mandelsoft/goutils/sliceutils" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/ocm/elements" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + me "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/generic/maven" + "ocm.software/ocm/api/ocm/extensions/repositories/composition" + "ocm.software/ocm/api/tech/maven" + "ocm.software/ocm/api/tech/maven/maventest" + mavenblob "ocm.software/ocm/api/utils/blobaccess/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()) + }) + + AfterEach(func() { + env.Cleanup() + }) + + 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.BlobAccessForCoords(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_PACKAGE)), 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/api/ocm/extensions/blobhandler/handlers/generic/maven/registration.go b/api/ocm/extensions/blobhandler/handlers/generic/maven/registration.go new file mode 100644 index 000000000..34b017f3f --- /dev/null +++ b/api/ocm/extensions/blobhandler/handlers/generic/maven/registration.go @@ -0,0 +1,109 @@ +package maven + +import ( + "encoding/json" + "fmt" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/general" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/ocm/cpi" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/tech/maven" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/api/utils/registrations" +) + +func init() { + cpi.RegisterBlobHandlerRegistrationHandler(BlobHandlerName, &RegistrationHandler{}) +} + +type Config struct { + 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 { + return nil + } + var raw rawConfig + err = json.Unmarshal(data, &raw) + if err != nil { + return err + } + *c = Config(raw) + + return nil +} + +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.MAVEN_PACKAGE, handler) + } + if config == nil { + return true, fmt.Errorf("maven target specification required") + } + cfg, err := registrations.DecodeConfig[Config](config) + if err != nil { + return true, errors.Wrapf(err, "blob handler configuration") + } + + ctx.BlobHandlers().Register(NewArtifactHandler(cfg), + cpi.ForArtifactType(resourcetypes.MAVEN_PACKAGE), + cpi.ForMimeType(mime.MIME_TGZ), + cpi.NewBlobHandlerOptions(olist...), + ) + + return true, nil +} + +func (r *RegistrationHandler) GetHandlers(_ cpi.Context) registrations.HandlerInfos { + 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 maven repository. +`, + ) +} diff --git a/api/ocm/extensions/blobhandler/handlers/generic/maven/registration_test.go b/api/ocm/extensions/blobhandler/handlers/generic/maven/registration_test.go new file mode 100644 index 000000000..5ba69eb09 --- /dev/null +++ b/api/ocm/extensions/blobhandler/handlers/generic/maven/registration_test.go @@ -0,0 +1,22 @@ +package maven_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/generic/maven" + "ocm.software/ocm/api/utils/registrations" +) + +var _ = Describe("Config deserialization Test Environment", func() { + It("deserializes string", func() { + cfg := Must(registrations.DecodeConfig[maven.Config]("test")) + Expect(cfg).To(Equal(&maven.Config{Url: "test"})) + }) + + It("deserializes struct", func() { + 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/api/ocm/extensions/blobhandler/handlers/generic/maven/suite_test.go similarity index 100% rename from pkg/contexts/ocm/blobhandler/handlers/generic/maven/suite_test.go rename to api/ocm/extensions/blobhandler/handlers/generic/maven/suite_test.go diff --git a/api/ocm/extensions/blobhandler/handlers/generic/npm/blobhandler.go b/api/ocm/extensions/blobhandler/handlers/generic/npm/blobhandler.go new file mode 100644 index 000000000..d37cebe7d --- /dev/null +++ b/api/ocm/extensions/blobhandler/handlers/generic/npm/blobhandler.go @@ -0,0 +1,172 @@ +package npm + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + + crds "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/npm" + npmLogin "ocm.software/ocm/api/tech/npm" + "ocm.software/ocm/api/utils/logging" + "ocm.software/ocm/api/utils/mime" +) + +const BLOB_HANDLER_NAME = "ocm/npmPackage" + +type artifactHandler struct { + spec *Config +} + +func NewArtifactHandler(repospec *Config) cpi.BlobHandler { + return &artifactHandler{repospec} +} + +func (b *artifactHandler) StoreBlob(blob cpi.BlobAccess, _ string, _ string, _ cpi.AccessSpec, ctx cpi.StorageContext) (cpi.AccessSpec, error) { + if b.spec == nil { + return nil, nil + } + + mimeType := blob.MimeType() + if mime.MIME_TGZ != mimeType && mime.MIME_TGZ_ALT != mimeType { + return nil, nil + } + + if b.spec.Url == "" { + return nil, fmt.Errorf("NPM registry url not provided") + } + + blobReader, err := blob.Reader() + if err != nil { + return nil, err + } + defer blobReader.Close() + + data, err := io.ReadAll(blobReader) + if err != nil { + return nil, err + } + + // read package.json from tarball to get name, version, etc. + log := logging.Context().Logger(npmLogin.REALM) + log.Debug("reading package.json from tarball") + var pkg *Package + pkg, err = prepare(data) + if err != nil { + return nil, err + } + tbName := pkg.Name + "-" + pkg.Version + ".tgz" + pkg.Dist.Tarball = b.spec.Url + pkg.Name + "/-/" + tbName + log = log.WithValues("package", pkg.Name, "version", pkg.Version) + log.Debug("identified") + + // check if package exists + exists, err := packageExists(b.spec.Url, *pkg, ctx.GetContext()) + if err != nil { + return nil, err + } + if exists { + log.Debug("package+version already exists, skipping upload") + return npm.New(b.spec.Url, pkg.Name, pkg.Version), nil + } + + // prepare body for upload + body := Body{ + ID: pkg.Name, + Name: pkg.Name, + Description: pkg.Description, + } + body.Versions = map[string]*Package{ + pkg.Version: pkg, + } + body.DistTags.Latest = pkg.Version + body.Readme = pkg.Readme + body.Attachments = map[string]*Attachment{ + tbName: NewAttachment(data), + } + marshal, err := json.Marshal(body) + if err != nil { + return nil, err + } + + // prepare PUT request + req, err := http.NewRequestWithContext(context.Background(), http.MethodPut, b.spec.Url+"/"+url.PathEscape(pkg.Name), bytes.NewReader(marshal)) + if err != nil { + return nil, err + } + err = npmLogin.Authorize(req, ctx.GetContext(), b.spec.Url, pkg.Name) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") + + // send PUT request - upload tgz + client := http.Client{} + log.Debug("uploading") + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusCreated { + all, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + return nil, fmt.Errorf("http (%d) - failed to upload package: %s", resp.StatusCode, string(all)) + } + log.Debug("successfully uploaded") + return npm.New(b.spec.Url, pkg.Name, pkg.Version), nil +} + +// Check if package already exists in npm registry. If it does, checks if it's the same. +func packageExists(repoUrl string, pkg Package, ctx crds.ContextProvider) (bool, error) { + client := http.Client{} + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, repoUrl+"/"+url.PathEscape(pkg.Name)+"/"+url.PathEscape(pkg.Version), nil) + if err != nil { + return false, err + } + err = npmLogin.Authorize(req, ctx, repoUrl, pkg.Name) + if err != nil { + return false, err + } + resp, err := client.Do(req) + if err != nil { + return false, err + } + defer resp.Body.Close() + if resp.StatusCode == http.StatusNotFound { + // artifact doesn't exist, it's safe to upload + return false, nil + } + + // artifact exists, let's check if it's the same + all, err := io.ReadAll(resp.Body) + if err != nil { + return false, err + } + if resp.StatusCode != http.StatusOK { + return false, fmt.Errorf("http (%d) - %s", resp.StatusCode, string(all)) + } + var data map[string]interface{} + err = json.Unmarshal(all, &data) + if err != nil { + return false, err + } + dist := data["dist"].(map[string]interface{}) + if pkg.Dist.Integrity == dist["integrity"] { + // sha-512 sum is the same, we can skip the upload + return true, nil + } + if pkg.Dist.Shasum == dist["shasum"] { + // sha-1 sum is the same, we can skip the upload + return true, nil + } + + return false, fmt.Errorf("artifact already exists but has different shasum or integrity") +} diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/npm/publish.go b/api/ocm/extensions/blobhandler/handlers/generic/npm/publish.go similarity index 100% rename from pkg/contexts/ocm/blobhandler/handlers/generic/npm/publish.go rename to api/ocm/extensions/blobhandler/handlers/generic/npm/publish.go diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/npm/publish_test.go b/api/ocm/extensions/blobhandler/handlers/generic/npm/publish_test.go similarity index 100% rename from pkg/contexts/ocm/blobhandler/handlers/generic/npm/publish_test.go rename to api/ocm/extensions/blobhandler/handlers/generic/npm/publish_test.go diff --git a/api/ocm/extensions/blobhandler/handlers/generic/npm/registration.go b/api/ocm/extensions/blobhandler/handlers/generic/npm/registration.go new file mode 100644 index 000000000..fc95d89d7 --- /dev/null +++ b/api/ocm/extensions/blobhandler/handlers/generic/npm/registration.go @@ -0,0 +1,75 @@ +package npm + +import ( + "encoding/json" + "fmt" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/ocm/cpi" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/api/utils/registrations" +) + +type Config struct { + Url string `json:"url"` +} + +type rawConfig Config + +func (c *Config) UnmarshalJSON(data []byte) error { + err := json.Unmarshal(data, &c.Url) + if err == nil { + return nil + } + var raw rawConfig + err = json.Unmarshal(data, &raw) + if err != nil { + return err + } + *c = Config(raw) + + return nil +} + +func init() { + cpi.RegisterBlobHandlerRegistrationHandler(BLOB_HANDLER_NAME, &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 npmjsArtifact handler %q", handler) + } + if config == nil { + return true, fmt.Errorf("npm target specification required") + } + cfg, err := registrations.DecodeConfig[Config](config) + if err != nil { + return true, errors.Wrapf(err, "blob handler configuration") + } + + ctx.BlobHandlers().Register(NewArtifactHandler(cfg), + cpi.ForArtifactType(resourcetypes.NPM_PACKAGE), + cpi.ForMimeType(mime.MIME_TGZ), + cpi.NewBlobHandlerOptions(olist...), + ) + + return true, nil +} + +func (r *RegistrationHandler) GetHandlers(_ cpi.Context) registrations.HandlerInfos { + return registrations.NewLeafHandlerInfo("uploading npm artifacts", ` +The `+BLOB_HANDLER_NAME+` uploader is able to upload npm artifacts +as artifact archive according to the npm package 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 npm repository. +`, + ) +} diff --git a/api/ocm/extensions/blobhandler/handlers/generic/npm/registration_test.go b/api/ocm/extensions/blobhandler/handlers/generic/npm/registration_test.go new file mode 100644 index 000000000..f18e85098 --- /dev/null +++ b/api/ocm/extensions/blobhandler/handlers/generic/npm/registration_test.go @@ -0,0 +1,22 @@ +package npm_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/generic/npm" + "ocm.software/ocm/api/utils/registrations" +) + +var _ = Describe("Config deserialization Test Environment", func() { + It("deserializes string", func() { + cfg := Must(registrations.DecodeConfig[npm.Config]("test")) + Expect(cfg).To(Equal(&npm.Config{Url: "test"})) + }) + + It("deserializes struct", func() { + cfg := Must(registrations.DecodeConfig[npm.Config](`{"url":"test"}`)) + Expect(cfg).To(Equal(&npm.Config{Url: "test"})) + }) +}) diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/npm/suite_test.go b/api/ocm/extensions/blobhandler/handlers/generic/npm/suite_test.go similarity index 100% rename from pkg/contexts/ocm/blobhandler/handlers/generic/npm/suite_test.go rename to api/ocm/extensions/blobhandler/handlers/generic/npm/suite_test.go diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/npm/testdata/testdata.tgz b/api/ocm/extensions/blobhandler/handlers/generic/npm/testdata/testdata.tgz similarity index 100% rename from pkg/contexts/ocm/blobhandler/handlers/generic/npm/testdata/testdata.tgz rename to api/ocm/extensions/blobhandler/handlers/generic/npm/testdata/testdata.tgz diff --git a/api/ocm/extensions/blobhandler/handlers/generic/ocirepo/blobhandler.go b/api/ocm/extensions/blobhandler/handlers/generic/ocirepo/blobhandler.go new file mode 100644 index 000000000..2c48ea05b --- /dev/null +++ b/api/ocm/extensions/blobhandler/handlers/generic/ocirepo/blobhandler.go @@ -0,0 +1,126 @@ +package ocirepo + +import ( + "encoding/json" + "path" + "strings" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/sliceutils" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + "ocm.software/ocm/api/ocm/extensions/attrs/ociuploadattr" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +func init() { + for _, mime := range artdesc.ArchiveBlobTypes() { + cpi.RegisterBlobHandler(NewArtifactHandler(), cpi.ForMimeType(mime), cpi.WithPrio(10)) + } +} + +//////////////////////////////////////////////////////////////////////////////// + +// artifactHandler stores artifact blobs as OCIArtifacts regardless of the +// intended OCM target repository. +// It acts on the OCI upload attribute to determine the target OCI repository. +// If none is configured, it does nothing. +type artifactHandler struct { + spec *ociuploadattr.Attribute +} + +func NewArtifactHandler(repospec ...*ociuploadattr.Attribute) cpi.BlobHandler { + return &artifactHandler{utils.Optional(repospec...)} +} + +func (b *artifactHandler) StoreBlob(blob cpi.BlobAccess, artType, hint string, global cpi.AccessSpec, ctx cpi.StorageContext) (cpi.AccessSpec, error) { + attr := b.spec + if attr == nil { + attr = ociuploadattr.Get(ctx.GetContext()) + } + if attr == nil { + return nil, nil + } + + mediaType := blob.MimeType() + if !artdesc.IsOCIMediaType(mediaType) || (!strings.HasSuffix(mediaType, "+tar") && !strings.HasSuffix(mediaType, "+tar+gzip")) { + return nil, nil + } + + repo, base, prefix, err := attr.GetInfo(ctx.GetContext()) + if err != nil { + return nil, err + } + + // this section is for logging, only + target, err := json.Marshal(repo.GetSpecification()) + if err != nil { + return nil, errors.Wrapf(err, "cannot marshal target specification") + } + values := []interface{}{ + "arttype", artType, + "mediatype", mediaType, + "hint", hint, + "target", string(target), + } + if m, ok := blob.(blobaccess.AnnotatedBlobAccess[cpi.AccessMethod]); ok { + // prepare for optimized point to point implementation + cpi.BlobHandlerLogger(ctx.GetContext()).Debug("oci generic artifact handler with ocm access source", + sliceutils.CopyAppend[any](values, "sourcetype", m.Source().AccessSpec().GetType())..., + ) + } else { + cpi.BlobHandlerLogger(ctx.GetContext()).Debug("oci generic artifact handler", values...) + } + + var namespace oci.NamespaceAccess + var version string + var name string + var tag string + + if hint == "" { + name = path.Join(prefix, ctx.TargetComponentName()) + } else { + i := strings.LastIndex(hint, ":") + if i > 0 { + version = hint[i:] + name = path.Join(prefix, hint[:i]) + tag = version[1:] // remove colon + } else { + name = hint + } + } + namespace, err = repo.LookupNamespace(name) + if err != nil { + return nil, errors.Wrapf(err, "lookup namespace %s in target repository %s", name, attr.Ref) + } + defer namespace.Close() + + set, err := artifactset.OpenFromBlob(accessobj.ACC_READONLY, blob) + if err != nil { + return nil, err + } + defer set.Close() + digest := set.GetMain() + if version == "" { + version = "@" + digest.String() + } + art, err := set.GetArtifact(digest.String()) + if err != nil { + return nil, err + } + defer art.Close() + + err = artifactset.TransferArtifact(art, namespace, oci.AsTags(tag)...) + if err != nil { + return nil, err + } + + ref := base.ComposeRef(namespace.GetNamespace() + version) + return ociartifact.New(ref), nil +} diff --git a/api/ocm/extensions/blobhandler/handlers/generic/ocirepo/registration.go b/api/ocm/extensions/blobhandler/handlers/generic/ocirepo/registration.go new file mode 100644 index 000000000..8bb6a2cb5 --- /dev/null +++ b/api/ocm/extensions/blobhandler/handlers/generic/ocirepo/registration.go @@ -0,0 +1,77 @@ +package ocirepo + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/attrs/ociuploadattr" + "ocm.software/ocm/api/utils/listformat" + "ocm.software/ocm/api/utils/registrations" +) + +type Config = ociuploadattr.Attribute + +func init() { + cpi.RegisterBlobHandlerRegistrationHandler("ocm/ociArtifacts", &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 ociArtifact handler %q", handler) + } + if config == nil { + return true, fmt.Errorf("oci target specification required") + } + attr, err := registrations.DecodeConfig[Config](config, ociuploadattr.AttributeType{}.Decode) + if err != nil { + return true, errors.Wrapf(err, "blob handler configuration") + } + + var mimes []string + opts := cpi.NewBlobHandlerOptions(olist...) + if opts.MimeType != "" { + found := false + for _, a := range artdesc.ArchiveBlobTypes() { + if a == opts.MimeType { + found = true + break + } + } + if !found { + return true, fmt.Errorf("unexpected type mime type %q for oci blob handler target", opts.MimeType) + } + mimes = append(mimes, opts.MimeType) + } else { + mimes = artdesc.ArchiveBlobTypes() + } + + h := NewArtifactHandler(attr) + for _, m := range mimes { + opts.MimeType = m + ctx.BlobHandlers().Register(h, opts) + } + + return true, nil +} + +func (r *RegistrationHandler) GetHandlers(ctx cpi.Context) registrations.HandlerInfos { + return registrations.NewLeafHandlerInfo("downloading OCI artifacts", ` +The ociArtifacts downloader is able to download OCI artifacts +as artifact archive according to the OCI distribution spec. +The following artifact media types are supported: +`+listformat.FormatList("", artdesc.ArchiveBlobTypes()...)+` +By default, it is registered for these mimetypes. + +It accepts a config with the following fields: +`+listformat.FormatMapElements("", ociuploadattr.AttributeDescription())+` +Alternatively, a single string value can be given representing an OCI repository +reference.`, + ) +} diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/ocirepo/suite_test.go b/api/ocm/extensions/blobhandler/handlers/generic/ocirepo/suite_test.go similarity index 100% rename from pkg/contexts/ocm/blobhandler/handlers/generic/ocirepo/suite_test.go rename to api/ocm/extensions/blobhandler/handlers/generic/ocirepo/suite_test.go diff --git a/api/ocm/extensions/blobhandler/handlers/generic/ocirepo/upload_test.go b/api/ocm/extensions/blobhandler/handlers/generic/ocirepo/upload_test.go new file mode 100644 index 000000000..758973d3f --- /dev/null +++ b/api/ocm/extensions/blobhandler/handlers/generic/ocirepo/upload_test.go @@ -0,0 +1,244 @@ +package ocirepo_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + . "ocm.software/ocm/api/oci/testhelper" + + "ocm.software/ocm/api/oci" + ctfoci "ocm.software/ocm/api/oci/extensions/repositories/ctf" + "ocm.software/ocm/api/oci/grammar" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/attrs/ociuploadattr" + "ocm.software/ocm/api/ocm/extensions/blobhandler" + "ocm.software/ocm/api/ocm/extensions/repositories/comparch" + ctfocm "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/tools/transfer" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" +) + +const ( + COMP = "github.com/compa" + VERS = "1.0.0" + CA = "ca" + CTF = "ctf" + COPY = "ctf.copy" + TARGET = "/tmp/target" +) + +const ( + OCIHOST = "alias" + OCIPATH = "/tmp/source" +) + +var _ = Describe("upload", func() { + var env *Builder + + BeforeEach(func() { + env = NewBuilder() + + // fake OCI registry + FakeOCIRepo(env, OCIPATH, OCIHOST) + + env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { + OCIManifest1(env) + }) + + env.OCICommonTransport(TARGET, accessio.FormatDirectory) + + env.ComponentArchive(CA, accessio.FormatDirectory, COMP, VERS, func() { + env.Provider("mandelsoft") + env.Resource("value", "", resourcetypes.OCI_IMAGE, v1.LocalRelation, func() { + env.Access( + ociartifact.New(oci.StandardOCIRef(OCIHOST+".alias", OCINAMESPACE, OCIVERSION)), + ) + }) + }) + + ca := Must(comparch.Open(env.OCMContext(), accessobj.ACC_READONLY, CA, 0, env)) + oca := accessio.OnceCloser(ca) + defer Close(oca) + + ctf := Must(ctfocm.Create(env.OCMContext(), accessobj.ACC_CREATE, CTF, 0o700, env)) + octf := accessio.OnceCloser(ctf) + defer Close(octf) + + handler := Must(standard.New(standard.ResourcesByValue())) + + MustBeSuccessful(transfer.TransferVersion(nil, nil, ca, ctf, handler)) + + // now we have a transport archive with local blob for the image + }) + + AfterEach(func() { + env.Cleanup() + }) + + It("validated original oci manifest", func() { + ctx := env.OCMContext() + + ocirepo := Must(ctfoci.Open(ctx, accessobj.ACC_READONLY, OCIPATH, 0o700, env)) + defer Close(ocirepo, "ocoirepo") + + ns := Must(ocirepo.LookupNamespace(OCINAMESPACE)) + defer Close(ns, "namespace") + + art := Must(ns.GetArtifact(OCIVERSION)) + defer Close(art, "artifact") + + Expect(art.Digest().Encoded()).To(Equal(D_OCIMANIFEST1)) + }) + + It("validated original digest", func() { + ctx := env.OCMContext() + + ctf := Must(ctfocm.Open(ctx, accessobj.ACC_READONLY, CTF, 0o700, env)) + defer Close(ctf, "ctf") + + cv := Must(ctf.LookupComponentVersion(COMP, VERS)) + defer Close(cv, "component version") + + ra := Must(cv.GetResourceByIndex(0)) + acc := Must(ra.Access()) + Expect(acc.GetKind()).To(Equal(localblob.Type)) + + Expect(ra.Meta().Digest).To(Equal(DS_OCIMANIFEST1)) + }) + + It("transfers oci artifact", func() { + ctx := env.OCMContext() + + ctf := Must(ctfocm.Open(ctx, accessobj.ACC_READONLY, CTF, 0o700, env)) + defer Close(ctf, "ctf") + + cv := Must(ctf.LookupComponentVersion(COMP, VERS)) + ocv := accessio.OnceCloser(cv) + defer Close(ocv) + ra := Must(cv.GetResourceByIndex(0)) + acc := Must(ra.Access()) + Expect(acc.GetKind()).To(Equal(localblob.Type)) + + // transfer component + copy := Must(ctfocm.Create(ctx, accessobj.ACC_CREATE, COPY, 0o700, env)) + ocopy := accessio.OnceCloser(copy) + defer Close(ocopy) + + // prepare upload to target OCI repo + attr := ociuploadattr.New(TARGET + grammar.RepositorySeparator + grammar.RepositorySeparator + "copy") + ociuploadattr.Set(ctx, attr) + + MustBeSuccessful(transfer.TransferVersion(nil, nil, cv, copy, nil)) + + // check type + cv2 := Must(copy.LookupComponentVersion(COMP, VERS)) + ocv2 := accessio.OnceCloser(cv2) + defer Close(ocv2) + ra = Must(cv2.GetResourceByIndex(0)) + Expect(ra.Meta().Digest).To(Equal(DS_OCIMANIFEST1)) + acc = Must(ra.Access()) + Expect(acc.GetKind()).To(Equal(ociartifact.Type)) + val := Must(ctx.AccessSpecForSpec(acc)) + // TODO: the result is invalid for ctf: better handling for ctf refs + Expect(val.(*ociartifact.AccessSpec).ImageReference).To(Equal("/tmp/target//copy/ocm/value:v2.0")) + + attr.Close() + target, err := ctfoci.Open(ctx.OCIContext(), accessobj.ACC_READONLY, TARGET, 0, env) + Expect(err).To(Succeed()) + defer Close(target) + Expect(target.ExistsArtifact("copy/ocm/value", "v2.0")).To(BeTrue()) + }) + + It("transfers oci artifact with named handler and object config", func() { + ctx := env.OCMContext() + + ctf := Must(ctfocm.Open(ctx, accessobj.ACC_READONLY, CTF, 0o700, env)) + defer Close(ctf, "ctf") + + cv := Must(ctf.LookupComponentVersion(COMP, VERS)) + ocv := accessio.OnceCloser(cv) + defer Close(ocv) + ra := Must(cv.GetResourceByIndex(0)) + acc := Must(ra.Access()) + Expect(acc.GetKind()).To(Equal(localblob.Type)) + + // transfer component + copy := Must(ctfocm.Create(ctx, accessobj.ACC_CREATE, COPY, 0o700, env)) + ocopy := accessio.OnceCloser(copy) + defer Close(ocopy) + + // prepare upload to target OCI repo + attr := ociuploadattr.New(TARGET + grammar.RepositorySeparator + grammar.RepositorySeparator + "copy") + MustBeSuccessful(blobhandler.RegisterHandlerByName(ctx, "ocm/ociArtifacts", attr)) + + MustBeSuccessful(transfer.TransferVersion(nil, nil, cv, copy, nil)) + + // check type + cv2 := Must(copy.LookupComponentVersion(COMP, VERS)) + ocv2 := accessio.OnceCloser(cv2) + defer Close(ocv2) + ra = Must(cv2.GetResourceByIndex(0)) + acc = Must(ra.Access()) + Expect(acc.GetKind()).To(Equal(ociartifact.Type)) + val := Must(ctx.AccessSpecForSpec(acc)) + // TODO: the result is invalid for ctf: better handling for ctf refs + Expect(val.(*ociartifact.AccessSpec).ImageReference).To(Equal("/tmp/target//copy/ocm/value:v2.0")) + + // attr.Close() + env.OCMContext().Finalize() + target, err := ctfoci.Open(ctx.OCIContext(), accessobj.ACC_READONLY, TARGET, 0, env) + Expect(err).To(Succeed()) + defer Close(target) + Expect(target.ExistsArtifact("copy/ocm/value", "v2.0")).To(BeTrue()) + }) + + It("transfers oci artifact with named handler and string config", func() { + ctx := env.OCMContext() + + ctf := Must(ctfocm.Open(ctx, accessobj.ACC_READONLY, CTF, 0o700, env)) + defer Close(ctf, "ctf") + + cv := Must(ctf.LookupComponentVersion(COMP, VERS)) + ocv := accessio.OnceCloser(cv) + defer Close(ocv) + ra := Must(cv.GetResourceByIndex(0)) + acc := Must(ra.Access()) + Expect(acc.GetKind()).To(Equal(localblob.Type)) + + // transfer component + copy := Must(ctfocm.Create(ctx, accessobj.ACC_CREATE, COPY, 0o700, env)) + ocopy := accessio.OnceCloser(copy) + defer Close(ocopy) + + // prepare upload to target OCI repo + // attr := ociuploadattr.New(TARGET + grammar.RepositorySeparator + grammar.RepositorySeparator + "copy") + attr := TARGET + grammar.RepositorySeparator + grammar.RepositorySeparator + "copy" + MustBeSuccessful(blobhandler.RegisterHandlerByName(ctx, "ocm/ociArtifacts", attr)) + + MustBeSuccessful(transfer.TransferVersion(nil, nil, cv, copy, nil)) + + // check type + cv2 := Must(copy.LookupComponentVersion(COMP, VERS)) + ocv2 := accessio.OnceCloser(cv2) + defer Close(ocv2) + ra = Must(cv2.GetResourceByIndex(0)) + acc = Must(ra.Access()) + Expect(acc.GetKind()).To(Equal(ociartifact.Type)) + val := Must(ctx.AccessSpecForSpec(acc)) + // TODO: the result is invalid for ctf: better handling for ctf refs + Expect(val.(*ociartifact.AccessSpec).ImageReference).To(Equal("/tmp/target//copy/ocm/value:v2.0")) + + // attr.Close() + env.OCMContext().Finalize() + target, err := ctfoci.Open(ctx.OCIContext(), accessobj.ACC_READONLY, TARGET, 0, env) + Expect(err).To(Succeed()) + defer Close(target) + Expect(target.ExistsArtifact("copy/ocm/value", "v2.0")).To(BeTrue()) + }) +}) diff --git a/api/ocm/extensions/blobhandler/handlers/generic/plugin/blobhandler.go b/api/ocm/extensions/blobhandler/handlers/generic/plugin/blobhandler.go new file mode 100644 index 000000000..c9ae369c4 --- /dev/null +++ b/api/ocm/extensions/blobhandler/handlers/generic/plugin/blobhandler.go @@ -0,0 +1,89 @@ +package plugin + +import ( + "encoding/json" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/identity/hostpath" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/plugin" + "ocm.software/ocm/api/ocm/plugin/descriptor" + "ocm.software/ocm/api/utils/accessio" +) + +// pluginHandler delegates storage of blobs to a plugin based handler. +type pluginHandler struct { + plugin plugin.Plugin + name string + target json.RawMessage + targetinfo *plugin.UploadTargetSpecInfo +} + +func New(p plugin.Plugin, name string, target json.RawMessage) (cpi.BlobHandler, error) { + var err error + + ud := p.GetUploaderDescriptor(name) + if ud == nil { + return nil, errors.ErrUnknown(descriptor.KIND_UPLOADER, name, p.Name()) + } + + var info *plugin.UploadTargetSpecInfo + if target != nil { + info, err = p.ValidateUploadTarget(name, target) + if err != nil { + return nil, err + } + } + return &pluginHandler{ + plugin: p, + name: name, + target: target, + targetinfo: info, + }, nil +} + +func (b *pluginHandler) StoreBlob(blob cpi.BlobAccess, artType, hint string, global cpi.AccessSpec, ctx cpi.StorageContext) (acc cpi.AccessSpec, err error) { + var creds credentials.Credentials + + if b.targetinfo != nil { + if len(b.targetinfo.ConsumerId) > 0 { + creds, err = credentials.CredentialsForConsumer(ctx.GetContext(), b.targetinfo.ConsumerId, hostpath.IdentityMatcher(b.targetinfo.ConsumerId.Type())) + if err != nil { + return nil, err + } + } + } + + target := b.target + + if b.target == nil { + target, err = json.Marshal(ctx.TargetComponentRepository().GetSpecification()) + if err != nil { + return nil, errors.Wrapf(err, "cannot marshal target repo spec") + } + } + + cpi.BlobHandlerLogger(ctx.GetContext()).Debug("plugin blob handler", + "plugin", b.plugin.Name(), + "uploader", b.name, + "arttype", artType, + "mediatype", blob.MimeType(), + "hint", hint, + "target", string(target), + ) + + var creddata json.RawMessage + if creds != nil { + creddata, err = json.Marshal(creds.Properties()) + if err != nil { + return nil, errors.Wrapf(err, "cannot marshal credentials") + } + } + + r := accessio.NewOndemandReader(blob) + defer errors.PropagateError(&err, r.Close) + + return b.plugin.Put(b.name, r, artType, blob.MimeType(), hint, creddata, target) +} diff --git a/api/ocm/extensions/blobhandler/handlers/generic/plugin/registration.go b/api/ocm/extensions/blobhandler/handlers/generic/plugin/registration.go new file mode 100644 index 000000000..5a4743e43 --- /dev/null +++ b/api/ocm/extensions/blobhandler/handlers/generic/plugin/registration.go @@ -0,0 +1,120 @@ +package plugin + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/attrs/plugincacheattr" + "ocm.software/ocm/api/ocm/plugin" + "ocm.software/ocm/api/utils/registrations" +) + +type Config = json.RawMessage + +func init() { + cpi.RegisterBlobHandlerRegistrationHandler("plugin", &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) { + path := cpi.NewNamePath(handler) + + if config == nil { + return true, fmt.Errorf("target specification required") + } + + if len(path) < 1 || len(path) > 2 { + return true, fmt.Errorf("plugin handler must be of the form [/]") + } + + opts := cpi.NewBlobHandlerOptions(olist...) + + name := "" + if len(path) > 1 { + name = path[1] + } + + attr, err := registrations.DecodeAnyConfig(config) + if err != nil { + return true, errors.Wrapf(err, "plugin upload handler target config for %s/%s", path[0], name) + } + + _, _, err = RegisterBlobHandler(ctx, path[0], name, opts.ArtifactType, opts.MimeType, attr) + return true, err +} + +func RegisterBlobHandler(ctx ocm.Context, pname, name string, artType, mediaType string, target json.RawMessage) (string, plugin.UploaderKeySet, error) { + set := plugincacheattr.Get(ctx) + if set == nil { + return "", nil, errors.ErrUnknown(plugin.KIND_PLUGIN, pname) + } + + p := set.Get(pname) + if p == nil { + return "", nil, errors.ErrUnknown(plugin.KIND_PLUGIN, pname) + } + + if name != "" { + if p.GetUploaderDescriptor(name) == nil { + return "", nil, fmt.Errorf("uploader %s not found in plugin %q", name, pname) + } + } + keys := plugin.UploaderKeySet{}.Add(plugin.UploaderKey{}.SetArtifact(artType, mediaType)) + d := p.LookupUploader(name, artType, mediaType) + + if len(d) == 0 { + keys = p.LookupUploaderKeys(name, artType, mediaType) + if len(keys) == 0 { + if name == "" { + return "", nil, fmt.Errorf("no uploader found for [art:%q, media:%q]", artType, mediaType) + } + return "", nil, fmt.Errorf("uploader %s not valid for [art:%q, media:%q]", name, artType, mediaType) + } + d = p.LookupUploadersForKeys(name, keys) + } + if len(d) > 1 { + return "", nil, fmt.Errorf("multiple uploaders found for [art:%q, media:%q]: %s", artType, mediaType, strings.Join(d.GetNames(), ", ")) + } + h, err := New(p, d[0].Name, target) + if err != nil { + return d[0].Name, nil, err + } + for k := range keys { + ctx.BlobHandlers().Register(h, cpi.ForArtifactType(k.GetArtifactType()), cpi.ForMimeType(k.GetMediaType())) + } + return d[0].Name, keys, nil +} + +func (r *RegistrationHandler) GetHandlers(ctx cpi.Context) registrations.HandlerInfos { + infos := registrations.NewNodeHandlerInfo("downloaders provided by plugins", + "sub namespace of the form <plugin name>/<handler>") + + set := plugincacheattr.Get(ctx) + if set == nil { + return infos + } + + for _, name := range set.PluginNames() { + p := set.Get(name) + if !p.IsValid() { + continue + } + for _, u := range p.GetDescriptor().Uploaders { + i := registrations.HandlerInfo{ + Name: name + "/" + u.GetName(), + ShortDesc: "", + Description: u.GetDescription(), + } + infos = append(infos, i) + } + } + return infos +} diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/plugin/suite_test.go b/api/ocm/extensions/blobhandler/handlers/generic/plugin/suite_test.go similarity index 100% rename from pkg/contexts/ocm/blobhandler/handlers/generic/plugin/suite_test.go rename to api/ocm/extensions/blobhandler/handlers/generic/plugin/suite_test.go diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/plugin/testdata/test b/api/ocm/extensions/blobhandler/handlers/generic/plugin/testdata/test similarity index 100% rename from pkg/contexts/ocm/blobhandler/handlers/generic/plugin/testdata/test rename to api/ocm/extensions/blobhandler/handlers/generic/plugin/testdata/test diff --git a/api/ocm/extensions/blobhandler/handlers/generic/plugin/upload_test.go b/api/ocm/extensions/blobhandler/handlers/generic/plugin/upload_test.go new file mode 100644 index 000000000..01d794590 --- /dev/null +++ b/api/ocm/extensions/blobhandler/handlers/generic/plugin/upload_test.go @@ -0,0 +1,201 @@ +//go:build unix + +package plugin_test + +import ( + "encoding/json" + "fmt" + "os" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + . "ocm.software/ocm/api/ocm/plugin/testutils" + + "github.com/mandelsoft/filepath/pkg/filepath" + "github.com/mandelsoft/vfs/pkg/osfs" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/blobhandler" + "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/generic/plugin" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + plugin2 "ocm.software/ocm/api/ocm/plugin" + "ocm.software/ocm/api/ocm/plugin/config" + "ocm.software/ocm/api/ocm/plugin/plugins" + "ocm.software/ocm/api/ocm/plugin/registration" + "ocm.software/ocm/api/ocm/tools/transfer" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/runtime" +) + +const PLUGIN = "test" + +const ( + ARCH = "ctf" + OUT = "/tmp/res" + COMP = "github.com/mandelsoft/comp" + VERS = "1.0.0" + PROVIDER = "mandelsoft" + RSCTYPE = "TestArtifact" + MEDIA = "text/plain" +) + +const ( + REPOTYPE = "test/v1" + ACCTYPE = "test/v1" + REPO = "plugin" + CONTENT = "some test content\n" + HINT = "given" +) + +type RepoSpec struct { + runtime.ObjectVersionedType + Path string `json:"path"` +} + +func NewRepoSpec(path string) *RepoSpec { + return &RepoSpec{ + ObjectVersionedType: runtime.ObjectVersionedType{Type: REPOTYPE}, + Path: path, + } +} + +type AccessSpec struct { + runtime.ObjectVersionedType + Path string `json:"path"` + MediaType string `json:"mediaType"` + Repository string `json:"repo"` +} + +func NewAccessSpec(media, path, repo string) *AccessSpec { + return &AccessSpec{ + ObjectVersionedType: runtime.ObjectVersionedType{Type: ACCTYPE}, + MediaType: media, + Path: path, + Repository: repo, + } +} + +var _ = Describe("setup plugin cache", func() { + var ctx ocm.Context + var registry plugins.Set + var repodir string + var env *Builder + var plugins TempPluginDir + + accessSpec := NewAccessSpec(MEDIA, "given", REPO) + repoSpec := NewRepoSpec(REPO) + + BeforeEach(func() { + repodir = Must(os.MkdirTemp(os.TempDir(), "uploadtest-*")) + + env = NewBuilder(nil) + ctx = env.OCMContext() + plugins, registry = Must2(ConfigureTestPlugins2(env, "testdata")) + p := registry.Get("test") + Expect(p).NotTo(BeNil()) + + ctx.ConfigContext().ApplyConfig(config.New(PLUGIN, []byte(fmt.Sprintf(`{"root": "`+repodir+`"}`))), "plugin config") + registration.RegisterExtensions(ctx) + + env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { + env.Component(COMP, func() { + env.Version(VERS, func() { + env.Provider(PROVIDER) + env.Resource("testdata", VERS, RSCTYPE, metav1.LocalRelation, func() { + env.Hint(HINT) + env.BlobStringData(MEDIA, CONTENT) + // env.Access(NewAccessSpec(MEDIA, "given", "dummy")) + }) + }) + }) + }) + }) + + AfterEach(func() { + plugins.Cleanup() + env.Cleanup() + os.RemoveAll(repodir) + }) + + It("uploads artifact", func() { + repo := Must(ctf.Open(ctx, accessobj.ACC_READONLY, ARCH, 0, env)) + defer Close(repo, "source repo") + + cv := Must(repo.LookupComponentVersion(COMP, VERS)) + defer Close(cv, "source version") + + _, _, err := plugin.RegisterBlobHandler(env.OCMContext(), "test", "", RSCTYPE, "", []byte("{}")) + fmt.Printf("error %q\n", err) + MustFailWithMessage(err, "plugin uploader test/testuploader: error processing plugin command upload: path missing in repository spec") + repospec := Must(json.Marshal(repoSpec)) + name, keys, err := plugin.RegisterBlobHandler(env.OCMContext(), "test", "", RSCTYPE, "", repospec) + MustBeSuccessful(err) + Expect(name).To(Equal("testuploader")) + Expect(keys).To(Equal(plugin2.UploaderKeySet{}.Add(plugin2.UploaderKey{}.SetArtifact(RSCTYPE, "")))) + + tgt := Must(ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) + defer Close(tgt, "target repo") + + MustBeSuccessful(transfer.TransferVersion(nil, nil, cv, tgt, Must(standard.New(standard.ResourcesByValue())))) + Expect(env.DirExists(OUT)).To(BeTrue()) + + Expect(vfs.FileExists(osfs.New(), filepath.Join(repodir, REPO, HINT))).To(BeTrue()) + + tcv := Must(tgt.LookupComponentVersion(COMP, VERS)) + defer Close(tcv, "target version") + + r := Must(tcv.GetResourceByIndex(0)) + a := Must(r.Access()) + + var spec AccessSpec + MustBeSuccessful(json.Unmarshal(Must(json.Marshal(a)), &spec)) + Expect(spec).To(Equal(*accessSpec)) + + m := Must(a.AccessMethod(tcv)) + defer Close(m, "method") + + Expect(string(Must(m.Get()))).To(Equal(CONTENT)) + }) + + It("uploads after abstract registration", func() { + repo := Must(ctf.Open(ctx, accessobj.ACC_READONLY, ARCH, 0, env)) + defer Close(repo, "source repo") + + cv := Must(repo.LookupComponentVersion(COMP, VERS)) + defer Close(cv, "source version") + + MustFailWithMessage(blobhandler.RegisterHandlerByName(ctx, "plugin/test", []byte("{}"), blobhandler.ForArtifactType(RSCTYPE)), + "plugin uploader test/testuploader: error processing plugin command upload: path missing in repository spec") + repospec := Must(json.Marshal(repoSpec)) + MustBeSuccessful(blobhandler.RegisterHandlerByName(ctx, "plugin/test", repospec)) + + tgt := Must(ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) + defer Close(tgt, "target repo") + + MustBeSuccessful(transfer.TransferVersion(nil, nil, cv, tgt, Must(standard.New(standard.ResourcesByValue())))) + Expect(env.DirExists(OUT)).To(BeTrue()) + + Expect(vfs.FileExists(osfs.New(), filepath.Join(repodir, REPO, HINT))).To(BeTrue()) + + tcv := Must(tgt.LookupComponentVersion(COMP, VERS)) + defer Close(tcv, "target version") + + r := Must(tcv.GetResourceByIndex(0)) + a := Must(r.Access()) + + var spec AccessSpec + MustBeSuccessful(json.Unmarshal(Must(json.Marshal(a)), &spec)) + Expect(spec).To(Equal(*accessSpec)) + + m := Must(a.AccessMethod(tcv)) + defer Close(m, "method") + + Expect(string(Must(m.Get()))).To(Equal(CONTENT)) + }) +}) diff --git a/api/ocm/extensions/blobhandler/handlers/init.go b/api/ocm/extensions/blobhandler/handlers/init.go new file mode 100644 index 000000000..f2b911839 --- /dev/null +++ b/api/ocm/extensions/blobhandler/handlers/init.go @@ -0,0 +1,9 @@ +package handlers + +import ( + _ "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/generic/maven" + _ "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/generic/npm" + _ "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/generic/ocirepo" + _ "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/oci/ocirepo" + _ "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/ocm/comparch" +) diff --git a/api/ocm/extensions/blobhandler/handlers/oci/ctx.go b/api/ocm/extensions/blobhandler/handlers/oci/ctx.go new file mode 100644 index 000000000..f7e4012a7 --- /dev/null +++ b/api/ocm/extensions/blobhandler/handlers/oci/ctx.go @@ -0,0 +1,76 @@ +package oci + +import ( + "reflect" + + ociv1 "github.com/opencontainers/image-spec/specs-go/v1" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/cpi" + ocmcpi "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/repositories/genericocireg/componentmapping" +) + +// StorageContext is the context information passed for Blobhandler +// registered for context type oci.CONTEXT_TYPE. +type StorageContext struct { + ocmcpi.DefaultStorageContext + Repository cpi.Repository + Namespace cpi.NamespaceAccess + Manifest cpi.ManifestAccess +} + +var _ ocmcpi.StorageContext = (*StorageContext)(nil) + +func New(compname string, repo ocmcpi.Repository, impltyp string, ocirepo oci.Repository, namespace oci.NamespaceAccess, manifest oci.ManifestAccess) *StorageContext { + return &StorageContext{ + DefaultStorageContext: *ocmcpi.NewDefaultStorageContext( + repo, + compname, + ocmcpi.ImplementationRepositoryType{ + ContextType: cpi.CONTEXT_TYPE, + RepositoryType: impltyp, + }, + ), + Repository: ocirepo, + Namespace: namespace, + Manifest: manifest, + } +} + +func (s *StorageContext) TargetComponentRepository() ocmcpi.Repository { + return s.ComponentRepository +} + +func (s *StorageContext) TargetComponentName() string { + return s.ComponentName +} + +func (s *StorageContext) AssureLayer(blob cpi.BlobAccess) error { + return AssureLayer(s.Manifest.GetDescriptor(), blob) +} + +func AssureLayer(desc *artdesc.Manifest, blob cpi.BlobAccess) error { + d := artdesc.DefaultBlobDescriptor(blob) + + found := -1 + for i, l := range desc.Layers { + if reflect.DeepEqual(&desc.Layers[i], d) { + return nil + } + if l.Digest == blob.Digest() { + found = i + } + } + if found > 0 { // ignore layer 0 used for component descriptor + desc.Layers[found] = *d + } else { + if len(desc.Layers) == 0 { + // fake descriptor layer + desc.Layers = append(desc.Layers, ociv1.Descriptor{MediaType: componentmapping.ComponentDescriptorConfigMimeType}) + } + desc.Layers = append(desc.Layers, *d) + } + return nil +} diff --git a/pkg/contexts/ocm/blobhandler/handlers/oci/doc.go b/api/ocm/extensions/blobhandler/handlers/oci/doc.go similarity index 100% rename from pkg/contexts/ocm/blobhandler/handlers/oci/doc.go rename to api/ocm/extensions/blobhandler/handlers/oci/doc.go diff --git a/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/blobhandler.go b/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/blobhandler.go new file mode 100644 index 000000000..91ea3d21a --- /dev/null +++ b/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/blobhandler.go @@ -0,0 +1,347 @@ +package ocirepo + +import ( + "fmt" + "path" + "strings" + + . "github.com/mandelsoft/goutils/finalizer" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/sliceutils" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/oci/extensions/repositories/ocireg" + "ocm.software/ocm/api/oci/grammar" + "ocm.software/ocm/api/oci/tools/transfer" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localociblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociblob" + "ocm.software/ocm/api/ocm/extensions/attrs/compatattr" + "ocm.software/ocm/api/ocm/extensions/attrs/keepblobattr" + "ocm.software/ocm/api/ocm/extensions/attrs/mapocirepoattr" + storagecontext "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/oci" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +func init() { + for _, mime := range artdesc.ArchiveBlobTypes() { + cpi.RegisterBlobHandler(NewArtifactHandler(OCIRegBaseFunction), cpi.ForRepo(oci.CONTEXT_TYPE, ocireg.Type), + cpi.ForMimeType(mime)) + cpi.RegisterBlobHandler(NewArtifactHandler(OCIRegBaseFunction), cpi.ForRepo(oci.CONTEXT_TYPE, ocireg.LegacyType), + cpi.ForMimeType(mime)) + cpi.RegisterBlobHandler(NewArtifactHandler(OCIRegBaseFunction), cpi.ForRepo(oci.CONTEXT_TYPE, ocireg.ShortType), + cpi.ForMimeType(mime)) + } + /* + cpi.RegisterBlobHandler(NewBlobHandler(OCIRegBaseFunction), cpi.ForRepo(oci.CONTEXT_TYPE, ocireg.Type)) + cpi.RegisterBlobHandler(NewBlobHandler(OCIRegBaseFunction), cpi.ForRepo(oci.CONTEXT_TYPE, ocireg.LegacyType)) + cpi.RegisterBlobHandler(NewBlobHandler(OCIRegBaseFunction), cpi.ForRepo(oci.CONTEXT_TYPE, ocireg.ShortType)) + */ +} + +//////////////////////////////////////////////////////////////////////////////// + +type BaseFunction func(ctx *storagecontext.StorageContext) string + +func OCIRegBaseFunction(ctx *storagecontext.StorageContext) string { + i, err := ocireg.GetRepositoryImplementation(ctx.Repository) + if err != nil { + panic("ocireg implementation mismatch") + } + return i.GetBaseURL() +} + +// blobHandler is the default handling to store local blobs as local blobs but with an additional +// globally accessible OCIBlob access method. +type blobHandler struct { + base BaseFunction +} + +func (h *blobHandler) GetBaseURL(ctx *storagecontext.StorageContext) string { + if h.base == nil { + return "" + } + return h.base(ctx) +} + +func NewBlobHandler(base BaseFunction) cpi.BlobHandler { + return &blobHandler{base} +} + +func (b *blobHandler) StoreBlob(blob cpi.BlobAccess, artType, hint string, global cpi.AccessSpec, ctx cpi.StorageContext) (cpi.AccessSpec, error) { + ocictx, ok := ctx.(*storagecontext.StorageContext) + if !ok { + return nil, fmt.Errorf("failed to assert type %T to storagecontext.StorageContext", ctx) + } + + values := []interface{}{ + "arttype", artType, + "mediatype", blob.MimeType(), + "hint", hint, + } + if m, ok := blob.(blobaccess.AnnotatedBlobAccess[accspeccpi.AccessMethodView]); ok { + cpi.BlobHandlerLogger(ctx.GetContext()).Debug("oci blob handler with ocm access source", + sliceutils.CopyAppend[any](values, "sourcetype", m.Source().AccessSpec().GetType())..., + ) + } else { + cpi.BlobHandlerLogger(ctx.GetContext()).Debug("oci blob handler", values...) + } + + err := ocictx.Manifest.AddBlob(blob) + if err != nil { + return nil, err + } + err = ocictx.AssureLayer(blob) + if err != nil { + return nil, err + } + if compatattr.Get(ctx.GetContext()) { + return localociblob.New(blob.Digest()), nil + } else { + if global == nil { + base := b.GetBaseURL(ocictx) + if base != "" { + global = ociblob.New(path.Join(base, ocictx.Namespace.GetNamespace()), blob.Digest(), blob.MimeType(), blob.Size()) + } + } + return localblob.New(blob.Digest().String(), "", blob.MimeType(), global), nil + } +} + +//////////////////////////////////////////////////////////////////////////////// + +// artifactHandler stores artifact blobs as OCIArtifacts. +type artifactHandler struct { + blobHandler +} + +func NewArtifactHandler(base BaseFunction) cpi.BlobHandler { + return &artifactHandler{blobHandler{base}} +} + +func (b *artifactHandler) CheckBlob(blob cpi.BlobAccess, artType, hint string, global cpi.AccessSpec, ctx cpi.StorageContext) (bool, bool, error) { + mediaType := blob.MimeType() + + if !artdesc.IsOCIMediaType(mediaType) || (!strings.HasSuffix(mediaType, "+tar") && !strings.HasSuffix(mediaType, "+tar+gzip")) { + return false, false, nil + } + + log := cpi.BlobHandlerLogger(ctx.GetContext()) + + values := []interface{}{ + "arttype", artType, + "mediatype", mediaType, + "hint", hint, + } + + var art oci.ArtifactAccess + var err error + var finalizer Finalizer + defer finalizer.Finalize() + + var namespace oci.NamespaceAccess + var version string + var name string + var tag string + + ocictx, ok := ctx.(*storagecontext.StorageContext) + if !ok { + return false, false, fmt.Errorf("failed to assert type %T to storagecontext.StorageContext", ctx) + } + if hint == "" { + namespace = ocictx.Namespace + } else { + prefix := cpi.RepositoryPrefix(ctx.TargetComponentRepository().GetSpecification()) + i := strings.LastIndex(hint, "@") + if i >= 0 { + hint = hint[:i] // remove digest + } + i = strings.LastIndex(hint, ":") + if i > 0 { + version = hint[i:] + tag = version[1:] // remove colon + name = hint[:i] + } else { + name = hint + } + + hash := mapocirepoattr.Get(ctx.GetContext()) + if hash.Prefix != nil { + prefix = *hash.Prefix + } + orig := name + mapped := hash.Map(name) + name = path.Join(prefix, mapped) + if mapped == orig { + log.Debug("namespace derived from hint", + sliceutils.CopyAppend[any](values, "namespace", name), + ) + } else { + log.Debug("mapped namespace derived from hint", + sliceutils.CopyAppend[any](values, "namespace", name), + ) + } + + namespace, err = ocictx.Repository.LookupNamespace(name) + if err != nil { + return false, false, err + } + defer namespace.Close() + } + + ok, err = namespace.HasArtifact(string(art.Digest())) + if ok { + return true, true, err + } + ok, err = namespace.HasArtifact(tag) + return ok, true, err +} + +func (b *artifactHandler) StoreBlob(blob cpi.BlobAccess, artType, hint string, global cpi.AccessSpec, ctx cpi.StorageContext) (cpi.AccessSpec, error) { + mediaType := blob.MimeType() + + if !artdesc.IsOCIMediaType(mediaType) || (!strings.HasSuffix(mediaType, "+tar") && !strings.HasSuffix(mediaType, "+tar+gzip")) { + return nil, nil + } + + errhint := "[" + hint + "]" + log := cpi.BlobHandlerLogger(ctx.GetContext()) + + values := []interface{}{ + "arttype", artType, + "mediatype", mediaType, + "hint", hint, + } + + var art oci.ArtifactAccess + var err error + var finalizer Finalizer + defer finalizer.Finalize() + + keep := keepblobattr.Get(ctx.GetContext()) + + if m, ok := blob.(blobaccess.AnnotatedBlobAccess[accspeccpi.AccessMethodView]); ok { + // prepare for optimized point to point implementation + log.Debug("oci artifact handler with ocm access source", + sliceutils.CopyAppend[any](values, "sourcetype", m.Source().AccessSpec().GetType())..., + ) + if ocimeth, ok := m.Source().Unwrap().(ociartifact.AccessMethodImpl); !keep && ok { + art, _, err = ocimeth.GetArtifact() + if err != nil { + return nil, errors.Wrapf(err, "cannot access source artifact") + } + if art != nil { + defer art.Close() + } + } + } else { + log.Debug("oci artifact handler", values...) + } + + var namespace oci.NamespaceAccess + var version string + var name string + var tag string + var digest digest.Digest + + ocictx, ok := ctx.(*storagecontext.StorageContext) + if !ok { + return nil, fmt.Errorf("failed to assert type %T to storagecontext.StorageContext", ctx) + } + base := b.GetBaseURL(ocictx) + if hint == "" { + namespace = ocictx.Namespace + } else { + prefix := cpi.RepositoryPrefix(ctx.TargetComponentRepository().GetSpecification()) + i := strings.LastIndex(hint, "@") + if i >= 0 { + hint = hint[:i] // remove digest + } + i = strings.LastIndex(hint, ":") + if i > 0 { + version = hint[i:] + tag = version[1:] // remove colon + name = hint[:i] + } else { + name = hint + } + + hash := mapocirepoattr.Get(ctx.GetContext()) + if hash.Prefix != nil { + prefix = *hash.Prefix + } + orig := name + mapped := hash.Map(name) + name = path.Join(prefix, mapped) + if mapped == orig { + log.Debug("namespace derived from hint", + sliceutils.CopyAppend[any](values, "namespace", name), + ) + } else { + log.Debug("mapped namespace derived from hint", + sliceutils.CopyAppend[any](values, "namespace", name), + ) + } + + namespace, err = ocictx.Repository.LookupNamespace(name) + if err != nil { + return nil, err + } + defer namespace.Close() + } + + errhint += " namespace " + namespace.GetNamespace() + + if art == nil { + log.Debug("using artifact set transfer mode") + set, err := artifactset.OpenFromBlob(accessobj.ACC_READONLY, blob) + if err != nil { + return nil, wrap(err, errhint, "open blob") + } + defer set.Close() + digest = set.GetMain() + art, err = set.GetArtifact(digest.String()) + if err != nil { + return nil, wrap(err, errhint, "get artifact from blob") + } + defer art.Close() + } else { + log.Debug("using direct transfer mode") + digest = art.Digest() + } + + if version == "" { + version = "@" + digest.String() + } + + err = transfer.TransferArtifact(art, namespace, oci.AsTags(tag)...) + if err != nil { + return nil, wrap(err, errhint, "transfer artifact") + } + match := grammar.AnchoredSchemedRegexp.FindStringSubmatch(base) + scheme := "" + if match != nil { + scheme = match[1] + base = match[2] + } + if scheme != "" { + scheme += "://" + } + ref := scheme + path.Join(base, namespace.GetNamespace()) + version + return ociartifact.New(ref), nil +} + +func wrap(err error, msg string, args ...interface{}) error { + for _, a := range args { + msg = fmt.Sprintf("%s: %s", msg, a) + } + return errors.Wrapf(err, "exploding OCI artifact resource blob (%s)", msg) +} diff --git a/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/handler_test.go b/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/handler_test.go new file mode 100644 index 000000000..855a6244a --- /dev/null +++ b/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/handler_test.go @@ -0,0 +1,245 @@ +package ocirepo_test + +import ( + "encoding/json" + "fmt" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + . "ocm.software/ocm/api/oci/testhelper" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + ocictf "ocm.software/ocm/api/oci/extensions/repositories/ctf" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/attrs/keepblobattr" + "ocm.software/ocm/api/ocm/extensions/attrs/mapocirepoattr" + storagecontext "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/oci" + "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/oci/ocirepo" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/extensions/repositories/genericocireg" + "ocm.software/ocm/api/ocm/tools/transfer" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/mime" +) + +const ( + ARCH = "/tmp/ctf" + ARCH2 = "/tmp/ctf2" + PROVIDER = "mandelsoft" + VERSION = "v1" + COMPONENT = "github.com/mandelsoft/test" + COMPONENT2 = "github.com/mandelsoft/test2" + OUT = "/tmp/res" + OCIPATH = "/tmp/oci" + OCIHOST = "alias" +) + +func FakeOCIRegBaseFunction(ctx *storagecontext.StorageContext) string { + return "baseurl.io" +} + +var _ = Describe("oci artifact transfer", func() { + var env *Builder + var ldesc *artdesc.Descriptor + + BeforeEach(func() { + env = NewBuilder() + + FakeOCIRepo(env, OCIPATH, OCIHOST) + + env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { + ldesc = OCIManifest1(env) + }) + + env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { + env.Component(COMPONENT, func() { + env.Version(VERSION, func() { + env.Provider(PROVIDER) + env.Resource("testdata", "", "PlainText", metav1.LocalRelation, func() { + env.BlobStringData(mime.MIME_TEXT, "testdata") + }) + env.Resource("artifact", "", resourcetypes.OCI_IMAGE, metav1.LocalRelation, func() { + env.Access( + ociartifact.New(oci.StandardOCIRef(OCIHOST+".alias", OCINAMESPACE, OCIVERSION)), + ) + }) + }) + }) + }) + + _ = ldesc + }) + + AfterEach(func() { + env.Cleanup() + }) + + It("it should copy a resource by value and export the OCI image but keep the local blob", func() { + env.OCMContext().BlobHandlers().Register(ocirepo.NewArtifactHandler(FakeOCIRegBaseFunction), + cpi.ForRepo(oci.CONTEXT_TYPE, ocictf.Type), cpi.ForMimeType(artdesc.ToContentMediaType(artdesc.MediaTypeImageManifest))) + keepblobattr.Set(env.OCMContext(), true) + + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) + cv := Must(src.LookupComponentVersion(COMPONENT, VERSION)) + tgt := Must(ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) + defer tgt.Close() + + opts := &standard.Options{} + opts.SetResourcesByValue(true) + handler := standard.NewDefaultHandler(opts) + + MustBeSuccessful(transfer.TransferVersion(nil, nil, cv, tgt, handler)) + Expect(env.DirExists(OUT)).To(BeTrue()) + + list := Must(tgt.ComponentLister().GetComponents("", true)) + Expect(list).To(Equal([]string{COMPONENT})) + comp := Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) + Expect(len(comp.GetDescriptor().Resources)).To(Equal(2)) + data := Must(json.Marshal(comp.GetDescriptor().Resources[1].Access)) + + fmt.Printf("%s\n", string(data)) + Expect(string(data)).To(StringEqualWithContext(`{"globalAccess":{"imageReference":"baseurl.io/ocm/value:v2.0","type":"ociArtifact"},"localReference":"sha256:b0692bcec00e0a875b6b280f3209d6776f3eca128adcb7e81e82fd32127c0c62","mediaType":"application/vnd.oci.image.manifest.v1+tar+gzip","referenceName":"ocm/value:v2.0","type":"localBlob"}`)) + ocirepo := genericocireg.GetOCIRepository(tgt) + Expect(ocirepo).NotTo(BeNil()) + + art := Must(ocirepo.LookupArtifact(OCINAMESPACE, OCIVERSION)) + defer Close(art, "artifact") + + man := MustBeNonNil(art.ManifestAccess()) + Expect(len(man.GetDescriptor().Layers)).To(Equal(1)) + Expect(man.GetDescriptor().Layers[0].Digest).To(Equal(ldesc.Digest)) + + blob := Must(man.GetBlob(ldesc.Digest)) + data = Must(blob.Get()) + Expect(string(data)).To(Equal(OCILAYER)) + }) + + It("it should copy a resource by value and export the OCI image", func() { + env.OCMContext().BlobHandlers().Register(ocirepo.NewArtifactHandler(FakeOCIRegBaseFunction), + cpi.ForRepo(oci.CONTEXT_TYPE, ocictf.Type), cpi.ForMimeType(artdesc.ToContentMediaType(artdesc.MediaTypeImageManifest))) + + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) + cv := Must(src.LookupComponentVersion(COMPONENT, VERSION)) + tgt := Must(ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) + defer tgt.Close() + + opts := &standard.Options{} + opts.SetResourcesByValue(true) + handler := standard.NewDefaultHandler(opts) + + MustBeSuccessful(transfer.TransferVersion(nil, nil, cv, tgt, handler)) + Expect(env.DirExists(OUT)).To(BeTrue()) + + list := Must(tgt.ComponentLister().GetComponents("", true)) + Expect(list).To(Equal([]string{COMPONENT})) + comp := Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) + Expect(len(comp.GetDescriptor().Resources)).To(Equal(2)) + data := Must(json.Marshal(comp.GetDescriptor().Resources[1].Access)) + + fmt.Printf("%s\n", string(data)) + Expect(string(data)).To(StringEqualWithContext("{\"imageReference\":\"baseurl.io/ocm/value:v2.0\",\"type\":\"ociArtifact\"}")) + + ocirepo := genericocireg.GetOCIRepository(tgt) + art := Must(ocirepo.LookupArtifact(OCINAMESPACE, OCIVERSION)) + defer Close(art, "artifact") + + man := MustBeNonNil(art.ManifestAccess()) + Expect(len(man.GetDescriptor().Layers)).To(Equal(1)) + Expect(man.GetDescriptor().Layers[0].Digest).To(Equal(ldesc.Digest)) + + blob := Must(man.GetBlob(ldesc.Digest)) + data = Must(blob.Get()) + Expect(string(data)).To(Equal(OCILAYER)) + }) + + It("it should copy a resource by value and export the OCI image with hashed repo name", func() { + env.OCMContext().BlobHandlers().Register(ocirepo.NewArtifactHandler(FakeOCIRegBaseFunction), + cpi.ForRepo(oci.CONTEXT_TYPE, ocictf.Type), cpi.ForMimeType(artdesc.ToContentMediaType(artdesc.MediaTypeImageManifest))) + + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) + cv := Must(src.LookupComponentVersion(COMPONENT, VERSION)) + tgt := Must(ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) + defer tgt.Close() + + opts := &standard.Options{} + opts.SetResourcesByValue(true) + handler := standard.NewDefaultHandler(opts) + mapocirepoattr.Set(env.OCMContext(), &mapocirepoattr.Attribute{Mode: mapocirepoattr.ShortHashMode, Always: true}) + rdigest := "e9b6af2174cb2fb78b2882a1f487b01295b8f6bfa7e4c1ceb350440104c9ce65" + + MustBeSuccessful(transfer.TransferVersion(nil, nil, cv, tgt, handler)) + Expect(env.DirExists(OUT)).To(BeTrue()) + + list := Must(tgt.ComponentLister().GetComponents("", true)) + Expect(list).To(Equal([]string{COMPONENT})) + comp := Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) + Expect(len(comp.GetDescriptor().Resources)).To(Equal(2)) + data := Must(json.Marshal(comp.GetDescriptor().Resources[1].Access)) + + fmt.Printf("%s\n", string(data)) + Expect(string(data)).To(StringEqualWithContext("{\"imageReference\":\"baseurl.io/" + rdigest[:8] + "/value:v2.0\",\"type\":\"ociArtifact\"}")) + + namespace := rdigest[:8] + "/value" + ocirepo := genericocireg.GetOCIRepository(tgt) + art := Must(ocirepo.LookupArtifact(namespace, OCIVERSION)) + defer Close(art, "artifact") + + man := MustBeNonNil(art.ManifestAccess()) + Expect(len(man.GetDescriptor().Layers)).To(Equal(1)) + Expect(man.GetDescriptor().Layers[0].Digest).To(Equal(ldesc.Digest)) + + blob := Must(man.GetBlob(ldesc.Digest)) + data = Must(blob.Get()) + Expect(string(data)).To(Equal(OCILAYER)) + }) + + It("it should copy a resource by value and export the OCI image with hashed repo name and prefix", func() { + env.OCMContext().BlobHandlers().Register(ocirepo.NewArtifactHandler(FakeOCIRegBaseFunction), + cpi.ForRepo(oci.CONTEXT_TYPE, ocictf.Type), cpi.ForMimeType(artdesc.ToContentMediaType(artdesc.MediaTypeImageManifest))) + + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) + cv := Must(src.LookupComponentVersion(COMPONENT, VERSION)) + tgt := Must(ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) + defer tgt.Close() + + opts := &standard.Options{} + opts.SetResourcesByValue(true) + handler := standard.NewDefaultHandler(opts) + prefix := "ocm" + mapocirepoattr.Set(env.OCMContext(), &mapocirepoattr.Attribute{Mode: mapocirepoattr.ShortHashMode, Always: true, Prefix: &prefix}) + rdigest := "e9b6af2174cb2fb78b2882a1f487b01295b8f6bfa7e4c1ceb350440104c9ce65" + + MustBeSuccessful(transfer.TransferVersion(nil, nil, cv, tgt, handler)) + Expect(env.DirExists(OUT)).To(BeTrue()) + + list := Must(tgt.ComponentLister().GetComponents("", true)) + Expect(list).To(Equal([]string{COMPONENT})) + comp := Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) + Expect(len(comp.GetDescriptor().Resources)).To(Equal(2)) + data := Must(json.Marshal(comp.GetDescriptor().Resources[1].Access)) + + fmt.Printf("%s\n", string(data)) + Expect(string(data)).To(StringEqualWithContext("{\"imageReference\":\"baseurl.io/ocm/" + rdigest[:8] + "/value:v2.0\",\"type\":\"ociArtifact\"}")) + + namespace := "ocm/" + rdigest[:8] + "/value" + ocirepo := genericocireg.GetOCIRepository(tgt) + art := Must(ocirepo.LookupArtifact(namespace, OCIVERSION)) + defer Close(art, "artifact") + + man := MustBeNonNil(art.ManifestAccess()) + Expect(len(man.GetDescriptor().Layers)).To(Equal(1)) + Expect(man.GetDescriptor().Layers[0].Digest).To(Equal(ldesc.Digest)) + + blob := Must(man.GetBlob(ldesc.Digest)) + data = Must(blob.Get()) + Expect(string(data)).To(Equal(OCILAYER)) + }) +}) diff --git a/pkg/contexts/ocm/blobhandler/handlers/oci/ocirepo/suite_test.go b/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/suite_test.go similarity index 100% rename from pkg/contexts/ocm/blobhandler/handlers/oci/ocirepo/suite_test.go rename to api/ocm/extensions/blobhandler/handlers/oci/ocirepo/suite_test.go diff --git a/api/ocm/extensions/blobhandler/handlers/ocm/comparch/blobhandler.go b/api/ocm/extensions/blobhandler/handlers/ocm/comparch/blobhandler.go new file mode 100644 index 000000000..a057a1666 --- /dev/null +++ b/api/ocm/extensions/blobhandler/handlers/ocm/comparch/blobhandler.go @@ -0,0 +1,50 @@ +package comparch + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localfsblob" + "ocm.software/ocm/api/ocm/extensions/attrs/compatattr" + storagecontext "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/ocm" + "ocm.software/ocm/api/ocm/extensions/repositories/comparch" + common "ocm.software/ocm/api/utils/misc" +) + +func init() { + cpi.RegisterBlobHandler(NewBlobHandler(), cpi.ForRepo(cpi.CONTEXT_TYPE, comparch.Type)) +} + +//////////////////////////////////////////////////////////////////////////////// + +// blobHandler is the default handling to store local blobs as local blobs. +type blobHandler struct{} + +func NewBlobHandler() cpi.BlobHandler { + return &blobHandler{} +} + +func (b *blobHandler) StoreBlob(blob cpi.BlobAccess, artType, hint string, global cpi.AccessSpec, ctx cpi.StorageContext) (cpi.AccessSpec, error) { + ocmctx, ok := ctx.(storagecontext.StorageContext) + if !ok { + return nil, fmt.Errorf("failed to assert type %T to storagecontext.StorageContext", ctx) + } + + if blob == nil { + return nil, errors.New("a resource has to be defined") + } + ref, err := ocmctx.AddBlob(blob) + if err != nil { + return nil, err + } + path := common.DigestToFileName(digest.Digest(ref)) + if compatattr.Get(ctx.GetContext()) { + return localfsblob.New(path, blob.MimeType()), nil + } else { + return localblob.New(path, hint, blob.MimeType(), global), nil + } +} diff --git a/api/ocm/extensions/blobhandler/handlers/ocm/comparch/blobhandler_test.go b/api/ocm/extensions/blobhandler/handlers/ocm/comparch/blobhandler_test.go new file mode 100644 index 000000000..c021f3509 --- /dev/null +++ b/api/ocm/extensions/blobhandler/handlers/ocm/comparch/blobhandler_test.go @@ -0,0 +1,81 @@ +package comparch_test + +import ( + "encoding/json" + "reflect" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/helper/builder" + "ocm.software/ocm/api/helper/env" + "ocm.software/ocm/api/ocm/compdesc" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localfsblob" + "ocm.software/ocm/api/ocm/extensions/attrs/compatattr" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/mime" +) + +const ARCHIVE = "archive" + +var _ = Describe("blobhandler", func() { + Context("regular", func() { + var b *builder.Builder + + BeforeEach(func() { + b = builder.NewBuilder() + }) + + AfterEach(func() { + b.Cleanup() + }) + + It("uses generic local access", func() { + b.ComponentArchive(ARCHIVE, accessio.FormatDirectory, "github.com/mandelsoft/test", "1.0.0", func() { + b.Resource("test", "1.0.0", "Test", v1.LocalRelation, func() { + b.BlobStringData(mime.MIME_TEXT, "testdata") + }) + }) + data := Must(b.ReadFile(vfs.Join(b, ARCHIVE, compdesc.ComponentDescriptorFileName))) + cd := Must(compdesc.Decode(data)) + Expect(cd.Resources[0].Access.GetType()).To(Equal(localblob.Type)) + + data = Must(json.Marshal(cd.Resources[0].Access)) + found := Must(localblob.Decode(data)) + spec := localblob.New("sha256.810ff2fb242a5dee4220f2cb0e6a519891fb67f2f828a6cab4ef8894633b1f50", "", mime.MIME_TEXT, nil) + Expect(found).To(Equal(spec)) + }) + }) + Context("legacy", func() { + var b *builder.Builder + BeforeEach(func() { + b = builder.NewBuilder(env.NewEnvironment()) + Expect(b.ConfigContext().GetAttributes().SetAttribute(compatattr.ATTR_KEY, true)).To(Succeed()) + }) + AfterEach(func() { + b.Cleanup() + }) + It("uses generic local access", func() { + b.ComponentArchive(ARCHIVE, accessio.FormatDirectory, "github.com/mandelsoft/test", "1.0.0", func() { + b.Resource("test", "1.0.0", "Test", v1.LocalRelation, func() { + b.BlobStringData(mime.MIME_TEXT, "testdata") + }) + }) + data := Must(b.ReadFile(vfs.Join(b, ARCHIVE, compdesc.ComponentDescriptorFileName))) + cd := Must(compdesc.Decode(data)) + Expect(cd.Resources[0].Access.GetType()).To(Equal(localfsblob.Type)) + + data = Must(json.Marshal(cd.Resources[0].Access)) + found := Must(localfsblob.Decode(data)) + + spec := localfsblob.New("sha256.810ff2fb242a5dee4220f2cb0e6a519891fb67f2f828a6cab4ef8894633b1f50", mime.MIME_TEXT) + reflect.DeepEqual(found, spec) + Expect(found).To(Equal(spec)) + }) + }) +}) diff --git a/pkg/contexts/ocm/blobhandler/handlers/ocm/comparch/suite_test.go b/api/ocm/extensions/blobhandler/handlers/ocm/comparch/suite_test.go similarity index 100% rename from pkg/contexts/ocm/blobhandler/handlers/ocm/comparch/suite_test.go rename to api/ocm/extensions/blobhandler/handlers/ocm/comparch/suite_test.go diff --git a/api/ocm/extensions/blobhandler/handlers/ocm/ctx.go b/api/ocm/extensions/blobhandler/handlers/ocm/ctx.go new file mode 100644 index 000000000..829c262a9 --- /dev/null +++ b/api/ocm/extensions/blobhandler/handlers/ocm/ctx.go @@ -0,0 +1,36 @@ +package ocm + +import ( + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +type BlobSink interface { + AddBlob(blob blobaccess.BlobAccess) (string, error) +} + +// StorageContext is the context information passed for Blobhandler +// registered for context type oci.CONTEXT_TYPE. +type StorageContext interface { + cpi.StorageContext + BlobSink +} + +type DefaultStorageContext struct { + cpi.DefaultStorageContext + Sink BlobSink + Payload interface{} +} + +func New(repo cpi.Repository, compname string, access BlobSink, impltyp string, payload ...interface{}) StorageContext { + return &DefaultStorageContext{ + DefaultStorageContext: *cpi.NewDefaultStorageContext(repo, compname, cpi.ImplementationRepositoryType{cpi.CONTEXT_TYPE, impltyp}), + Sink: access, + Payload: utils.Optional(payload...), + } +} + +func (c *DefaultStorageContext) AddBlob(blob blobaccess.BlobAccess) (string, error) { + return c.Sink.AddBlob(blob) +} diff --git a/pkg/contexts/ocm/blobhandler/handlers/ocm/doc.go b/api/ocm/extensions/blobhandler/handlers/ocm/doc.go similarity index 100% rename from pkg/contexts/ocm/blobhandler/handlers/ocm/doc.go rename to api/ocm/extensions/blobhandler/handlers/ocm/doc.go diff --git a/api/ocm/extensions/blobhandler/interface.go b/api/ocm/extensions/blobhandler/interface.go new file mode 100644 index 000000000..c3fa5c645 --- /dev/null +++ b/api/ocm/extensions/blobhandler/interface.go @@ -0,0 +1,17 @@ +package blobhandler + +import ( + "ocm.software/ocm/api/ocm/cpi" +) + +type ( + HandlerConfig = cpi.BlobHandlerConfig + HandlerOption = cpi.BlobHandlerOption + HandlerOptions = cpi.BlobHandlerOptions + HandlerRegistry = cpi.BlobHandlerRegistry + HandlerKey = cpi.BlobHandlerKey +) + +func For(ctx cpi.ContextProvider) cpi.BlobHandlerRegistry { + return ctx.OCMContext().BlobHandlers() +} diff --git a/api/ocm/extensions/blobhandler/registration.go b/api/ocm/extensions/blobhandler/registration.go new file mode 100644 index 000000000..83a176351 --- /dev/null +++ b/api/ocm/extensions/blobhandler/registration.go @@ -0,0 +1,34 @@ +package blobhandler + +import ( + "fmt" + + "ocm.software/ocm/api/ocm/cpi" +) + +func RegisterHandlerByName(ctx cpi.ContextProvider, name string, config HandlerConfig, opts ...HandlerOption) error { + o, err := For(ctx).RegisterByName(name, ctx.OCMContext(), config, opts...) + if err != nil { + return err + } + if !o { + return fmt.Errorf("no matching handler found for %q", name) + } + return nil +} + +func WithPrio(prio int) HandlerOption { + return cpi.WithPrio(prio) +} + +func ForArtifactType(t string) HandlerOption { + return cpi.ForArtifactType(t) +} + +func ForMimeType(t string) HandlerOption { + return cpi.ForMimeType(t) +} + +func ForRepo(ctxtype string, repotype string) HandlerOption { + return cpi.ForRepo(ctxtype, repotype) +} diff --git a/api/ocm/extensions/digester/digesters/artifact/digester.go b/api/ocm/extensions/digester/digesters/artifact/digester.go new file mode 100644 index 000000000..64800aab0 --- /dev/null +++ b/api/ocm/extensions/digester/digesters/artifact/digester.go @@ -0,0 +1,174 @@ +package artifact + +import ( + "archive/tar" + "fmt" + "io" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/general" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/hasher/sha256" + "ocm.software/ocm/api/tech/signing/hasher/sha512" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/compression" +) + +const OciArtifactDigestV1 string = "ociArtifactDigest/v1" + +const LegacyOciArtifactDigestV1 string = "ociArtefactDigest/v1" + +func init() { + cpi.MustRegisterDigester(New(sha256.Algorithm), "") + cpi.MustRegisterDigester(New(sha512.Algorithm), "") + + // legacy digester types + cpi.MustRegisterDigester(New(digest.SHA256.String(), OciArtifactDigestV1), "") + cpi.MustRegisterDigester(New(digest.SHA512.String(), OciArtifactDigestV1), "") + + cpi.MustRegisterDigester(New(digest.SHA256.String(), LegacyOciArtifactDigestV1), "") + cpi.MustRegisterDigester(New(digest.SHA512.String(), LegacyOciArtifactDigestV1), "") +} + +func New(algo string, ts ...string) cpi.BlobDigester { + norm := general.OptionalDefaulted(OciArtifactDigestV1, ts...) + return &Digester{ + cpi.DigesterType{ + HashAlgorithm: algo, + NormalizationAlgorithm: norm, + }, + } +} + +type Digester struct { + typ cpi.DigesterType +} + +var _ cpi.BlobDigester = (*Digester)(nil) + +func (d *Digester) GetType() cpi.DigesterType { + return d.typ +} + +func (d *Digester) DetermineDigest(reftyp string, method cpi.AccessMethod, preferred signing.Hasher) (*cpi.DigestDescriptor, error) { + if method.IsLocal() { + mime := method.MimeType() + if !artdesc.IsOCIMediaType(mime) { + return nil, nil + } + r, err := method.Reader() + if err != nil { + return nil, err + } + defer r.Close() + + var reader io.ReadCloser + reader, _, err = compression.AutoDecompress(r) + if err != nil { + return nil, err + } + defer reader.Close() + tr := tar.NewReader(reader) + + var desc *cpi.DigestDescriptor + oci := false + layout := false + for { + header, err := tr.Next() + if err != nil { + if errors.Is(err, io.EOF) { + if oci { + if layout { + return desc, nil + } else { + err = fmt.Errorf("oci-layout not found") + } + } else { + err = fmt.Errorf("descriptor not found in archive") + } + } + return nil, errors.ErrInvalidWrap(err, "artifact archive") + } + + switch header.Typeflag { + case tar.TypeDir: + case tar.TypeReg: + switch header.Name { + case artifactset.OCILayouFileName: + layout = true + case artifactset.OCIArtifactSetDescriptorFileName: + oci = true + fallthrough + case artifactset.ArtifactSetDescriptorFileName: + data, err := io.ReadAll(tr) + if err != nil { + return nil, fmt.Errorf("unable to read descriptor from archive: %w", err) + } + index, err := artdesc.DecodeIndex(data) + if err != nil { + return nil, err + } + if index == nil { + return nil, fmt.Errorf("no main artifact found") + } + main := artifactset.RetrieveMainArtifactFromIndex(index) + if main == "" { + return nil, fmt.Errorf("no main artifact found") + } + dig := artifactset.RetrieveDigest(index, main) + if dig == "" { + return nil, fmt.Errorf("no main artifact digest found for %s", main) + } + if d.GetType().HashAlgorithm != signing.NormalizeHashAlgorithm(string(dig.Algorithm())) { + return nil, nil + } + desc = cpi.NewDigestDescriptor(dig.Hex(), d.GetType()) + if !oci { + return desc, nil + } + } + } + } + // not reached (endless for) + } + if ociartifact.Is(method.AccessSpec()) { + var ( + dig digest.Digest + err error + ) + + impl := accspeccpi.GetAccessMethodImplementation(method) + // first, ask access specification (inexpensive) + if s, ok := method.AccessSpec().(blobaccess.DigestSource); ok { + dig = s.Digest() + } + if dig == "" { + // second: check for error providing interface + if s, ok := impl.(accspeccpi.DigestSource); ok { + dig, err = s.GetDigest() + } + } + if dig == "" && err == nil { + // third: fallback to standard digest interface + if s, ok := impl.(blobaccess.DigestSource); ok { + dig = s.Digest() + } + } + + if dig != "" { + if d.GetType().HashAlgorithm != signing.NormalizeHashAlgorithm(dig.Algorithm().String()) { + return nil, nil + } + return cpi.NewDigestDescriptor(dig.Hex(), d.GetType()), nil + } + return nil, errors.NewEf(err, "cannot determine digest") + } + return nil, nil +} diff --git a/api/ocm/extensions/digester/digesters/blob/digester.go b/api/ocm/extensions/digester/digesters/blob/digester.go new file mode 100644 index 000000000..3132d1db2 --- /dev/null +++ b/api/ocm/extensions/digester/digesters/blob/digester.go @@ -0,0 +1,45 @@ +package blob + +import ( + "fmt" + "io" + + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/tech/signing" +) + +const GenericBlobDigestV1 = "genericBlobDigest/v1" + +func init() { + cpi.MustRegisterDigester(&defaultDigester{}) + cpi.SetDefaultDigester(&defaultDigester{}) +} + +type defaultDigester struct{} + +var _ cpi.BlobDigester = (*defaultDigester)(nil) + +func (d defaultDigester) GetType() cpi.DigesterType { + return cpi.DigesterType{ + HashAlgorithm: "", + NormalizationAlgorithm: GenericBlobDigestV1, + } +} + +func (d defaultDigester) DetermineDigest(typ string, acc cpi.AccessMethod, preferred signing.Hasher) (*cpi.DigestDescriptor, error) { + r, err := acc.Reader() + if err != nil { + return nil, err + } + hash := preferred.Create() + + if _, err := io.Copy(hash, r); err != nil { + return nil, err + } + + return &cpi.DigestDescriptor{ + Value: fmt.Sprintf("%x", hash.Sum(nil)), + HashAlgorithm: preferred.Algorithm(), + NormalisationAlgorithm: GenericBlobDigestV1, + }, nil +} diff --git a/api/ocm/extensions/digester/digesters/init.go b/api/ocm/extensions/digester/digesters/init.go new file mode 100644 index 000000000..b3c0880c4 --- /dev/null +++ b/api/ocm/extensions/digester/digesters/init.go @@ -0,0 +1,6 @@ +package digesters + +import ( + _ "ocm.software/ocm/api/ocm/extensions/digester/digesters/artifact" + _ "ocm.software/ocm/api/ocm/extensions/digester/digesters/blob" +) diff --git a/api/ocm/extensions/download/config/type.go b/api/ocm/extensions/download/config/type.go new file mode 100644 index 000000000..29c8a2ca2 --- /dev/null +++ b/api/ocm/extensions/download/config/type.go @@ -0,0 +1,91 @@ +package config + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/config" + cfgcpi "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/download" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ConfigType = "downloader.ocm" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX + ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" +) + +func init() { + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1, usage)) +} + +// Config describes a memory based config interface. +type Config struct { + runtime.ObjectVersionedType `json:",inline"` + Registrations []Registration `json:"registrations,omitempty"` +} + +type Registration struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` + download.HandlerOptions `json:",inline"` + Config download.HandlerConfig `json:"config,omitempty"` +} + +// New creates a new memory ConfigSpec. +func New() *Config { + return &Config{ + ObjectVersionedType: runtime.NewVersionedObjectType(ConfigType), + } +} + +func (a *Config) GetType() string { + return ConfigType +} + +func (a *Config) AddRegistration(hdlrs ...Registration) error { + for i, h := range hdlrs { + if h.Name == "" { + return fmt.Errorf("handler registration %d requires a name", i) + } + } + a.Registrations = append(a.Registrations, hdlrs...) + return nil +} + +func (a *Config) ApplyTo(ctx cfgcpi.Context, target interface{}) error { + t, ok := target.(cpi.Context) + if !ok { + return config.ErrNoContext(ConfigType) + } + reg := download.For(t) + for _, h := range a.Registrations { + accepted, err := reg.RegisterByName(h.Name, t, h.Config, &h.HandlerOptions) + if err != nil { + return errors.Wrapf(err, "registering download handler %q[%s]", h.Name, h.Description) + } + if !accepted { + download.Logger(t).Info("no matching handler for configuration %q[%s]", h.Name, h.Description) + } + } + return nil +} + +const usage = ` +The config type ` + ConfigType + ` can be used to define a list +of pre-configured download handler registrations (see ocm ocm-downloadhandlers): + +
+    type: ` + ConfigType + `
+    descrition: "my standard download handler configuration"
+    handlers:
+      - name: oci/artifact
+        artifactType: ociImage
+        mimeType:
+        config: ...
+      ...
+
+` diff --git a/pkg/contexts/ocm/download/doc.go b/api/ocm/extensions/download/doc.go similarity index 100% rename from pkg/contexts/ocm/download/doc.go rename to api/ocm/extensions/download/doc.go diff --git a/api/ocm/extensions/download/download.go b/api/ocm/extensions/download/download.go new file mode 100644 index 000000000..36313d761 --- /dev/null +++ b/api/ocm/extensions/download/download.go @@ -0,0 +1,69 @@ +package download + +import ( + "github.com/mandelsoft/goutils/optionutils" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils" + common "ocm.software/ocm/api/utils/misc" +) + +type Option = optionutils.Option[*Options] + +type Options struct { + Printer common.Printer + FileSystem vfs.FileSystem +} + +func (o *Options) ApplyTo(opts *Options) { + if o.Printer != nil { + opts.Printer = o.Printer + } + if o.FileSystem != nil { + opts.FileSystem = o.FileSystem + } +} + +//////////////////////////////////////////////////////////////////////////////// + +type filesystem struct { + fs vfs.FileSystem +} + +func (o *filesystem) ApplyTo(opts *Options) { + if o.fs != nil { + opts.FileSystem = o.fs + } +} + +func WithFileSystem(fs vfs.FileSystem) Option { + return &filesystem{fs} +} + +//////////////////////////////////////////////////////////////////////////////// + +type printer struct { + pr common.Printer +} + +func (o *printer) ApplyTo(opts *Options) { + if o.pr != nil { + opts.Printer = o.pr + } +} + +func WithPrinter(pr common.Printer) Option { + return &printer{pr} +} + +//////////////////////////////////////////////////////////////////////////////// + +func DownloadResource(ctx cpi.ContextProvider, r cpi.ResourceAccess, path string, opts ...Option) (string, error) { + eff := optionutils.EvalOptions(opts...) + + fs := utils.FileSystem(eff.FileSystem) + pr := utils.OptionalDefaulted(common.NewPrinter(nil), eff.Printer) + _, tgt, err := For(ctx).Download(pr, r, path, fs) + return tgt, err +} diff --git a/api/ocm/extensions/download/handlers/blob/handler.go b/api/ocm/extensions/download/handlers/blob/handler.go new file mode 100644 index 000000000..af7449bfa --- /dev/null +++ b/api/ocm/extensions/download/handlers/blob/handler.go @@ -0,0 +1,47 @@ +package blob + +import ( + "io" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/download" + common "ocm.software/ocm/api/utils/misc" +) + +type Handler struct{} + +func init() { + download.Register(&Handler{}, download.ForArtifactType(download.ALL)) +} + +func wrapErr(err error, racc cpi.ResourceAccess) error { + if err == nil { + return nil + } + m := racc.Meta() + return errors.Wrapf(err, "resource %s/%s%s", m.GetName(), m.GetVersion(), m.ExtraIdentity.String()) +} + +func (_ Handler) Download(p common.Printer, racc cpi.ResourceAccess, path string, fs vfs.FileSystem) (bool, string, error) { + rd, err := cpi.GetResourceReader(racc) + if err != nil { + return true, "", wrapErr(err, racc) + } + defer rd.Close() + if path == "" { + path = racc.Meta().GetName() + } + file, err := fs.OpenFile(path, vfs.O_TRUNC|vfs.O_CREATE|vfs.O_WRONLY, 0o660) + if err != nil { + return true, "", wrapErr(errors.Wrapf(err, "creating target file %q", path), racc) + } + defer file.Close() + n, err := io.Copy(file, rd) + if err == nil { + p.Printf("%s: %d byte(s) written\n", path, n) + } + return true, path, wrapErr(err, racc) +} diff --git a/pkg/contexts/ocm/download/handlers/blueprint/blueprint_test.go b/api/ocm/extensions/download/handlers/blueprint/blueprint_test.go similarity index 77% rename from pkg/contexts/ocm/download/handlers/blueprint/blueprint_test.go rename to api/ocm/extensions/download/handlers/blueprint/blueprint_test.go index 5e98f8f65..952ac0aff 100644 --- a/pkg/contexts/ocm/download/handlers/blueprint/blueprint_test.go +++ b/api/ocm/extensions/download/handlers/blueprint/blueprint_test.go @@ -4,21 +4,21 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/env/builder" + . "ocm.software/ocm/api/helper/builder" "github.com/mandelsoft/vfs/pkg/projectionfs" - "github.com/open-component-model/ocm/pkg/common" - "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/oci/testhelper" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download/handlers/blueprint" - ctfocm "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - tenv "github.com/open-component-model/ocm/pkg/env" - "github.com/open-component-model/ocm/pkg/utils/tarutils" + tenv "ocm.software/ocm/api/helper/env" + "ocm.software/ocm/api/oci/testhelper" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + "ocm.software/ocm/api/ocm/extensions/download" + "ocm.software/ocm/api/ocm/extensions/download/handlers/blueprint" + ctfocm "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/tarutils" ) const ( diff --git a/pkg/contexts/ocm/download/handlers/blueprint/extractor.go b/api/ocm/extensions/download/handlers/blueprint/extractor.go similarity index 85% rename from pkg/contexts/ocm/download/handlers/blueprint/extractor.go rename to api/ocm/extensions/download/handlers/blueprint/extractor.go index 9a90af8b7..481eda4f4 100644 --- a/pkg/contexts/ocm/download/handlers/blueprint/extractor.go +++ b/api/ocm/extensions/download/handlers/blueprint/extractor.go @@ -5,13 +5,13 @@ import ( "github.com/mandelsoft/vfs/pkg/projectionfs" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common" - "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/common/compression" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/utils/tarutils" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/compression" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/tarutils" ) const ( diff --git a/api/ocm/extensions/download/handlers/blueprint/handler.go b/api/ocm/extensions/download/handlers/blueprint/handler.go new file mode 100644 index 000000000..f5736d851 --- /dev/null +++ b/api/ocm/extensions/download/handlers/blueprint/handler.go @@ -0,0 +1,85 @@ +package blueprint + +import ( + "github.com/mandelsoft/goutils/finalizer" + "github.com/mandelsoft/goutils/set" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/ocm/cpi" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + registry "ocm.software/ocm/api/ocm/extensions/download" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/mime" + common "ocm.software/ocm/api/utils/misc" +) + +const ( + TYPE = resourcetypes.BLUEPRINT + LEGACY_TYPE = resourcetypes.BLUEPRINT_LEGACY + CONFIG_MIME_TYPE = "application/vnd.gardener.landscaper.blueprint.config.v1" +) + +type Extractor func(pr common.Printer, handler *Handler, access blobaccess.DataAccess, path string, fs vfs.FileSystem) (bool, error) + +var ( + supportedArtifactTypes []string + mimeTypeExtractorRegistry map[string]Extractor +) + +type Handler struct { + ociConfigMimeTypes set.Set[string] +} + +func init() { + supportedArtifactTypes = []string{TYPE, LEGACY_TYPE} + mimeTypeExtractorRegistry = map[string]Extractor{ + mime.MIME_TAR: ExtractArchive, + mime.MIME_TGZ: ExtractArchive, + mime.MIME_TGZ_ALT: ExtractArchive, + BLUEPRINT_MIMETYPE: ExtractArchive, + BLUEPRINT_MIMETYPE_COMPRESSED: ExtractArchive, + BLUEPRINT_MIMETYPE_LEGACY: ExtractArchive, + BLUEPRINT_MIMETYPE_LEGACY_COMPRESSED: ExtractArchive, + } + for _, t := range append(artdesc.ToArchiveMediaTypes(artdesc.MediaTypeImageManifest), artdesc.ToArchiveMediaTypes(artdesc.MediaTypeDockerSchema2Manifest)...) { + mimeTypeExtractorRegistry[t] = ExtractArtifact + } + + h := New() + + registry.Register(h, registry.ForArtifactType(TYPE)) + registry.Register(h, registry.ForArtifactType(LEGACY_TYPE)) +} + +func New(configmimetypes ...string) *Handler { + if len(configmimetypes) == 0 || utils.Optional(configmimetypes...) == "" { + configmimetypes = []string{CONFIG_MIME_TYPE} + } + return &Handler{ + ociConfigMimeTypes: set.New[string](configmimetypes...), + } +} + +func (h *Handler) Download(pr common.Printer, racc cpi.ResourceAccess, path string, fs vfs.FileSystem) (_ bool, _ string, err error) { + var finalize finalizer.Finalizer + defer finalize.FinalizeWithErrorPropagationf(&err, "downloading blueprint") + + meth, err := racc.AccessMethod() + if err != nil { + return false, "", err + } + finalize.Close(meth) + + ex := mimeTypeExtractorRegistry[meth.MimeType()] + if ex == nil { + return false, "", nil + } + + ok, err := ex(pr, h, meth, path, fs) + if err != nil || !ok { + return ok, "", err + } + return true, path, nil +} diff --git a/api/ocm/extensions/download/handlers/blueprint/registration.go b/api/ocm/extensions/download/handlers/blueprint/registration.go new file mode 100644 index 000000000..dfe3315a7 --- /dev/null +++ b/api/ocm/extensions/download/handlers/blueprint/registration.go @@ -0,0 +1,95 @@ +package blueprint + +import ( + "fmt" + "strings" + + "github.com/mandelsoft/goutils/errors" + "golang.org/x/exp/slices" + + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/download" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/listformat" + "ocm.software/ocm/api/utils/registrations" +) + +const PATH = "landscaper/blueprint" + +func init() { + download.RegisterHandlerRegistrationHandler(PATH, &RegistrationHandler{}) +} + +type Config struct { + OCIConfigTypes []string `json:"ociConfigTypes"` +} + +func AttributeDescription() map[string]string { + return map[string]string{ + "ociConfigTypes": "a list of accepted OCI config archive mime types\n" + + "defaulted by " + CONFIG_MIME_TYPE + ".", + } +} + +type RegistrationHandler struct{} + +var _ download.HandlerRegistrationHandler = (*RegistrationHandler)(nil) + +func (r *RegistrationHandler) RegisterByName(handler string, ctx download.Target, config download.HandlerConfig, olist ...download.HandlerOption) (bool, error) { + var err error + + if handler != "" { + return true, fmt.Errorf("invalid blueprint handler %q", handler) + } + + opts := download.NewHandlerOptions(olist...) + if opts.MimeType != "" && !slices.Contains(supportedArtifactTypes, opts.ArtifactType) { + return false, errors.Newf("artifact type %s not supported", opts.ArtifactType) + } + + if opts.MimeType != "" { + if _, ok := mimeTypeExtractorRegistry[opts.MimeType]; !ok { + return false, errors.Newf("mime type %s not supported", opts.MimeType) + } + } + + attr, err := registrations.DecodeDefaultedConfig[Config](config) + if err != nil { + return true, errors.Wrapf(err, "cannot unmarshal download handler configuration") + } + + h := New(attr.OCIConfigTypes...) + if opts.MimeType == "" { + for m := range mimeTypeExtractorRegistry { + opts.MimeType = m + download.For(ctx).Register(h, opts) + } + } else { + download.For(ctx).Register(h, opts) + } + + return true, nil +} + +func (r *RegistrationHandler) GetHandlers(ctx cpi.Context) registrations.HandlerInfos { + return registrations.NewLeafHandlerInfo("uploading an OCI artifact to an OCI registry", ` +The artifact downloader is able to transfer OCI artifact-like resources +into an OCI registry given by the combination of the download target and the +registration config. + +If no config is given, the target must be an OCI reference with a potentially +omitted repository. The repo part is derived from the reference hint provided +by the resource's access specification. + +If the config is given, the target is used as repository name prefixed with an +optional repository prefix given by the configuration. + +The following artifact media types are supported: +`+listformat.FormatList("", utils.StringMapKeys(mimeTypeExtractorRegistry)...)+` +It accepts a config with the following fields: +`+listformat.FormatMapElements("", AttributeDescription())+` + +This handler is by default registered for the following artifact types: +`+strings.Join(supportedArtifactTypes, ","), + ) +} diff --git a/api/ocm/extensions/download/handlers/blueprint/registration_test.go b/api/ocm/extensions/download/handlers/blueprint/registration_test.go new file mode 100644 index 000000000..192eb26b7 --- /dev/null +++ b/api/ocm/extensions/download/handlers/blueprint/registration_test.go @@ -0,0 +1,78 @@ +package blueprint_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + + "github.com/mandelsoft/vfs/pkg/projectionfs" + + tenv "ocm.software/ocm/api/helper/env" + "ocm.software/ocm/api/oci/testhelper" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + "ocm.software/ocm/api/ocm/extensions/download" + "ocm.software/ocm/api/ocm/extensions/download/handlers/blueprint" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/tarutils" +) + +var _ = Describe("blueprint downloader registration", func() { + var env *Builder + + BeforeEach(func() { + env = NewBuilder(tenv.TestData()) + + MustBeSuccessful(tarutils.CreateTarFromFs(Must(projectionfs.New(env, TESTDATA_PATH)), ARCHIVE_PATH, tarutils.Gzip, env)) + + env.OCICommonTransport(OCI, accessio.FormatDirectory, func() { + env.Namespace(OCINAMESPACE, func() { + env.Manifest(OCIVERSION, func() { + env.Config(func() { + env.BlobStringData(MIMETYPE, "{}") + }) + env.Layer(func() { + env.BlobFromFile(MIMETYPE, ARCHIVE_PATH) + }) + }) + }) + }) + + testhelper.FakeOCIRepo(env, OCI, OCIHOST) + env.OCMCommonTransport(CTF, accessio.FormatDirectory, func() { + env.ComponentVersion(COMPONENT, VERSION, func() { + env.Resource(OCI_ARTIFACT_NAME, ARTIFACT_VERSION, ARTIFACT_TYPE, v1.ExternalRelation, func() { + env.Access(ociartifact.New(OCIHOST + ".alias/" + OCINAMESPACE + ":" + OCIVERSION)) + }) + }) + }) + }) + + AfterEach(func() { + env.Cleanup() + }) + + It("register and use blueprint downloader for artifact type \"testartifacttype\"", func() { + // As the handler is not registered for the artifact type "testartifacttype" per default (thus, in the + // init-function of handler.go), this test fails if the registration does not work. + Expect(download.For(env).RegisterByName(blueprint.PATH, env.OCMContext(), &blueprint.Config{[]string{MIMETYPE}}, download.ForArtifactType(ARTIFACT_TYPE))).To(BeTrue()) + + repo := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, CTF, 0, env)) + defer Close(repo) + cv := Must(repo.LookupComponentVersion(COMPONENT, VERSION)) + defer Close(cv) + racc := Must(cv.GetResourceByIndex(0)) + + p, buf := common.NewBufferedPrinter() + ok, path := Must2(download.For(env).Download(p, racc, DOWNLOAD_PATH, env)) + Expect(ok).To(BeTrue()) + Expect(path).To(Equal(DOWNLOAD_PATH)) + Expect(env.FileExists(DOWNLOAD_PATH + "/blueprint.yaml")).To(BeTrue()) + Expect(env.FileExists(DOWNLOAD_PATH + "/test/README.md")).To(BeTrue()) + Expect(buf.String()).To(StringEqualTrimmedWithContext(DOWNLOAD_PATH + ": 2 file(s) with 390 byte(s) written")) + }) +}) diff --git a/pkg/contexts/ocm/download/handlers/blueprint/suite_test.go b/api/ocm/extensions/download/handlers/blueprint/suite_test.go similarity index 100% rename from pkg/contexts/ocm/download/handlers/blueprint/suite_test.go rename to api/ocm/extensions/download/handlers/blueprint/suite_test.go diff --git a/pkg/contexts/ocm/download/handlers/blueprint/testdata/blueprint/blueprint.yaml b/api/ocm/extensions/download/handlers/blueprint/testdata/blueprint/blueprint.yaml similarity index 100% rename from pkg/contexts/ocm/download/handlers/blueprint/testdata/blueprint/blueprint.yaml rename to api/ocm/extensions/download/handlers/blueprint/testdata/blueprint/blueprint.yaml diff --git a/pkg/contexts/ocm/download/handlers/blueprint/testdata/blueprint/test/README.md b/api/ocm/extensions/download/handlers/blueprint/testdata/blueprint/test/README.md similarity index 100% rename from pkg/contexts/ocm/download/handlers/blueprint/testdata/blueprint/test/README.md rename to api/ocm/extensions/download/handlers/blueprint/testdata/blueprint/test/README.md diff --git a/api/ocm/extensions/download/handlers/dirtree/README.md b/api/ocm/extensions/download/handlers/dirtree/README.md new file mode 100644 index 000000000..fc1db442a --- /dev/null +++ b/api/ocm/extensions/download/handlers/dirtree/README.md @@ -0,0 +1,82 @@ +# Directory Tree Downloader + +The standard configuration now provides a downloader for resources of type `directoryTree`and the legacy type `filesystem`. + +It acts on the mimetypes for an artifact set (`application/vnd.oci.image.manifest.v1+tar+gzip`) and a tar/tgz archive (`application/x-tar`, `application/x-tar+gzip`, `application/x-tgz`). The default configuration extracts the content to a +filesystem folder. If the blob format is an artifact set, for example provided by the access method `ociArtifact`, +the default configuration accepts the image config mimetype (`application/vnd.oci.image.config.v1+json`). +In this case the final filesystem content provided by the image is downloaded by evaluating the layered image file system. + +The default behaviour can just be used with +
+import "ocm.software/ocm/api/ocm/extensions/download"
+download.For(octx).Download(printer,resourceAccess,targetdir,vfs)
+
+ +If the resource access describes an appropriate artifact, the new handler is automatically selected. + +As usual, the target is always a virtual filesystem. + +Like all download handlers. the dirtree download handler can also explicitly be used with + +
+import "ocm.software/ocm/api/ocm/extensions/download/handlers/dirtree"
+dirtree.New().Download(...)
+
+ +In this mode, the behaviour can be influenced by specifying any list of accepted OCI artifact config mime types. + +With `New(mimetypes ...string).SetArchiveMode(true)` it is possible to enable the archive target mode. The content is downloaded to an archive instead of extracted filesystem content. + +The handler checks the mimetypes, only, but the default registration is done exclusively for the directory content resource types. +A context can be extended for other resource types with + +``` +download.For(octx),Register(dirtree.New...(...), download.ForCombi(type, [mimetype])) +``` + +The handler also provides additional methods, which can be used to execute more specific tasks, for example +methods for an optimized content access providing an internal virtual filesystem or an archive byte stream trying to avoid unnecessary conversions depending on the actual input format, + +The localization package has been adapted, accordingly. The `localize.Instantiate` method now prefers to use the +download handlers to get to the filesystem content to be configured, instead of expecting an archive resource. +Therefore, any (potentially own) resource type with any format can be used, as long as there is an appropriate downloader configured for the used OCM context. An optional additional parameter can be used to restrict the accepted resource types +to an explicitly given set of types. + +## Use cases + +If you use `dirtree.New(...).Download(...)` you explicitly use the `dirtree` downloader and nothing else. +It only checks the mime types, but not the resource types. So, you can enforce to use it on resources, +regardless of their type to download dirtree-like resources (with a matching mime type). + +If you use `download.For(...).Download(...)` it tries to find a registered downloader with registration +criteria matching the actual resource. This can be used without bothering with the kind of actually used +resource (to just download it, whatever it is in a standard manner). If a matching downloader is found, +it is used, otherwise just the blob is downloaded as provided by the access method. Here, for sure the +`dirtree` downloader is used for the standard scenarios (it is registered for). This is especially the +`directoryTree` resource type with the tar-like mimetypes and the oci artifact archive mime type. But it +is not used for other scenarios. + +So, if you want to use an own resource type (directly expressing the dedicated new meaning of a general +filesystem content), for example `gitOpsTemplate`, which is more expressive than just `directoryTree`. You +can +- either register the `dirtree` handler in advance for your OCM context and for this resource type at the +- registry (then it would automatically be chosen for all downloads using this context) +- or you know what you are doing, and explicitly call the `dirtree` downloader on such a resource. + +If, for example `gitOpsTemplate` should be a standard resource type, we should add such a registration as +part of the standard. + +Another possible scenario, where you might want to use the explicit `dirtree` usage is to overwrite the +standard behaviour for a special use case. For example, an OCI image is typically downloaded as OCI +artifact with the distribution spec format. But. if you want to access the effective filesystem, you +could explicitly use the `dirtree` downloader for an OCI image, which handles this for you. It would +make less sense to use it on a helm chart OCI artifact, because here the layers have a different meaning +than building a directory tree. + +## Registration Handler + +It provides a registration handler with the path `ocm/dirtree`. and a config +object with the fields: +- *`asArchive`* *bool*: download as archive (default is directory tree). +- *`ociConfigtypes`* *[]string*: list of accepted OCI manifest config media types. Default is the OCI image config media type. \ No newline at end of file diff --git a/pkg/contexts/ocm/download/handlers/dirtree/dirtree_test.go b/api/ocm/extensions/download/handlers/dirtree/dirtree_test.go similarity index 92% rename from pkg/contexts/ocm/download/handlers/dirtree/dirtree_test.go rename to api/ocm/extensions/download/handlers/dirtree/dirtree_test.go index eccd219f1..c4818eb29 100644 --- a/pkg/contexts/ocm/download/handlers/dirtree/dirtree_test.go +++ b/api/ocm/extensions/download/handlers/dirtree/dirtree_test.go @@ -11,20 +11,20 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" ociv1 "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/open-component-model/ocm/pkg/common" - "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/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download/handlers/dirtree" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - env2 "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/utils/tarutils" + "ocm.software/ocm/api/helper/builder" + env2 "ocm.software/ocm/api/helper/env" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/download" + "ocm.software/ocm/api/ocm/extensions/download/handlers/dirtree" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/mime" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/tarutils" ) const ( diff --git a/api/ocm/extensions/download/handlers/dirtree/handler.go b/api/ocm/extensions/download/handlers/dirtree/handler.go new file mode 100644 index 000000000..06d54d375 --- /dev/null +++ b/api/ocm/extensions/download/handlers/dirtree/handler.go @@ -0,0 +1,364 @@ +package dirtree + +import ( + "fmt" + "io" + "os" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/finalizer" + "github.com/mandelsoft/goutils/general" + "github.com/mandelsoft/goutils/set" + "github.com/mandelsoft/vfs/pkg/layerfs" + "github.com/mandelsoft/vfs/pkg/osfs" + "github.com/mandelsoft/vfs/pkg/projectionfs" + "github.com/mandelsoft/vfs/pkg/vfs" + "golang.org/x/exp/slices" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/ocm/cpi" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/download" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/compression" + "ocm.software/ocm/api/utils/iotools" + "ocm.software/ocm/api/utils/mime" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/tarutils" +) + +var ( + MimeOCIImageArtifactArchive = artifactset.MediaType(artdesc.MediaTypeImageManifest) + MimeOCIImageArtifact = artdesc.ToContentMediaType(artdesc.MediaTypeImageManifest) +) + +var ( + supportedMimeTypes = []string{MimeOCIImageArtifactArchive, mime.MIME_TGZ, mime.MIME_TGZ_ALT, mime.MIME_TAR} + defaultArtifactTypes = []string{resourcetypes.DIRECTORY_TREE, resourcetypes.FILESYSTEM_LEGACY} +) + +func SupportedMimeTypes() []string { + return slices.Clone(supportedMimeTypes) +} + +type Handler struct { + ociConfigtypes set.Set[string] + archive bool +} + +func New(mimetypes ...string) *Handler { + if len(mimetypes) == 0 || general.Optional(mimetypes...) == "" { + mimetypes = []string{artdesc.MediaTypeImageConfig} + } + return &Handler{ + ociConfigtypes: set.New[string](mimetypes...), + } +} + +var DefaultHandler = New() + +func init() { + for _, t := range defaultArtifactTypes { + for _, m := range supportedMimeTypes { + download.Register(DefaultHandler, download.ForCombi(t, m)) + } + } +} + +func (h *Handler) SetArchiveMode(b bool) *Handler { + h.archive = b + return h +} + +func (h *Handler) Download(p common.Printer, racc cpi.ResourceAccess, path string, fs vfs.FileSystem) (bool, string, error) { + lfs, r, err := h.GetForResource(racc) + if err != nil || (lfs == nil && r == nil) { + return err != nil, "", err + } + if path == "" { + path = racc.Meta().GetName() + } + return h.download(p, fs, path, lfs, r) +} + +func (h *Handler) DownloadFromArtifactSet(pr common.Printer, set *artifactset.ArtifactSet, path string, fs vfs.FileSystem) (bool, string, error) { + lfs, r, err := h.GetForArtifactSet(set) + if err != nil || (lfs == nil && r != nil) { + return err != nil, "", err + } + if path == "" { + path = set.GetMain().String() + } + return h.download(common.NewPrinter(nil), fs, path, lfs, r) +} + +func (h *Handler) download(pr common.Printer, fs vfs.FileSystem, path string, lfs vfs.FileSystem, r io.ReadCloser) (ok bool, dest string, err error) { + var finalize finalizer.Finalizer + defer finalize.FinalizeWithErrorPropagation(&err) + + if r != nil { + finalize.Close(r) + } + if lfs != nil { + finalize.With(func() error { return vfs.Cleanup(lfs) }) + } + if h.archive { + w, err := fs.OpenFile(path, vfs.O_TRUNC|vfs.O_CREATE|vfs.O_WRONLY, 0o600) + if err != nil { + return true, "", errors.Wrapf(err, "cannot write target archive %s", path) + } + finalize.Close(w) + if r != nil { + n, err := io.Copy(w, r) + if err != nil { + return true, "", errors.Wrapf(err, "cannot copy to archive %s", path) + } + pr.Printf("%s: %d byte(s) written\n", path, n) + return true, path, nil + } else { + cw := iotools.NewCountingWriter(w) + err := tarutils.PackFsIntoTar(lfs, "", cw, tarutils.TarFileSystemOptions{}) + if err == nil { + pr.Printf("%s: %d byte(s) written\n", path, cw.Size()) + } + return true, path, err + } + } else { + err := fs.MkdirAll(path, 0o700) + if err != nil { + return true, "", errors.Wrapf(err, "cannot create target directory") + } + + var fcnt, size int64 + if r != nil { + var p vfs.FileSystem + p, err = projectionfs.New(fs, path) + if err != nil { + return true, "", err + } + fcnt, size, err = tarutils.ExtractTarToFsWithInfo(p, r) + } else { + fcnt, size, err = CopyDir(lfs, "/", fs, path) + } + if err == nil { + pr.Printf("%s: %d file(s) with %d byte(s) written\n", path, fcnt, size) + } + return true, path, err + } +} + +// GetForResource provides a virtual filesystem for an OCi image manifest +// provided by the given resource matching the configured config types. +// It returns nil without error, if the OCI artifact does not match the requirement. +func (h *Handler) GetForResource(racc cpi.ResourceAccess) (fs vfs.FileSystem, reader io.ReadCloser, err error) { + var finalize finalizer.Finalizer + defer finalize.FinalizeWithErrorPropagation(&err) + + meth, err := racc.AccessMethod() + if err != nil { + return nil, nil, err + } + finalize.Close(meth) + + media := mime.BaseType(meth.MimeType()) + + switch media { + case mime.MIME_TGZ, mime.MIME_TAR: + case MimeOCIImageArtifact: + default: + if !h.ociConfigtypes.Contains(media) && !h.ociConfigtypes.Contains(meth.MimeType()) { + return nil, nil, nil + } + } + + r, err := meth.Reader() + if err != nil { + return nil, nil, err + } + if media != MimeOCIImageArtifact { + r, _, err = compression.AutoDecompress(r) + if err != nil { + return nil, nil, errors.Wrapf(err, "cannot determine compression for filesystem blob") + } + return nil, finalize.BindToReader(r), nil + } + finalize.Close(r) + set, err := artifactset.Open(accessobj.ACC_READONLY, "", 0, accessio.Reader(r)) + if err != nil { + return nil, nil, err + } + finalize.Close(set) + return h.getForArtifactSet(&finalize, set) +} + +// GetForArtifactSet provides a virtual filesystem for an OCi image manifest +// provided by the given artifact set matching the configured config types. +// It returns nil without error, if the OCI artifact does not match the requirement. +func (h *Handler) GetForArtifactSet(set *artifactset.ArtifactSet) (fs vfs.FileSystem, reader io.ReadCloser, err error) { + var finalize finalizer.Finalizer + defer finalize.FinalizeWithErrorPropagation(&err) + + return h.getForArtifactSet(&finalize, set) +} + +func (h *Handler) getForArtifactSet(finalize *finalizer.Finalizer, set *artifactset.ArtifactSet) (fs vfs.FileSystem, reader io.ReadCloser, err error) { + m, err := set.GetArtifact(set.GetMain().String()) + if err != nil { + return nil, nil, err + } + finalize.Close(m) + + return h.getForArtifact(finalize, m) +} + +// GetForArtifact provides a virtual filesystem for an OCi image manifest. +// It returns nil without error, if the OCI artifact does not match the requirement. +func (h *Handler) GetForArtifact(art oci.ArtifactAccess) (fs vfs.FileSystem, reader io.ReadCloser, err error) { + var finalize finalizer.Finalizer + defer finalize.FinalizeWithErrorPropagation(&err) + + return h.getForArtifact(&finalize, art) +} + +func (h *Handler) getForArtifact(finalize *finalizer.Finalizer, m oci.ArtifactAccess) (fs vfs.FileSystem, reader io.ReadCloser, err error) { + if !m.IsManifest() { + return nil, nil, fmt.Errorf("oci artifact is no image manifest") + } + macc := m.ManifestAccess() + if !h.ociConfigtypes.Contains(macc.GetDescriptor().Config.MediaType) { + return nil, nil, nil + } + + var cfs vfs.FileSystem + finalize.With(func() error { + return vfs.Cleanup(cfs) + }) + + // setup layered filesystem from manifest layers + for i, l := range macc.GetDescriptor().Layers { + nested := finalize.Nested() + + blob, err := macc.GetBlob(l.Digest) + if err != nil { + return nil, nil, errors.Wrapf(err, "cannot get blob for layer %d", i) + } + nested.Close(blob) + r, err := blob.Reader() + if err != nil { + return nil, nil, errors.Wrapf(err, "cannot get reader for layer blob %d", i) + } + nested.Close(r) + r, _, err = compression.AutoDecompress(r) + if err != nil { + return nil, nil, errors.Wrapf(err, "cannot determine compression for layer blob %d", i) + } + + if len(macc.GetDescriptor().Layers) == 1 { + // return archive reader to enable optimized handling bay caller + return nil, finalize.BindToReader(r), nil + } + + nested.Close(r) + + fslayer, err := osfs.NewTempFileSystem() + if err != nil { + return nil, nil, errors.Wrapf(err, "cannot create filesystem for layer %d", i) + } + nested.With(func() error { + return vfs.Cleanup(fslayer) + }) + err = tarutils.ExtractTarToFs(fslayer, r) + if err != nil { + return nil, nil, errors.Wrapf(err, "cannot unpack layer blob %d", i) + } + + if cfs == nil { + cfs = fslayer + } else { + cfs = layerfs.New(fslayer, cfs) + } + fslayer = nil // don't cleanup used layer + if err := nested.Finalize(); err != nil { + return nil, nil, err + } + } + fs = cfs + cfs = nil // don't cleanup used filesystem + return fs, nil, nil +} + +// TODO: to be moved to vfs + +// CopyDir recursively copies a directory tree, attempting to preserve permissions. +// Source directory must exist, destination directory may exist. +// Symlinks are ignored and skipped. +func CopyDir(srcfs vfs.FileSystem, src string, dstfs vfs.FileSystem, dst string) (int64, int64, error) { + var fcnt, bcnt int64 + var n, b int64 + + src = vfs.Trim(srcfs, src) + dst = vfs.Trim(dstfs, dst) + + si, err := srcfs.Stat(src) + if err != nil { + return 0, 0, err + } + if !si.IsDir() { + return 0, 0, vfs.NewPathError("CopyDir", src, vfs.ErrNotDir) + } + + di, err := dstfs.Stat(dst) + if err != nil && !os.IsNotExist(err) { + return 0, 0, err + } + if err == nil && !di.IsDir() { + return 0, 0, vfs.NewPathError("CopyDir", dst, vfs.ErrNotDir) + } + + err = dstfs.MkdirAll(dst, si.Mode()) + if err != nil { + return 0, 0, err + } + + entries, err := vfs.ReadDir(srcfs, src) + if err != nil { + return 0, 0, err + } + + for _, entry := range entries { + srcPath := vfs.Join(srcfs, src, entry.Name()) + dstPath := vfs.Join(dstfs, dst, entry.Name()) + + if entry.IsDir() { + n, b, err = CopyDir(srcfs, srcPath, dstfs, dstPath) + fcnt += n + bcnt += b + } else { + // Skip symlinks. + if entry.Mode()&os.ModeSymlink != 0 { + var old string + old, err = srcfs.Readlink(srcPath) + if err == nil { + err = dstfs.Symlink(old, dstPath) + } + if err == nil { + fcnt++ + err = os.Chmod(dst, entry.Mode()) + } + } else { + err = vfs.CopyFile(srcfs, srcPath, dstfs, dstPath) + if err == nil { + bcnt += entry.Size() + fcnt++ + } + } + } + if err != nil { + return fcnt, bcnt, err + } + } + return fcnt, bcnt, nil +} diff --git a/api/ocm/extensions/download/handlers/dirtree/registration.go b/api/ocm/extensions/download/handlers/dirtree/registration.go new file mode 100644 index 000000000..5f521b3c9 --- /dev/null +++ b/api/ocm/extensions/download/handlers/dirtree/registration.go @@ -0,0 +1,81 @@ +package dirtree + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + ociv1 "github.com/opencontainers/image-spec/specs-go/v1" + "golang.org/x/exp/slices" + + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/download" + "ocm.software/ocm/api/utils/listformat" + "ocm.software/ocm/api/utils/registrations" +) + +func init() { + download.RegisterHandlerRegistrationHandler("ocm/dirtree", &RegistrationHandler{}) +} + +type Config struct { + AsArchive bool `json:"asArchive"` + OCIConfigTypes []string `json:"ociConfigTypes"` +} + +func AttributeDescription() map[string]string { + return map[string]string{ + "asArchive": "flag to request an archive download", + "ociConfigTypes": "a list of accepted OCI config archive mime types\n" + + "defaulted by " + ociv1.MediaTypeImageConfig + ".", + } +} + +type RegistrationHandler struct{} + +var _ download.HandlerRegistrationHandler = (*RegistrationHandler)(nil) + +func (r *RegistrationHandler) RegisterByName(handler string, ctx download.Target, config download.HandlerConfig, olist ...download.HandlerOption) (bool, error) { + var err error + + if handler != "" { + return true, fmt.Errorf("invalid dirtree handler %q", handler) + } + + attr, err := registrations.DecodeDefaultedConfig[Config](config) + if err != nil { + return true, errors.Wrapf(err, "cannot unmarshal download handler configuration") + } + + opts := download.NewHandlerOptions(olist...) + if opts.MimeType != "" && !slices.Contains(supportedMimeTypes, opts.MimeType) { + return true, errors.Wrapf(err, "mime type %s not supported", opts.MimeType) + } + if opts.ArtifactType != "" && slices.Contains(defaultArtifactTypes, opts.ArtifactType) && !attr.AsArchive { + return true, nil + } + + h := New(attr.OCIConfigTypes...).SetArchiveMode(attr.AsArchive) + if opts.MimeType == "" { + for _, m := range supportedMimeTypes { + opts.MimeType = m + download.For(ctx).Register(h, opts) + } + } else { + download.For(ctx).Register(h, opts) + } + + return true, nil +} + +func (r *RegistrationHandler) GetHandlers(ctx cpi.Context) registrations.HandlerInfos { + return registrations.NewLeafHandlerInfo("downloading directory tree-like resources", ` +The dirtree downloader is able to download directory-tree like +resources as directory structure (default) or archive. +The following artifact media types are supported: +`+listformat.FormatList("", SupportedMimeTypes()...)+` +By default, it is registered for the following resource types: +`+listformat.FormatList("", defaultArtifactTypes...)+` +It accepts a config with the following fields: +`+listformat.FormatMapElements("", AttributeDescription()), + ) +} diff --git a/api/ocm/extensions/download/handlers/dirtree/registration_test.go b/api/ocm/extensions/download/handlers/dirtree/registration_test.go new file mode 100644 index 000000000..09df184a0 --- /dev/null +++ b/api/ocm/extensions/download/handlers/dirtree/registration_test.go @@ -0,0 +1,137 @@ +package dirtree_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/mandelsoft/vfs/pkg/projectionfs" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/helper/builder" + env2 "ocm.software/ocm/api/helper/env" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/download" + "ocm.software/ocm/api/ocm/extensions/download/handlers/dirtree" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/mime" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/tarutils" +) + +const TEST_ARTIFACT = "testArtifact" + +var _ = Describe("artifact management", func() { + var env *builder.Builder + + BeforeEach(func() { + env = builder.NewBuilder(env2.TestData()) + }) + + AfterEach(func() { + env.Cleanup() + }) + + Context("archive", func() { + BeforeEach(func() { + MustBeSuccessful(tarutils.CreateTarFromFs(Must(projectionfs.New(env, "testdata/layers/all")), "archive", tarutils.Gzip, env)) + + env.OCMCommonTransport("ctf", accessio.FormatDirectory, func() { + env.ComponentVersion(COMPONENT, VERSION, func() { + env.Resource(RESOURCE, VERSION, TEST_ARTIFACT, metav1.LocalRelation, func() { + env.BlobFromFile(artifactset.MediaType(mime.MIME_TGZ_ALT), "archive") + }) + }) + }) + }) + + It("downloads to dir", func() { + Expect(download.For(env).RegisterByName("ocm/dirtree", env.OCMContext(), &dirtree.Config{AsArchive: false}, download.ForArtifactType(TEST_ARTIFACT))).To(BeTrue()) + + repo := Must(ctf.Open(ocm.DefaultContext(), accessobj.ACC_READONLY, "ctf", 0, env)) + defer Close(repo) + cv := Must(repo.LookupComponentVersion(COMPONENT, VERSION)) + defer Close(cv) + res := Must(cv.GetResource(metav1.NewIdentity(RESOURCE))) + + p, buf := common.NewBufferedPrinter() + accepted, path := Must2(download.For(env).Download(p, res, "result", env)) + Expect(accepted).To(BeTrue()) + Expect(path).To(Equal("result")) + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +result: 2 file(s) with 25 byte(s) written +`)) + + data := Must(vfs.ReadFile(env, "result/testfile")) + Expect(string(data)).To(StringEqualWithContext("testdata\n")) + data = Must(vfs.ReadFile(env, "result/dir/nestedfile")) + Expect(string(data)).To(StringEqualWithContext("other test data\n")) + }) + + It("downloads archive to archive", func() { + Expect(download.For(env).RegisterByName("ocm/dirtree", env.OCMContext(), &dirtree.Config{AsArchive: true}, download.ForArtifactType(TEST_ARTIFACT))).To(BeTrue()) + + repo := Must(ctf.Open(ocm.DefaultContext(), accessobj.ACC_READONLY, "ctf", 0, env)) + defer Close(repo) + cv := Must(repo.LookupComponentVersion(COMPONENT, VERSION)) + defer Close(cv) + res := Must(cv.GetResource(metav1.NewIdentity(RESOURCE))) + + p, buf := common.NewBufferedPrinter() + accepted, path := Must2(download.For(env).Download(p, res, "target", env)) + Expect(accepted).To(BeTrue()) + Expect(path).To(Equal("target")) + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +target: 3584 byte(s) written +`)) + + MustBeSuccessful(env.MkdirAll("result", 0o700)) + resultfs := Must(projectionfs.New(env, "result")) + MustBeSuccessful(tarutils.ExtractArchiveToFs(resultfs, "target", env)) + + data := Must(vfs.ReadFile(env, "result/testfile")) + Expect(string(data)).To(StringEqualWithContext("testdata\n")) + data = Must(vfs.ReadFile(env, "result/dir/nestedfile")) + Expect(string(data)).To(StringEqualWithContext("other test data\n")) + }) + + It("downloads archive to archive using config", func() { + spec := ` +type: downloader.ocm.config.ocm.software +registrations: +- name: ocm/dirtree + artifactType: ` + TEST_ARTIFACT + ` + config: + asArchive: true +` + env.ConfigContext().ApplyData([]byte(spec), nil, "manual") + + repo := Must(ctf.Open(ocm.DefaultContext(), accessobj.ACC_READONLY, "ctf", 0, env)) + defer Close(repo) + cv := Must(repo.LookupComponentVersion(COMPONENT, VERSION)) + defer Close(cv) + res := Must(cv.GetResource(metav1.NewIdentity(RESOURCE))) + + p, buf := common.NewBufferedPrinter() + accepted, path := Must2(download.For(env).Download(p, res, "target", env)) + Expect(accepted).To(BeTrue()) + Expect(path).To(Equal("target")) + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +target: 3584 byte(s) written +`)) + + MustBeSuccessful(env.MkdirAll("result", 0o700)) + resultfs := Must(projectionfs.New(env, "result")) + MustBeSuccessful(tarutils.ExtractArchiveToFs(resultfs, "target", env)) + + data := Must(vfs.ReadFile(env, "result/testfile")) + Expect(string(data)).To(StringEqualWithContext("testdata\n")) + data = Must(vfs.ReadFile(env, "result/dir/nestedfile")) + Expect(string(data)).To(StringEqualWithContext("other test data\n")) + }) + }) +}) diff --git a/pkg/contexts/ocm/download/handlers/dirtree/suite_test.go b/api/ocm/extensions/download/handlers/dirtree/suite_test.go similarity index 100% rename from pkg/contexts/ocm/download/handlers/dirtree/suite_test.go rename to api/ocm/extensions/download/handlers/dirtree/suite_test.go diff --git a/pkg/contexts/ocm/download/handlers/dirtree/testdata/layers/0/testfile b/api/ocm/extensions/download/handlers/dirtree/testdata/layers/0/testfile similarity index 100% rename from pkg/contexts/ocm/download/handlers/dirtree/testdata/layers/0/testfile rename to api/ocm/extensions/download/handlers/dirtree/testdata/layers/0/testfile diff --git a/pkg/contexts/ocm/download/handlers/dirtree/testdata/layers/1/dir/nestedfile b/api/ocm/extensions/download/handlers/dirtree/testdata/layers/1/dir/nestedfile similarity index 100% rename from pkg/contexts/ocm/download/handlers/dirtree/testdata/layers/1/dir/nestedfile rename to api/ocm/extensions/download/handlers/dirtree/testdata/layers/1/dir/nestedfile diff --git a/pkg/contexts/ocm/download/handlers/dirtree/testdata/layers/all/dir/nestedfile b/api/ocm/extensions/download/handlers/dirtree/testdata/layers/all/dir/nestedfile similarity index 100% rename from pkg/contexts/ocm/download/handlers/dirtree/testdata/layers/all/dir/nestedfile rename to api/ocm/extensions/download/handlers/dirtree/testdata/layers/all/dir/nestedfile diff --git a/pkg/contexts/ocm/download/handlers/dirtree/testdata/layers/all/testfile b/api/ocm/extensions/download/handlers/dirtree/testdata/layers/all/testfile similarity index 100% rename from pkg/contexts/ocm/download/handlers/dirtree/testdata/layers/all/testfile rename to api/ocm/extensions/download/handlers/dirtree/testdata/layers/all/testfile diff --git a/api/ocm/extensions/download/handlers/executable/handler.go b/api/ocm/extensions/download/handlers/executable/handler.go new file mode 100644 index 000000000..8bfe9f147 --- /dev/null +++ b/api/ocm/extensions/download/handlers/executable/handler.go @@ -0,0 +1,82 @@ +package executable + +import ( + "io" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/ocm/cpi" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/download" + "ocm.software/ocm/api/utils/compression" + "ocm.software/ocm/api/utils/mime" + common "ocm.software/ocm/api/utils/misc" +) + +type Handler struct{} + +func init() { + h := &Handler{} + download.Register(h, download.ForCombi(resourcetypes.OCM_PLUGIN, mime.MIME_OCTET)) + download.Register(h, download.ForCombi(resourcetypes.OCM_PLUGIN, mime.MIME_GZIP)) + download.Register(h, download.ForCombi(resourcetypes.EXECUTABLE, mime.MIME_OCTET)) + download.Register(h, download.ForCombi(resourcetypes.EXECUTABLE, mime.MIME_GZIP)) +} + +func wrapErr(err error, racc cpi.ResourceAccess) error { + if err == nil { + return nil + } + m := racc.Meta() + return errors.Wrapf(err, "resource %s/%s%s", m.GetName(), m.GetVersion(), m.ExtraIdentity.String()) +} + +func (_ Handler) Download(p common.Printer, racc cpi.ResourceAccess, path string, fs vfs.FileSystem) (bool, string, error) { + rd, err := cpi.GetResourceReader(racc) + if err != nil { + return true, "", wrapErr(err, racc) + } + defer rd.Close() + + r, _, err := compression.AutoDecompress(rd) + if err != nil { + return true, "", err + } + if path == "" { + path = racc.Meta().GetName() + } + + t := "" + if ok, err := vfs.Exists(fs, path); err == nil && ok { + t = path + path += ".new" + } + file, err := fs.OpenFile(path, vfs.O_TRUNC|vfs.O_CREATE|vfs.O_WRONLY, 0o660) + if err != nil { + return true, "", wrapErr(errors.Wrapf(err, "creating target file %q", path), racc) + } + n, err := io.Copy(file, r) + file.Close() + if err == nil { + if t != "" { + err = fs.Remove(t) + if err == nil { + err = vfs.CopyFile(fs, path, fs, t) + } + if err == nil { + err = fs.Remove(path) + } + if err == nil { + path = t + } else { + p.Printf("cannot replace existing target file %s -> downloaded to %s\n", t, path) + } + } + p.Printf("%s: %d byte(s) written\n", path, n) + fs.Chmod(path, 0o755) + } else { + fs.Remove(path) + } + return true, path, wrapErr(err, racc) +} diff --git a/api/ocm/extensions/download/handlers/helm/download.go b/api/ocm/extensions/download/handlers/helm/download.go new file mode 100644 index 000000000..6cbc663e8 --- /dev/null +++ b/api/ocm/extensions/download/handlers/helm/download.go @@ -0,0 +1,67 @@ +package helm + +import ( + "strings" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/finalizer" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + common "ocm.software/ocm/api/utils/misc" +) + +func Download(p common.Printer, ctx oci.Context, ref string, path string, fs vfs.FileSystem, creds ...credentials.CredentialsSource) error { + _, _, _, err := Download2(p, ctx, ref, path, fs, false, creds...) + return err +} + +func Download2(p common.Printer, ctx oci.Context, ref string, path string, fs vfs.FileSystem, asartifact bool, creds ...credentials.CredentialsSource) (chart, prov string, aset string, err error) { + var finalize finalizer.Finalizer + defer finalize.FinalizeWithErrorPropagationf(&err, "downloading helm chart %q", ref) + + r, err := oci.ParseRef(ref) + if err != nil { + return + } + + spec, err := ctx.MapUniformRepositorySpec(&r.UniformRepositorySpec) + if err != nil { + return + } + + repo, err := ctx.RepositoryForSpec(spec, creds...) + if err != nil { + return + } + finalize.Close(repo) + + art, err := repo.LookupArtifact(r.Repository, r.Version()) + if err != nil { + return + } + finalize.Close(art) + + if asartifact { + aset = strings.TrimSuffix(path, ".tgz") + ".ctf" + ctf, err := artifactset.Open(accessobj.ACC_CREATE|accessobj.ACC_WRITABLE, aset, 0o600, accessio.FormatTGZ, accessio.PathFileSystem(fs)) + if err != nil { + return "", "", "", errors.Wrapf(err, "cannot create artifact set") + } + err = artifactset.TransferArtifact(art, ctf) + if err == nil { + ctf.Annotate(artifactset.MAINARTIFACT_ANNOTATION, art.Digest().String()) + } + ctf.Close() + if err != nil { + fs.Remove(aset) + return "", "", "", errors.Wrapf(err, "cannot transfer helm OCI artifact") + } + } + chart, prov, err = download(p, art, path, fs) + return chart, prov, aset, err +} diff --git a/api/ocm/extensions/download/handlers/helm/handler.go b/api/ocm/extensions/download/handlers/helm/handler.go new file mode 100644 index 000000000..521e4a465 --- /dev/null +++ b/api/ocm/extensions/download/handlers/helm/handler.go @@ -0,0 +1,152 @@ +package helm + +import ( + "io" + "strings" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/finalizer" + "github.com/mandelsoft/vfs/pkg/vfs" + helmregistry "helm.sh/helm/v3/pkg/registry" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/ocm/cpi" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + registry "ocm.software/ocm/api/ocm/extensions/download" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/mime" + common "ocm.software/ocm/api/utils/misc" +) + +const TYPE = resourcetypes.HELM_CHART + +type Handler struct{} + +func init() { + registry.Register(&Handler{}, registry.ForArtifactType(TYPE)) +} + +func (h Handler) fromArchive(p common.Printer, meth cpi.AccessMethod, path string, fs vfs.FileSystem) (_ bool, _ string, err error) { + basetype := mime.BaseType(helmregistry.ChartLayerMediaType) + if mime.BaseType(meth.MimeType()) != basetype { + return false, "", nil + } + + chart := path + if !strings.HasSuffix(chart, ".tgz") { + chart += ".tgz" + } + err = write(p, meth, chart, fs) + if err != nil { + return true, "", err + } + return true, chart, nil +} + +func (h Handler) fromOCIArtifact(p common.Printer, meth cpi.AccessMethod, path string, fs vfs.FileSystem) (_ bool, _ string, err error) { + var finalize finalizer.Finalizer + defer finalize.FinalizeWithErrorPropagationf(&err, "from OCI artifact") + + rd, err := meth.Reader() + if err != nil { + return true, "", err + } + finalize.Close(rd, "access method reader") + set, err := artifactset.Open(accessobj.ACC_READONLY, "", 0, accessio.Reader(rd)) + if err != nil { + return true, "", err + } + finalize.Close(set, "artifact set") + art, err := set.GetArtifact(set.GetMain().String()) + if err != nil { + return true, "", err + } + finalize.Close(art) + chart, _, err := download(p, art, path, fs) + if err != nil { + return true, "", err + } + return true, chart, nil +} + +func (h Handler) Download(p common.Printer, racc cpi.ResourceAccess, path string, fs vfs.FileSystem) (_ bool, _ string, err error) { + var finalize finalizer.Finalizer + defer finalize.FinalizeWithErrorPropagationf(&err, "downloading helm chart") + + if path == "" { + path = racc.Meta().GetName() + } + + meth, err := racc.AccessMethod() + if err != nil { + return false, "", err + } + finalize.Close(meth) + if mime.BaseType(meth.MimeType()) != mime.BaseType(artdesc.MediaTypeImageManifest) { + return h.fromArchive(p, meth, path, fs) + } + return h.fromOCIArtifact(p, meth, path, fs) +} + +func download(p common.Printer, art oci.ArtifactAccess, path string, fs vfs.FileSystem) (chart, prov string, err error) { + var finalize finalizer.Finalizer + defer finalize.FinalizeWithErrorPropagation(&err) + + m := art.ManifestAccess() + if m == nil { + return "", "", errors.Newf("artifact is no image manifest") + } + if len(m.GetDescriptor().Layers) < 1 { + return "", "", errors.Newf("no layers found") + } + chart = path + if !strings.HasSuffix(chart, ".tgz") { + chart += ".tgz" + } + blob, err := m.GetBlob(m.GetDescriptor().Layers[0].Digest) + if err != nil { + return "", "", err + } + finalize.Close(blob) + err = write(p, blob, chart, fs) + if err != nil { + return "", "", err + } + if len(m.GetDescriptor().Layers) > 1 { + prov = chart[:len(chart)-3] + "prov" + blob, err := m.GetBlob(m.GetDescriptor().Layers[1].Digest) + if err != nil { + return "", "", err + } + err = write(p, blob, path, fs) + if err != nil { + return "", "", err + } + } + return chart, prov, err +} + +func write(p common.Printer, blob blobaccess.DataReader, path string, fs vfs.FileSystem) (err error) { + var finalize finalizer.Finalizer + defer finalize.FinalizeWithErrorPropagation(&err) + + cr, err := blob.Reader() + if err != nil { + return err + } + finalize.Close(cr) + file, err := fs.OpenFile(path, vfs.O_TRUNC|vfs.O_CREATE|vfs.O_WRONLY, 0o660) + if err != nil { + return err + } + finalize.Close(file) + n, err := io.Copy(file, cr) + if err == nil { + p.Printf("%s: %d byte(s) written\n", path, n) + } + return nil +} diff --git a/api/ocm/extensions/download/handlers/init.go b/api/ocm/extensions/download/handlers/init.go new file mode 100644 index 000000000..6f8614fa4 --- /dev/null +++ b/api/ocm/extensions/download/handlers/init.go @@ -0,0 +1,10 @@ +package handlers + +import ( + _ "ocm.software/ocm/api/ocm/extensions/download/handlers/blob" + _ "ocm.software/ocm/api/ocm/extensions/download/handlers/blueprint" + _ "ocm.software/ocm/api/ocm/extensions/download/handlers/dirtree" + _ "ocm.software/ocm/api/ocm/extensions/download/handlers/executable" + _ "ocm.software/ocm/api/ocm/extensions/download/handlers/helm" + _ "ocm.software/ocm/api/ocm/extensions/download/handlers/ocirepo" +) diff --git a/api/ocm/extensions/download/handlers/ocirepo/handler.go b/api/ocm/extensions/download/handlers/ocirepo/handler.go new file mode 100644 index 000000000..f29cacca5 --- /dev/null +++ b/api/ocm/extensions/download/handlers/ocirepo/handler.go @@ -0,0 +1,190 @@ +package ocirepo + +import ( + "fmt" + "strings" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/finalizer" + "github.com/mandelsoft/goutils/general" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/oci/grammar" + "ocm.software/ocm/api/oci/tools/transfer" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + "ocm.software/ocm/api/ocm/extensions/attrs/ociuploadattr" + "ocm.software/ocm/api/ocm/extensions/download" + "ocm.software/ocm/api/utils/accessobj" + common "ocm.software/ocm/api/utils/misc" +) + +//////////////////////////////////////////////////////////////////////////////// + +type handler struct { + spec *ociuploadattr.Attribute +} + +func New(repospec ...*ociuploadattr.Attribute) download.Handler { + return &handler{spec: general.Optional(repospec...)} +} + +func (h *handler) Download(p common.Printer, racc cpi.ResourceAccess, path string, fs vfs.FileSystem) (accepted bool, target string, err error) { + var finalize finalizer.Finalizer + defer finalize.FinalizeWithErrorPropagationf(&err, "upload to OCI registry") + + ctx := racc.GetOCMContext() + m, err := racc.AccessMethod() + if err != nil { + return false, "", err + } + finalize.Close(m, "access method for download") + + mediaType := m.MimeType() + + if !artdesc.IsOCIMediaType(mediaType) || (!strings.HasSuffix(mediaType, "+tar") && !strings.HasSuffix(mediaType, "+tar+gzip")) { + return false, "", nil + } + + log := download.Logger(ctx).WithName("ocireg") + + var repo oci.Repository + + var version string = "latest" + + aspec := m.AccessSpec() + namespace := racc.ReferenceHint() + if l, ok := aspec.(*localblob.AccessSpec); namespace == "" && ok { + namespace = l.ReferenceName + } + + i := strings.LastIndex(namespace, ":") + if i > 0 { + version = namespace[i:] + version = version[1:] // remove colon + namespace = namespace[:i] + } + + ocictx := ctx.OCIContext() + + var artspec oci.ArtSpec + var prefix string + var result oci.RefSpec + + if h.spec == nil { + log.Debug("no config set") + if path == "" { + return false, "", fmt.Errorf("path required as target repo specification") + } + ref, err := oci.ParseRef(path) + if err != nil { + return true, "", err + } + result.UniformRepositorySpec = ref.UniformRepositorySpec + repospec, err := ocictx.MapUniformRepositorySpec(&ref.UniformRepositorySpec) + if err != nil { + return true, "", err + } + repo, err = ocictx.RepositoryForSpec(repospec) + if err != nil { + return true, "", err + } + finalize.Close(repo, "repository for downloading OCI artifact") + artspec = ref.ArtSpec + } else { + log.Debug("evaluating config") + if path != "" { + artspec, err = oci.ParseArt(path) + if err != nil { + return true, "", err + } + } + var us *oci.UniformRepositorySpec + repo, us, prefix, err = h.spec.GetInfo(ctx) + if err != nil { + return true, "", err + } + result.UniformRepositorySpec = *us + } + log.Debug("using artifact spec", "spec", artspec.String()) + if artspec.Digest != nil { + return true, "", fmt.Errorf("digest no possible for target") + } + + if artspec.Repository != "" { + namespace = artspec.Repository + } + if artspec.Reference() != "" { + version = artspec.Reference() + } + + if prefix != "" && namespace != "" { + namespace = prefix + grammar.RepositorySeparator + namespace + } + if version == "" || version == "latest" { + version = racc.Meta().GetVersion() + } + log.Debug("using final target", "namespace", namespace, "version", version) + if namespace == "" { + return true, "", fmt.Errorf("no OCI namespace") + } + + var art oci.ArtifactAccess + + cand := m + if local, ok := aspec.(*localblob.AccessSpec); ok { + if local.GlobalAccess != nil { + s, err := ctx.AccessSpecForSpec(local.GlobalAccess) + if err == nil { + _ = s + // c, err := s.AccessMethod() // TODO: try global access for direct artifact access + // set cand to oci access method + } + } + } + if ocimeth, ok := accspeccpi.GetAccessMethodImplementation(cand).(ociartifact.AccessMethodImpl); ok { + // prepare for optimized point to point implementation + art, _, err = ocimeth.GetArtifact() + if err != nil { + return true, "", errors.Wrapf(err, "cannot access source artifact") + } + finalize.Close(art) + } + + ns, err := repo.LookupNamespace(namespace) + if err != nil { + return true, "", err + } + finalize.Close(ns) + + if art == nil { + log.Debug("using artifact set transfer mode") + set, err := artifactset.OpenFromDataAccess(accessobj.ACC_READONLY, m.MimeType(), m) + if err != nil { + return true, "", errors.Wrapf(err, "opening resource blob as artifact set") + } + finalize.Close(set) + art, err = set.GetArtifact(set.GetMain().String()) + if err != nil { + return true, "", errors.Wrapf(err, "get artifact from blob") + } + finalize.Close(art) + } else { + log.Debug("using direct transfer mode") + } + + p.Printf("uploading resource %s to %s[%s:%s]...\n", racc.Meta().GetName(), repo.GetSpecification().UniformRepositorySpec(), namespace, version) + err = transfer.TransferArtifact(art, ns, oci.AsTags(version)...) + if err != nil { + return true, "", errors.Wrapf(err, "transfer artifact") + } + + result.Repository = namespace + result.Tag = &version + return true, result.String(), nil +} diff --git a/api/ocm/extensions/download/handlers/ocirepo/registration.go b/api/ocm/extensions/download/handlers/ocirepo/registration.go new file mode 100644 index 000000000..c5f8c9b2d --- /dev/null +++ b/api/ocm/extensions/download/handlers/ocirepo/registration.go @@ -0,0 +1,87 @@ +package ocirepo + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + "golang.org/x/exp/slices" + + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/attrs/ociuploadattr" + "ocm.software/ocm/api/ocm/extensions/download" + "ocm.software/ocm/api/utils/listformat" + "ocm.software/ocm/api/utils/registrations" +) + +const PATH = "oci/artifact" + +func init() { + download.RegisterHandlerRegistrationHandler(PATH, &RegistrationHandler{}) +} + +var supportedMimeTypes = []string{ + artifactset.MediaType(artdesc.MediaTypeImageManifest), + artifactset.MediaType(artdesc.MediaTypeImageIndex), +} + +type Config = ociuploadattr.Attribute + +func AttributeDescription() map[string]string { + return ociuploadattr.AttributeDescription() +} + +type RegistrationHandler struct{} + +var _ download.HandlerRegistrationHandler = (*RegistrationHandler)(nil) + +func (r *RegistrationHandler) RegisterByName(handler string, ctx download.Target, config download.HandlerConfig, olist ...download.HandlerOption) (bool, error) { + var err error + + if handler != "" { + return true, fmt.Errorf("invalid ocireg handler %q", handler) + } + + attr, err := registrations.DecodeConfig[Config](config, ociuploadattr.AttributeType{}.Decode) + if err != nil { + return true, errors.Wrapf(err, "cannot unmarshal download handler configuration") + } + + opts := download.NewHandlerOptions(olist...) + if opts.MimeType != "" && !slices.Contains(supportedMimeTypes, opts.MimeType) { + return true, errors.Wrapf(err, "mime type %s not supported", opts.MimeType) + } + + h := New(attr) + if opts.MimeType == "" { + for _, m := range supportedMimeTypes { + opts.MimeType = m + download.For(ctx).Register(h, opts) + } + } else { + download.For(ctx).Register(h, opts) + } + + return true, nil +} + +func (r *RegistrationHandler) GetHandlers(ctx cpi.Context) registrations.HandlerInfos { + return registrations.NewLeafHandlerInfo("uploading an OCI artifact to an OCI registry", ` +The artifact downloader is able to transfer OCI artifact-like resources +into an OCI registry given by the combination of the download target and the +registration config. + +If no config is given, the target must be an OCI reference with a potentially +omitted repository. The repo part is derived from the reference hint provided +by the resource's access specification. + +If the config is given, the target is used as repository name prefixed with an +optional repository prefix given by the configuration. + +The following artifact media types are supported: +`+listformat.FormatList("", supportedMimeTypes...)+` +It accepts a config with the following fields: +`+listformat.FormatMapElements("", AttributeDescription()), + ) +} diff --git a/pkg/contexts/ocm/download/handlers/ocirepo/suite_test.go b/api/ocm/extensions/download/handlers/ocirepo/suite_test.go similarity index 100% rename from pkg/contexts/ocm/download/handlers/ocirepo/suite_test.go rename to api/ocm/extensions/download/handlers/ocirepo/suite_test.go diff --git a/api/ocm/extensions/download/handlers/ocirepo/upload_test.go b/api/ocm/extensions/download/handlers/ocirepo/upload_test.go new file mode 100644 index 000000000..1c6bc835b --- /dev/null +++ b/api/ocm/extensions/download/handlers/ocirepo/upload_test.go @@ -0,0 +1,221 @@ +package ocirepo_test + +import ( + "strings" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + + ociv1 "github.com/opencontainers/image-spec/specs-go/v1" + + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + ctfoci "ocm.software/ocm/api/oci/extensions/repositories/ctf" + "ocm.software/ocm/api/oci/grammar" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/attrs/ociuploadattr" + "ocm.software/ocm/api/ocm/extensions/download" + "ocm.software/ocm/api/ocm/extensions/download/handlers/ocirepo" + ctfocm "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/mime" +) + +const ( + COMP = "github.com/compa" + VERS = "1.0.0" + CTF = "ctf" +) + +const ( + HINT = "ocm.software/test" + UPLOAD = "ocm.software/upload" +) + +const ( + TARGETHOST = "target" + TARGETPATH = "/tmp/target" +) + +const ( + OCIHOST = "source" + OCIPATH = "/tmp/source" + OCINAMESPACE = "ocm/value" + OCIVERSION = "v2.0" +) + +const ARTIFACTSET = "/tmp/set.tgz" + +var _ = Describe("upload", func() { + var env *Builder + + BeforeEach(func() { + env = NewBuilder() + + // fake OCI registry + spec := Must(ctfoci.NewRepositorySpec(accessobj.ACC_WRITABLE, TARGETPATH, env)) + env.OCIContext().SetAlias(TARGETHOST, spec) + + env.OCICommonTransport(TARGETPATH, accessio.FormatDirectory) + }) + + AfterEach(func() { + env.Cleanup() + }) + + Context("local blob", func() { + BeforeEach(func() { + env.ArtifactSet(ARTIFACTSET, accessio.FormatTGZ, func() { + env.Manifest(OCIVERSION, func() { + env.Config(func() { + env.BlobStringData(mime.MIME_JSON, "{}") + }) + env.Layer(func() { + env.BlobStringData(mime.MIME_TEXT, "manifestlayer") + }) + }) + env.Annotation(artifactset.MAINARTIFACT_ANNOTATION, OCIVERSION) + }) + + env.OCMCommonTransport(CTF, accessio.FormatDirectory, func() { + env.ComponentVersion(COMP, VERS, func() { + env.Provider("mandelsoft") + env.Resource("value", "", resourcetypes.OCI_IMAGE, v1.LocalRelation, func() { + env.BlobFromFile(artifactset.MediaType(ociv1.MediaTypeImageManifest), ARTIFACTSET) + env.Hint(HINT) + }) + }) + }) + }) + + It("uploads local oci artifact blob", func() { + download.For(env).Register(ocirepo.New(), download.ForArtifactType(resourcetypes.OCI_IMAGE)) + + src := Must(ctfocm.Open(env, accessobj.ACC_READONLY, CTF, 0, env)) + defer Close(src, "source ctf") + + cv := Must(src.LookupComponentVersion(COMP, VERS)) + defer Close(cv) + + racc := Must(cv.GetResourceByIndex(0)) + + ok, path := Must2(download.For(env).Download(nil, racc, TARGETHOST+".alias"+grammar.RepositorySeparator+UPLOAD, env)) + Expect(ok).To(BeTrue()) + Expect(path).To(Equal("target.alias/ocm.software/upload:1.0.0")) + + env.OCMContext().Finalize() + + target, err := ctfoci.Open(env.OCIContext(), accessobj.ACC_READONLY, TARGETPATH, 0, env) + Expect(err).To(Succeed()) + defer Close(target) + Expect(target.ExistsArtifact(path[strings.Index(path, grammar.RepositorySeparator)+1:strings.Index(path, ":")], VERS)).To(BeTrue()) + }) + + It("uploads local oci artifact blob using named handler", func() { + download.RegisterHandlerByName(env, ocirepo.PATH, nil, download.ForArtifactType(resourcetypes.OCI_IMAGE)) + + src := Must(ctfocm.Open(env.OCMContext(), accessobj.ACC_READONLY, CTF, 0, accessio.PathFileSystem(env))) + defer Close(src, "source ctf") + + cv := Must(src.LookupComponentVersion(COMP, VERS)) + defer Close(cv) + + racc := Must(cv.GetResourceByIndex(0)) + + ok, path := Must2(download.For(env).Download(nil, racc, TARGETHOST+".alias"+grammar.RepositorySeparator+UPLOAD, env)) + Expect(ok).To(BeTrue()) + Expect(path).To(Equal("target.alias/ocm.software/upload:1.0.0")) + + env.OCMContext().Finalize() + + target, err := ctfoci.Open(env.OCIContext(), accessobj.ACC_READONLY, TARGETPATH, 0, env) + Expect(err).To(Succeed()) + defer Close(target) + Expect(target.ExistsArtifact(path[strings.Index(path, grammar.RepositorySeparator)+1:strings.Index(path, ":")], VERS)).To(BeTrue()) + }) + + It("uploads local oci artifact blob using named handler and config", func() { + cfg := ociuploadattr.Attribute{ + Ref: TARGETHOST + ".alias" + grammar.RepositorySeparator + "upload", + } + download.RegisterHandlerByName(env, ocirepo.PATH, cfg, download.ForArtifactType(resourcetypes.OCI_IMAGE)) + + src := Must(ctfocm.Open(env.OCMContext(), accessobj.ACC_READONLY, CTF, 0, accessio.PathFileSystem(env))) + defer Close(src, "source ctf") + + cv := Must(src.LookupComponentVersion(COMP, VERS)) + defer Close(cv) + + racc := Must(cv.GetResourceByIndex(0)) + + ok, path := Must2(download.For(env).Download(nil, racc, "", env)) + Expect(ok).To(BeTrue()) + // Expect(path).To(Equal("CommonTransportFormat::/tmp/target//upload/ocm.software/test:1.0.0")) + Expect(path).To(Equal("target.alias/upload/ocm.software/test:1.0.0")) + + env.OCMContext().Finalize() + + target, err := ctfoci.Open(env.OCIContext(), accessobj.ACC_READONLY, TARGETPATH, 0, env) + Expect(err).To(Succeed()) + defer Close(target) + // Expect(target.ExistsArtifact(path[strings.Index(path, grammar.RepositorySeparator+grammar.RepositorySeparator)+2:strings.LastIndex(path, ":")], VERS)).To(BeTrue()) + Expect(target.ExistsArtifact(path[strings.Index(path, grammar.RepositorySeparator)+1:strings.Index(path, ":")], VERS)).To(BeTrue()) + }) + }) + + Context("oci ref", func() { + BeforeEach(func() { + env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { + env.Namespace(OCINAMESPACE, func() { + env.Manifest(OCIVERSION, func() { + env.Config(func() { + env.BlobStringData(mime.MIME_JSON, "{}") + }) + env.Layer(func() { + env.BlobStringData(mime.MIME_TEXT, "manifestlayer") + }) + }) + }) + }) + + // fake OCI registry + spec := Must(ctfoci.NewRepositorySpec(accessobj.ACC_WRITABLE, OCIPATH, env)) + env.OCIContext().SetAlias(OCIHOST, spec) + + env.OCMCommonTransport(CTF, accessio.FormatDirectory, func() { + env.ComponentVersion(COMP, VERS, func() { + env.Provider("mandelsoft") + env.Resource("value", "", resourcetypes.OCI_IMAGE, v1.LocalRelation, func() { + env.Access(ociartifact.New(OCIHOST + ".alias" + grammar.RepositorySeparator + OCINAMESPACE + grammar.TagSeparator + OCIVERSION)) + }) + }) + }) + }) + + It("uploads oci artifact ref", func() { + download.For(env).Register(ocirepo.New(), download.ForArtifactType(resourcetypes.OCI_IMAGE)) + + src := Must(ctfocm.Open(env.OCMContext(), accessobj.ACC_READONLY, CTF, 0, accessio.PathFileSystem(env))) + defer Close(src, "source ctf") + + cv := Must(src.LookupComponentVersion(COMP, VERS)) + defer Close(cv, "version") + + racc := Must(cv.GetResourceByIndex(0)) + + ok, path := Must2(download.For(env).Download(nil, racc, TARGETHOST+".alias"+grammar.RepositorySeparator+UPLOAD, env)) + Expect(ok).To(BeTrue()) + Expect(path).To(Equal("target.alias/ocm.software/upload:1.0.0")) + + MustBeSuccessful(env.OCMContext().Finalize()) + + target := Must(ctfoci.Open(env.OCIContext(), accessobj.ACC_READONLY, TARGETPATH, 0, env)) + defer Close(target, "download target") + Expect(target.ExistsArtifact(path[strings.Index(path, grammar.RepositorySeparator)+1:strings.Index(path, ":")], VERS)).To(BeTrue()) + }) + }) +}) diff --git a/pkg/contexts/ocm/download/handlers/plugin/download_test.go b/api/ocm/extensions/download/handlers/plugin/download_test.go similarity index 76% rename from pkg/contexts/ocm/download/handlers/plugin/download_test.go rename to api/ocm/extensions/download/handlers/plugin/download_test.go index 79f46a7f2..e2f1dcfe8 100644 --- a/pkg/contexts/ocm/download/handlers/plugin/download_test.go +++ b/api/ocm/extensions/download/handlers/plugin/download_test.go @@ -9,23 +9,23 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/testutils" - . "github.com/open-component-model/ocm/pkg/env/builder" + . "ocm.software/ocm/api/helper/builder" + . "ocm.software/ocm/api/ocm/plugin/testutils" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/common" - "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/ocm" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download/handlers/plugin" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/config" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/plugins" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/out" - "github.com/open-component-model/ocm/pkg/runtime" + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/download" + "ocm.software/ocm/api/ocm/extensions/download/handlers/plugin" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/plugin/config" + "ocm.software/ocm/api/ocm/plugin/plugins" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/api/utils/runtime" ) const PLUGIN = "test" diff --git a/api/ocm/extensions/download/handlers/plugin/handler.go b/api/ocm/extensions/download/handlers/plugin/handler.go new file mode 100644 index 000000000..5d568aa15 --- /dev/null +++ b/api/ocm/extensions/download/handlers/plugin/handler.go @@ -0,0 +1,49 @@ +package plugin + +import ( + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/finalizer" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/download" + "ocm.software/ocm/api/ocm/plugin" + "ocm.software/ocm/api/ocm/plugin/descriptor" + "ocm.software/ocm/api/utils/accessio" + common "ocm.software/ocm/api/utils/misc" +) + +// pluginHandler delegates download format of artifacts to a plugin based handler. +type pluginHandler struct { + plugin plugin.Plugin + name string + config []byte +} + +func New(p plugin.Plugin, name string, config []byte) (download.Handler, error) { + dd := p.GetDownloaderDescriptor(name) + if dd == nil { + return nil, errors.ErrUnknown(descriptor.KIND_DOWNLOADER, name, p.Name()) + } + + return &pluginHandler{ + plugin: p, + name: name, + config: config, + }, nil +} + +func (b *pluginHandler) Download(_ common.Printer, racc cpi.ResourceAccess, path string, _ vfs.FileSystem) (resp bool, eff string, rerr error) { + m, err := racc.AccessMethod() + if err != nil { + return true, "", err + } + var finalize finalizer.Finalizer + defer finalize.FinalizeWithErrorPropagation(&rerr) + + finalize.Close(m, "method for download") + r := accessio.NewOndemandReader(m) + finalize.Close(r, "reader for downlowd download") + + return b.plugin.Download(b.name, r, racc.Meta().Type, m.MimeType(), path, b.config) +} diff --git a/api/ocm/extensions/download/handlers/plugin/registration.go b/api/ocm/extensions/download/handlers/plugin/registration.go new file mode 100644 index 000000000..3493a71f8 --- /dev/null +++ b/api/ocm/extensions/download/handlers/plugin/registration.go @@ -0,0 +1,148 @@ +package plugin + +import ( + "encoding/json" + "fmt" + + "github.com/ghodss/yaml" + "github.com/mandelsoft/goutils/errors" + "github.com/xeipuuv/gojsonschema" + + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/attrs/plugincacheattr" + "ocm.software/ocm/api/ocm/extensions/download" + "ocm.software/ocm/api/ocm/plugin" + "ocm.software/ocm/api/utils/registrations" +) + +type Config = json.RawMessage + +func init() { + download.RegisterHandlerRegistrationHandler("plugin", &RegistrationHandler{}) +} + +type RegistrationHandler struct{} + +var _ download.HandlerRegistrationHandler = (*RegistrationHandler)(nil) + +func (r *RegistrationHandler) RegisterByName(handler string, ctx cpi.Context, config download.HandlerConfig, olist ...download.HandlerOption) (bool, error) { + path := cpi.NewNamePath(handler) + + if config == nil { + return true, fmt.Errorf("target specification required") + } + + if len(path) < 1 || len(path) > 2 { + return true, fmt.Errorf("plugin handler name must be of the form [/]") + } + + opts := download.NewHandlerOptions(olist...) + + name := "" + if len(path) > 1 { + name = path[1] + } + + attr, err := registrations.DecodeAnyConfig(config) + if err != nil { + return true, errors.Wrapf(err, "plugin download handler config for %s/%s", path[0], name) + } + + err = RegisterDownloadHandler(ctx, path[0], name, attr, opts) + return true, err +} + +func RegisterDownloadHandler(ctx cpi.Context, pname, name string, config []byte, olist ...download.HandlerOption) error { + opts := download.NewHandlerOptions(olist...) + set := plugincacheattr.Get(ctx) + if set == nil { + return errors.ErrUnknown(plugin.KIND_PLUGIN, pname) + } + + p := set.Get(pname) + if p == nil { + return errors.ErrUnknown(plugin.KIND_PLUGIN, pname) + } + d := p.LookupDownloader(name, opts.ArtifactType, opts.MimeType) + if len(d) == 0 { + if name == "" { + return fmt.Errorf("no downloader found for [art:%q, media:%q]", opts.ArtifactType, opts.MimeType) + } + return fmt.Errorf("downloader %s not valid for [art:%q, media:%q]", name, opts.ArtifactType, opts.MimeType) + } + for _, e := range d { + if len(config) != 0 { + if e.ConfigScheme == "" { + return errors.Newf("no config accepted by download handler") + } + err := ValidateConfig([]byte(e.ConfigScheme), config) + if err != nil { + return err + } + } + h, err := New(p, e.Name, config) + if err != nil { + return err + } + download.For(ctx).Register(h, opts) + } + return nil +} + +func ValidateConfig(schemadata, configdata []byte) error { + if string(schemadata) == "any" { + var i interface{} + return json.Unmarshal(configdata, &i) + } + data, err := yaml.YAMLToJSON(schemadata) + if err != nil { + return errors.Wrapf(err, "invalid JSON scheme for downloader config") + } + + schema, err := gojsonschema.NewSchema(gojsonschema.NewBytesLoader(configdata)) + if err != nil { + return errors.Wrapf(err, "invalid JSON scheme for downloader config") + } + + loader := gojsonschema.NewBytesLoader(data) + res, err := schema.Validate(loader) + if err != nil { + return err + } + + if !res.Valid() { + errs := res.Errors() + errMsg := errs[0].String() + for i := 1; i < len(errs); i++ { + errMsg = fmt.Sprintf("%s;%s", errMsg, errs[i].String()) + } + return errors.New(errMsg) + } + return nil +} + +func (r *RegistrationHandler) GetHandlers(ctx cpi.Context) registrations.HandlerInfos { + infos := registrations.NewNodeHandlerInfo("downloaders provided by plugins", + "sub namespace of the form <plugin name>/<handler>") + + set := plugincacheattr.Get(ctx) + if set == nil { + return infos + } + + for _, name := range set.PluginNames() { + p := set.Get(name) + if !p.IsValid() { + continue + } + for _, d := range set.Get(name).GetDescriptor().Downloaders { + i := registrations.HandlerInfo{ + Name: name + "/" + d.GetName(), + ShortDesc: "", + Description: d.GetDescription(), + } + infos = append(infos, i) + } + } + return infos +} diff --git a/pkg/contexts/ocm/download/handlers/plugin/suite_test.go b/api/ocm/extensions/download/handlers/plugin/suite_test.go similarity index 100% rename from pkg/contexts/ocm/download/handlers/plugin/suite_test.go rename to api/ocm/extensions/download/handlers/plugin/suite_test.go diff --git a/pkg/contexts/ocm/download/handlers/plugin/testdata/test b/api/ocm/extensions/download/handlers/plugin/testdata/test similarity index 100% rename from pkg/contexts/ocm/download/handlers/plugin/testdata/test rename to api/ocm/extensions/download/handlers/plugin/testdata/test diff --git a/api/ocm/extensions/download/logging.go b/api/ocm/extensions/download/logging.go new file mode 100644 index 000000000..007cad624 --- /dev/null +++ b/api/ocm/extensions/download/logging.go @@ -0,0 +1,13 @@ +package download + +import ( + "github.com/mandelsoft/logging" + + ocmlog "ocm.software/ocm/api/utils/logging" +) + +var REALM = ocmlog.DefineSubRealm("Downloaders", "downloader") + +func Logger(ctx logging.ContextProvider, messageContext ...logging.MessageContext) logging.Logger { + return ctx.LoggingContext().Logger(append([]logging.MessageContext{REALM}, messageContext...)) +} diff --git a/api/ocm/extensions/download/registration.go b/api/ocm/extensions/download/registration.go new file mode 100644 index 000000000..e7f64a08e --- /dev/null +++ b/api/ocm/extensions/download/registration.go @@ -0,0 +1,120 @@ +package download + +import ( + "fmt" + + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils/registrations" +) + +type Target = cpi.Context + +//////////////////////////////////////////////////////////////////////////////// + +type HandlerOptions struct { + HandlerKey `json:",inline"` + Priority int `json:"priority,omitempty"` +} + +func NewHandlerOptions(olist ...HandlerOption) *HandlerOptions { + var opts HandlerOptions + for _, o := range olist { + o.ApplyHandlerOptionTo(&opts) + } + return &opts +} + +func (o *HandlerOptions) ApplyHandlerOptionTo(opts *HandlerOptions) { + if o.Priority > 0 { + opts.Priority = o.Priority + } + o.HandlerKey.ApplyHandlerOptionTo(opts) +} + +type HandlerOption interface { + ApplyHandlerOptionTo(*HandlerOptions) +} + +//////////////////////////////////////////////////////////////////////////////// + +// HandlerKey is the registration key for download handlers. +type HandlerKey struct { + ArtifactType string `json:"artifactType,omitempty"` + MimeType string `json:"mimeType,omitempty"` +} + +var _ HandlerOption = HandlerKey{} + +func NewHandlerKey(artifactType, mimetype string) HandlerKey { + return HandlerKey{ + ArtifactType: artifactType, + MimeType: mimetype, + } +} + +func (k HandlerKey) ApplyHandlerOptionTo(opts *HandlerOptions) { + if k.ArtifactType != "" { + opts.ArtifactType = k.ArtifactType + } + if k.MimeType != "" { + opts.MimeType = k.MimeType + } +} + +func ForCombi(artifacttype string, mimetype string) HandlerOption { + return HandlerKey{ArtifactType: artifacttype, MimeType: mimetype} +} + +func ForMimeType(mimetype string) HandlerOption { + return HandlerKey{MimeType: mimetype} +} + +func ForArtifactType(artifacttype string) HandlerOption { + return HandlerKey{ArtifactType: artifacttype} +} + +type prio struct { + prio int +} + +func WithPrio(p int) HandlerOption { + return prio{p} +} + +func (o prio) ApplyHandlerOptionTo(opts *HandlerOptions) { + opts.Priority = o.prio +} + +//////////////////////////////////////////////////////////////////////////////// + +type ( + HandlerConfig = registrations.HandlerConfig + HandlerRegistrationHandler = registrations.HandlerRegistrationHandler[Target, HandlerOption] + HandlerRegistrationRegistry = registrations.HandlerRegistrationRegistry[Target, HandlerOption] + + RegistrationHandlerInfo = registrations.RegistrationHandlerInfo[Target, HandlerOption] +) + +func NewHandlerRegistrationRegistry(base ...HandlerRegistrationRegistry) HandlerRegistrationRegistry { + return registrations.NewHandlerRegistrationRegistry[Target, HandlerOption](base...) +} + +func NewRegistrationHandlerInfo(path string, handler HandlerRegistrationHandler) *RegistrationHandlerInfo { + return registrations.NewRegistrationHandlerInfo[Target, HandlerOption](path, handler) +} + +func RegisterHandlerRegistrationHandler(path string, handler HandlerRegistrationHandler) { + DefaultRegistry.RegisterRegistrationHandler(path, handler) +} + +func RegisterHandlerByName(ctx cpi.ContextProvider, name string, config HandlerConfig, opts ...HandlerOption) error { + hdlrs := For(ctx) + o, err := hdlrs.RegisterByName(name, ctx.OCMContext(), config, opts...) + if err != nil { + return err + } + if !o { + return fmt.Errorf("no matching handler found for %q", name) + } + return nil +} diff --git a/api/ocm/extensions/download/registry.go b/api/ocm/extensions/download/registry.go new file mode 100644 index 000000000..88264c6d5 --- /dev/null +++ b/api/ocm/extensions/download/registry.go @@ -0,0 +1,190 @@ +package download + +import ( + "sort" + "sync" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/general" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/ocmutils/registry" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/registrations" + "ocm.software/ocm/api/utils/runtimefinalizer" +) + +const ALL = "*" + +type Handler interface { + Download(p common.Printer, racc cpi.ResourceAccess, path string, fs vfs.FileSystem) (bool, string, error) +} + +const DEFAULT_BLOBHANDLER_PRIO = 100 + +type PrioHandler struct { + Handler + Prio int +} + +// MultiHandler is a Handler consisting of a sequence of handlers. +type MultiHandler []Handler + +var _ sort.Interface = MultiHandler(nil) + +func (m MultiHandler) Download(p common.Printer, racc cpi.ResourceAccess, path string, fs vfs.FileSystem) (bool, string, error) { + errs := errors.ErrListf("download") + for _, h := range m { + ok, p, err := h.Download(p, racc, path, fs) + if ok { + return ok, p, err + } + errs.Add(err) + } + return false, "", errs.Result() +} + +func (m MultiHandler) Len() int { + return len(m) +} + +func (m MultiHandler) Less(i, j int) bool { + pi := DEFAULT_BLOBHANDLER_PRIO + pj := DEFAULT_BLOBHANDLER_PRIO + + if p, ok := m[i].(*PrioHandler); ok { + pi = p.Prio + } + if p, ok := m[j].(*PrioHandler); ok { + pj = p.Prio + } + return pi > pj +} + +func (m MultiHandler) Swap(i, j int) { + m[i], m[j] = m[j], m[i] +} + +type Registry interface { + Copy() Registry + AsHandlerRegistrationRegistry() registrations.HandlerRegistrationRegistry[Target, HandlerOption] + + registrations.HandlerRegistrationRegistryAccess[Target, HandlerOption] + + Register(hdlr Handler, olist ...HandlerOption) + LookupHandler(art, media string) MultiHandler + Handler + DownloadAsBlob(p common.Printer, racc cpi.ResourceAccess, path string, fs vfs.FileSystem) (bool, string, error) +} + +func AsHandlerRegistrationRegistry(r Registry) registrations.HandlerRegistrationRegistry[Target, HandlerOption] { + if r == nil { + return nil + } + return r.AsHandlerRegistrationRegistry() +} + +type _registry struct { + registrations.HandlerRegistrationRegistry[Target, HandlerOption] + + id runtimefinalizer.ObjectIdentity + lock sync.RWMutex + base Registry + handlers *registry.Registry[Handler, registry.RegistrationKey] +} + +func NewRegistry(base ...Registry) Registry { + b := general.Optional(base...) + return &_registry{ + id: runtimefinalizer.NewObjectIdentity("downloader.registry.ocm.software"), + base: b, + HandlerRegistrationRegistry: NewHandlerRegistrationRegistry(AsHandlerRegistrationRegistry(b)), + handlers: registry.NewRegistry[Handler, registry.RegistrationKey](), + } +} + +func (r *_registry) AsHandlerRegistrationRegistry() registrations.HandlerRegistrationRegistry[Target, HandlerOption] { + return r.HandlerRegistrationRegistry +} + +func (r *_registry) Copy() Registry { + n := NewRegistry(r.base).(*_registry) + n.handlers = r.handlers.Copy() + return n +} + +func (r *_registry) LookupHandler(art, media string) MultiHandler { + r.lock.RLock() + defer r.lock.RUnlock() + + return r.getHandlers(art, media) +} + +func (r *_registry) Register(hdlr Handler, olist ...HandlerOption) { + opts := NewHandlerOptions(olist...) + r.lock.Lock() + defer r.lock.Unlock() + if opts.Priority != 0 { + hdlr = &PrioHandler{hdlr, opts.Priority} + } + r.handlers.Register(registry.RegistrationKey{opts.ArtifactType, opts.MimeType}, hdlr) +} + +func (r *_registry) getHandlers(arttype, mediatype string) MultiHandler { + list := r.handlers.LookupHandler(registry.RegistrationKey{arttype, mediatype}) + if r.base != nil { + list = append(list, r.base.LookupHandler(arttype, mediatype)...) + } + return list +} + +func (r *_registry) Download(p common.Printer, racc cpi.ResourceAccess, path string, fs vfs.FileSystem) (bool, string, error) { + p = common.AssurePrinter(p) + art := racc.Meta().GetType() + m, err := racc.AccessMethod() + if err != nil { + return false, "", err + } + defer m.Close() + mime := m.MimeType() + if ok, p, err := r.download(r.LookupHandler(art, mime), p, racc, path, fs); ok { + return ok, p, err + } + return r.download(r.LookupHandler(ALL, ""), p, racc, path, fs) +} + +func (r *_registry) DownloadAsBlob(p common.Printer, racc cpi.ResourceAccess, path string, fs vfs.FileSystem) (bool, string, error) { + return r.download(r.LookupHandler(ALL, ""), p, racc, path, fs) +} + +func (r *_registry) download(list MultiHandler, p common.Printer, racc cpi.ResourceAccess, path string, fs vfs.FileSystem) (bool, string, error) { + sort.Stable(list) + return list.Download(p, racc, path, fs) +} + +var DefaultRegistry = NewRegistry() + +func Register(hdlr Handler, olist ...HandlerOption) { + DefaultRegistry.Register(hdlr, olist...) +} + +//////////////////////////////////////////////////////////////////////////////// + +const ATTR_DOWNLOADER_HANDLERS = "ocm.software/ocm/api/ocm/extensions/download" + +func For(ctx cpi.ContextProvider) Registry { + if ctx == nil { + return DefaultRegistry + } + return ctx.OCMContext().GetAttributes().GetOrCreateAttribute(ATTR_DOWNLOADER_HANDLERS, create).(Registry) +} + +func create(datacontext.Context) interface{} { + return NewRegistry(DefaultRegistry) +} + +func SetFor(ctx datacontext.Context, registry Registry) { + ctx.GetAttributes().SetAttribute(ATTR_DOWNLOADER_HANDLERS, registry) +} diff --git a/api/ocm/extensions/download/setup.go b/api/ocm/extensions/download/setup.go new file mode 100644 index 000000000..122f87ea1 --- /dev/null +++ b/api/ocm/extensions/download/setup.go @@ -0,0 +1,27 @@ +package download + +import ( + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/ocm/cpi" +) + +func init() { + datacontext.RegisterSetupHandler(datacontext.SetupHandlerFunction(setupContext)) +} + +func setupContext(mode datacontext.BuilderMode, ctx datacontext.Context) { + if octx, ok := ctx.(cpi.Context); ok { + switch mode { + case datacontext.MODE_SHARED: + fallthrough + case datacontext.MODE_DEFAULTED: + // do nothing, fallback to the default attribute lookup + case datacontext.MODE_EXTENDED: + SetFor(octx, NewRegistry(DefaultRegistry)) + case datacontext.MODE_CONFIGURED: + SetFor(octx, DefaultRegistry.Copy()) + case datacontext.MODE_INITIAL: + SetFor(octx, NewRegistry()) + } + } +} diff --git a/api/ocm/extensions/labels/init.go b/api/ocm/extensions/labels/init.go new file mode 100644 index 000000000..26d99bbc1 --- /dev/null +++ b/api/ocm/extensions/labels/init.go @@ -0,0 +1,5 @@ +package labels + +import ( + _ "ocm.software/ocm/api/ocm/extensions/labels/routingslip/types" +) diff --git a/api/ocm/extensions/labels/routingslip/entry.go b/api/ocm/extensions/labels/routingslip/entry.go new file mode 100644 index 000000000..69cf1d05a --- /dev/null +++ b/api/ocm/extensions/labels/routingslip/entry.go @@ -0,0 +1,93 @@ +package routingslip + +import ( + "fmt" + "strings" + + "github.com/mandelsoft/goutils/errors" + "github.com/opencontainers/go-digest" + + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/labels/routingslip/internal" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/norm/jcs" + "ocm.software/ocm/api/utils/runtime" +) + +func AsGenericEntry(u *runtime.UnstructuredTypedObject) *GenericEntry { + return internal.AsGenericEntry(u) +} + +func ToGenericEntry(e Entry) (*GenericEntry, error) { + return internal.ToGenericEntry(e) +} + +func NewGenericEntryWith(typ string, attrs ...interface{}) (*GenericEntry, error) { + r := map[string]interface{}{} + i := 0 + for len(attrs) > i { + n, ok := attrs[i].(string) + if !ok { + return nil, errors.ErrInvalid("key type", fmt.Sprintf("%T", attrs[i])) + } + r[n] = attrs[i+1] + i += 2 + } + return NewGenericEntry(typ, r) +} + +func NewGenericEntry(typ string, data interface{}) (*GenericEntry, error) { + u, err := runtime.ToUnstructuredTypedObject(data) + if err != nil { + return nil, err + } + if typ != "" { + u.SetType(typ) + } + return AsGenericEntry(u), nil +} + +var excludes = signing.MapExcludes{ + "digest": nil, + "signature": nil, +} + +type HistoryEntries = []HistoryEntry + +type HistoryEntry struct { + Payload *GenericEntry `json:"payload"` + Timestamp metav1.Timestamp `json:"timestamp"` + Parent *digest.Digest `json:"parent,omitempty"` + Links []Link `json:"links,omitempty"` + Digest digest.Digest `json:"digest"` + Signature *metav1.SignatureSpec `json:"signature,omitempty"` +} + +func (e *HistoryEntry) Normalize() ([]byte, error) { + return signing.Normalize(jcs.New(), e, excludes) +} + +func (e *HistoryEntry) CalculateDigest() (digest.Digest, error) { + data, err := e.Normalize() + if err != nil { + return "", err + } + return digest.SHA256.FromBytes(data), nil +} + +type Link struct { + Name string `json:"name"` + Digest digest.Digest `json:"digest"` +} + +func (l Link) Compare(o Link) int { + r := strings.Compare(l.Name, o.Name) + if r == 0 { + r = strings.Compare(l.Digest.String(), o.Digest.String()) + } + return r +} + +func CreateEntry(t runtime.VersionedTypedObject) (Entry, error) { + return internal.CreateEntry(t) +} diff --git a/pkg/contexts/ocm/labels/routingslip/entrytypes_test.go b/api/ocm/extensions/labels/routingslip/entrytypes_test.go similarity index 78% rename from pkg/contexts/ocm/labels/routingslip/entrytypes_test.go rename to api/ocm/extensions/labels/routingslip/entrytypes_test.go index fb5cd0821..0cd591b33 100644 --- a/pkg/contexts/ocm/labels/routingslip/entrytypes_test.go +++ b/api/ocm/extensions/labels/routingslip/entrytypes_test.go @@ -8,12 +8,12 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip" - "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip/internal" - "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip/types/comment" - "github.com/open-component-model/ocm/pkg/runtime" + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/labels/routingslip" + "ocm.software/ocm/api/ocm/extensions/labels/routingslip/internal" + "ocm.software/ocm/api/ocm/extensions/labels/routingslip/types/comment" + "ocm.software/ocm/api/utils/runtime" ) const TYPE = "my" diff --git a/api/ocm/extensions/labels/routingslip/init.go b/api/ocm/extensions/labels/routingslip/init.go new file mode 100644 index 000000000..4dd3cba4f --- /dev/null +++ b/api/ocm/extensions/labels/routingslip/init.go @@ -0,0 +1,5 @@ +package routingslip + +import ( + _ "ocm.software/ocm/api/ocm/extensions/labels/routingslip/types" +) diff --git a/api/ocm/extensions/labels/routingslip/interface.go b/api/ocm/extensions/labels/routingslip/interface.go new file mode 100644 index 000000000..dc4f8b0bd --- /dev/null +++ b/api/ocm/extensions/labels/routingslip/interface.go @@ -0,0 +1,26 @@ +package routingslip + +import ( + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/labels/routingslip/internal" +) + +type ( + Context = internal.Context + ContextProvider = ocm.ContextProvider + EntryTypeScheme = internal.EntryTypeScheme + Entry = internal.Entry + GenericEntry = internal.GenericEntry +) + +type SlipAccess interface { + Get(name string) (*RoutingSlip, error) +} + +func DefaultEntryTypeScheme() EntryTypeScheme { + return internal.DefaultEntryTypeScheme() +} + +func For(ctx ContextProvider) EntryTypeScheme { + return internal.For(ctx) +} diff --git a/api/ocm/extensions/labels/routingslip/internal/attr.go b/api/ocm/extensions/labels/routingslip/internal/attr.go new file mode 100644 index 000000000..de61f894c --- /dev/null +++ b/api/ocm/extensions/labels/routingslip/internal/attr.go @@ -0,0 +1,25 @@ +package internal + +import ( + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/ocm/cpi" +) + +//////////////////////////////////////////////////////////////////////////////// + +const ATTR_ROUTINGSLIP_ENTRYTYPES = "ocm.software/ocm/api/ocm/extensions/labels/routingslip" + +func For(ctx cpi.ContextProvider) EntryTypeScheme { + if ctx == nil { + return DefaultEntryTypeScheme() + } + return ctx.OCMContext().GetAttributes().GetOrCreateAttribute(ATTR_ROUTINGSLIP_ENTRYTYPES, create).(EntryTypeScheme) +} + +func create(datacontext.Context) interface{} { + return NewEntryTypeScheme(DefaultEntryTypeScheme()) +} + +func SetFor(ctx datacontext.Context, registry EntryTypeScheme) { + ctx.GetAttributes().SetAttribute(ATTR_ROUTINGSLIP_ENTRYTYPES, registry) +} diff --git a/pkg/contexts/ocm/labels/routingslip/internal/entrytypes.go b/api/ocm/extensions/labels/routingslip/internal/entrytypes.go similarity index 94% rename from pkg/contexts/ocm/labels/routingslip/internal/entrytypes.go rename to api/ocm/extensions/labels/routingslip/internal/entrytypes.go index 319d2bc61..dbb7f4205 100644 --- a/pkg/contexts/ocm/labels/routingslip/internal/entrytypes.go +++ b/api/ocm/extensions/labels/routingslip/internal/entrytypes.go @@ -12,11 +12,11 @@ import ( "github.com/mandelsoft/goutils/sliceutils" "github.com/modern-go/reflect2" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets/flagsetscheme" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/api/utils/cobrautils/flagsets/flagsetscheme" + "ocm.software/ocm/api/utils/runtime" ) type Context = cpi.Context diff --git a/api/ocm/extensions/labels/routingslip/label.go b/api/ocm/extensions/labels/routingslip/label.go new file mode 100644 index 000000000..0bce14944 --- /dev/null +++ b/api/ocm/extensions/labels/routingslip/label.go @@ -0,0 +1,109 @@ +package routingslip + +import ( + "sort" + + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/valuemergehandler/handlers/maplistmerge" + "ocm.software/ocm/api/ocm/valuemergehandler/handlers/simplemapmerge" + "ocm.software/ocm/api/ocm/valuemergehandler/hpi" + "ocm.software/ocm/api/utils" +) + +const NAME = "routing-slips" + +type LabelValue map[string]HistoryEntries + +var spec = utils.Must(hpi.NewSpecification( + simplemapmerge.ALGORITHM, + simplemapmerge.NewConfig( + "", + utils.Must(hpi.NewSpecification( + maplistmerge.ALGORITHM, + maplistmerge.NewConfig("digest", maplistmerge.MODE_INBOUND), + )), + )), +) + +func init() { + hpi.Assign(hpi.LabelHint(NAME), spec) +} + +func (l LabelValue) Has(name string) bool { + return l[name] != nil +} + +func (l LabelValue) Get(name string) (*RoutingSlip, error) { + return NewRoutingSlip(name, l) +} + +func (l LabelValue) Query(name string) (*RoutingSlip, error) { + a := l[name] + if a == nil { + return nil, nil + } + return l.Get(name) +} + +func (l LabelValue) Leaves() []Link { + var links []Link + + for k := range l { + s, err := l.Get(k) + if err == nil { + for _, d := range s.Leaves() { + links = append(links, Link{ + Name: k, + Digest: d, + }) + } + } + } + sort.Slice(links, func(i, j int) bool { return links[i].Compare(links[j]) < 0 }) + return links +} + +func (l LabelValue) Set(slip *RoutingSlip) { + l[slip.name] = slip.entries +} + +func AddEntry(cv cpi.ComponentVersionAccess, name string, algo string, e Entry, links []Link, parent ...digest.Digest) (*HistoryEntry, error) { + var label LabelValue + _, err := cv.GetDescriptor().Labels.GetValue(NAME, &label) + if err != nil { + return nil, err + } + if label == nil { + label = LabelValue{} + } + slip, err := label.Get(name) + if err != nil { + return nil, err + } + entry, err := slip.Add(cv.GetContext(), name, algo, e, links, parent...) + if err != nil { + return nil, err + } + label.Set(slip) + + err = Set(cv, label) + if err != nil { + return nil, err + } + return entry, nil +} + +func Get(cv cpi.ComponentVersionAccess) (LabelValue, error) { + var label LabelValue + _, err := cv.GetDescriptor().Labels.GetValue(NAME, &label) + if err != nil { + return nil, err + } + return label, nil +} + +func Set(cv cpi.ComponentVersionAccess, label LabelValue) error { + return cv.GetDescriptor().Labels.SetValue(NAME, label) +} diff --git a/pkg/contexts/ocm/labels/routingslip/slip.go b/api/ocm/extensions/labels/routingslip/slip.go similarity index 94% rename from pkg/contexts/ocm/labels/routingslip/slip.go rename to api/ocm/extensions/labels/routingslip/slip.go index cceb6a24b..c3ea45d7f 100644 --- a/pkg/contexts/ocm/labels/routingslip/slip.go +++ b/api/ocm/extensions/labels/routingslip/slip.go @@ -9,13 +9,13 @@ import ( "github.com/opencontainers/go-digest" "golang.org/x/exp/slices" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" - "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/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/hasher/sha256" - "github.com/open-component-model/ocm/pkg/signing/signutils" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/hasher/sha256" + "ocm.software/ocm/api/tech/signing/signutils" ) const ( diff --git a/pkg/contexts/ocm/labels/routingslip/slip_test.go b/api/ocm/extensions/labels/routingslip/slip_test.go similarity index 86% rename from pkg/contexts/ocm/labels/routingslip/slip_test.go rename to api/ocm/extensions/labels/routingslip/slip_test.go index c9ceb1882..11f9dd2f4 100644 --- a/pkg/contexts/ocm/labels/routingslip/slip_test.go +++ b/api/ocm/extensions/labels/routingslip/slip_test.go @@ -11,11 +11,11 @@ import ( "github.com/opencontainers/go-digest" "sigs.k8s.io/yaml" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip" - "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip/types/comment" - "github.com/open-component-model/ocm/pkg/env/builder" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" + "ocm.software/ocm/api/helper/builder" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/labels/routingslip" + "ocm.software/ocm/api/ocm/extensions/labels/routingslip/types/comment" + "ocm.software/ocm/api/tech/signing/handlers/rsa" ) const ( diff --git a/api/ocm/extensions/labels/routingslip/spi/entrytype_options.go b/api/ocm/extensions/labels/routingslip/spi/entrytype_options.go new file mode 100644 index 000000000..6edbbc088 --- /dev/null +++ b/api/ocm/extensions/labels/routingslip/spi/entrytype_options.go @@ -0,0 +1,20 @@ +package spi + +import ( + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/api/utils/cobrautils/flagsets/flagsetscheme" +) + +type EntryTypeOption = flagsetscheme.TypeOption + +func WithFormatSpec(value string) EntryTypeOption { + return flagsetscheme.WithFormatSpec(value) +} + +func WithDescription(value string) EntryTypeOption { + return flagsetscheme.WithDescription(value) +} + +func WithConfigHandler(value flagsets.ConfigOptionTypeSetHandler) EntryTypeOption { + return flagsetscheme.WithConfigHandler(value) +} diff --git a/api/ocm/extensions/labels/routingslip/spi/interface.go b/api/ocm/extensions/labels/routingslip/spi/interface.go new file mode 100644 index 000000000..e3393e46e --- /dev/null +++ b/api/ocm/extensions/labels/routingslip/spi/interface.go @@ -0,0 +1,28 @@ +package spi + +import ( + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/labels/routingslip/internal" + "ocm.software/ocm/api/utils/runtime" +) + +type ( + Context = cpi.Context + Entry = internal.Entry + UnknownEntry = internal.UnknownEntry + GenericEntry = internal.GenericEntry + EntryType = internal.EntryType + EntryTypeScheme = internal.EntryTypeScheme +) + +func NewStrictEntryTypeScheme() runtime.VersionedTypeRegistry[Entry, EntryType] { + return internal.NewStrictEntryTypeScheme() +} + +func DefaultEntryTypeScheme() EntryTypeScheme { + return internal.DefaultEntryTypeScheme() +} + +func For(ctx cpi.ContextProvider) EntryTypeScheme { + return internal.For(ctx) +} diff --git a/api/ocm/extensions/labels/routingslip/spi/support.go b/api/ocm/extensions/labels/routingslip/spi/support.go new file mode 100644 index 000000000..b0e50ca24 --- /dev/null +++ b/api/ocm/extensions/labels/routingslip/spi/support.go @@ -0,0 +1,48 @@ +package spi + +import ( + "ocm.software/ocm/api/utils/cobrautils/flagsets/flagsetscheme" + "ocm.software/ocm/api/utils/runtime" +) + +type EntryTypeVersionScheme = runtime.TypeVersionScheme[Entry, EntryType] + +func NewEntryTypeVersionScheme(kind string) EntryTypeVersionScheme { + return runtime.NewTypeVersionScheme[Entry, EntryType](kind, NewStrictEntryTypeScheme()) +} + +//////////////////////////////////////////////////////////////////////////////// + +type EntryFormatVersionRegistry = runtime.FormatVersionRegistry[Entry] + +func NewEntryFormatVersionRegistry() EntryFormatVersionRegistry { + return runtime.NewFormatVersionRegistry[Entry]() +} + +func MustNewEntryMultiFormatVersion(kind string, formats EntryFormatVersionRegistry) runtime.FormatVersion[Entry] { + return runtime.MustNewMultiFormatVersion[Entry](kind, formats) +} + +//////////////////////////////////////////////////////////////////////////////// + +func NewEntryType[I Entry](name string, opts ...EntryTypeOption) EntryType { + return flagsetscheme.NewTypedObjectTypeObject[Entry](runtime.NewVersionedTypedObjectType[Entry, I](name), opts...) +} + +func NewEntryTypeByConverter[I Entry, V runtime.VersionedTypedObject](name string, converter runtime.Converter[I, V], opts ...EntryTypeOption) EntryType { + return flagsetscheme.NewTypedObjectTypeObject[Entry](runtime.NewVersionedTypedObjectTypeByConverter[Entry, I, V](name, converter), opts...) +} + +func NewEntryTypeByFormatVersion(name string, fmt runtime.FormatVersion[Entry], opts ...EntryTypeOption) EntryType { + return flagsetscheme.NewTypedObjectTypeObject[Entry](runtime.NewVersionedTypedObjectTypeByFormatVersion[Entry](name, fmt), opts...) +} + +//////////////////////////////////////////////////////////////////////////////// + +func Register(atype EntryType) { + DefaultEntryTypeScheme().Register(atype) +} + +func RegisterEntryTypeVersions(s EntryTypeVersionScheme) { + DefaultEntryTypeScheme().AddKnownTypes(s) +} diff --git a/pkg/contexts/ocm/labels/routingslip/suite_test.go b/api/ocm/extensions/labels/routingslip/suite_test.go similarity index 100% rename from pkg/contexts/ocm/labels/routingslip/suite_test.go rename to api/ocm/extensions/labels/routingslip/suite_test.go diff --git a/api/ocm/extensions/labels/routingslip/transfer_test.go b/api/ocm/extensions/labels/routingslip/transfer_test.go new file mode 100644 index 000000000..113efc458 --- /dev/null +++ b/api/ocm/extensions/labels/routingslip/transfer_test.go @@ -0,0 +1,108 @@ +package routingslip_test + +import ( + "fmt" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/mandelsoft/goutils/finalizer" + + "ocm.software/ocm/api/helper/builder" + "ocm.software/ocm/api/ocm/extensions/attrs/compositionmodeattr" + "ocm.software/ocm/api/ocm/extensions/labels/routingslip" + "ocm.software/ocm/api/ocm/extensions/labels/routingslip/types/comment" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/tools/transfer" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/api/utils/accessobj" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ARCH = "/tmp/ctf" + TARGET = "/tmp/target" + COMPONENT = "acme.org/routingslip" + VERSION = "1.0.0" + LOCAL = "local.org" +) + +var _ = Describe("management", func() { + var env *builder.Builder + + BeforeEach(func() { + env = builder.NewBuilder() + env.RSAKeyPair(ORG, LOCAL) + }) + + AfterEach(func() { + env.Cleanup() + }) + + DescribeTable("transfers and updates", func(mode bool) { + var finalize finalizer.Finalizer + + defer Defer(finalize.Finalize, "finalizer") + + compositionmodeattr.Set(env.OCMContext(), mode) + e1 := comment.New("start of routing slip") + e2 := comment.New("additional entry") + + repo := Must(ctf.Open(env, accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, ARCH, 0o700, env)) + finalize.Close(repo, "repo") + + c := Must(repo.LookupComponent(COMPONENT)) + finalize.Close(c, "comp") + cv := Must(c.NewVersion(VERSION)) + finalize.Close(cv, "vers") + cv.GetDescriptor().Provider.Name = ORG + MustBeSuccessful(routingslip.AddEntry(cv, ORG, rsa.Algorithm, e1, nil)) + MustBeSuccessful(c.AddVersion(cv)) + + target := Must(ctf.Open(env, accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, TARGET, 0o700, env)) + finalize.Close(target, "target") + pr, buf := common.NewBufferedPrinter() + + MustBeSuccessful(transfer.TransferVersion(pr, nil, cv, target, Must(standard.New()))) + + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +transferring version "acme.org/routingslip:1.0.0"... +...adding component version... +`)) + nested := finalize.Nested() + tc := Must(target.LookupComponent(COMPONENT)) + nested.Close(tc, "target comp") + tcv := Must(tc.LookupVersion(VERSION)) + nested.Close(tcv) + + slip := Must(routingslip.GetSlip(tcv, ORG)) + MustBeSuccessful(routingslip.AddEntry(tcv, LOCAL, rsa.Algorithm, e1, nil)) + Expect(slip.Len()).To(Equal(1)) + + MustBeSuccessful(tc.AddVersion(tcv)) + MustBeSuccessful(nested.Finalize()) + + buf.Reset() + MustBeSuccessful(routingslip.AddEntry(cv, ORG, rsa.Algorithm, e2, nil)) + MustBeSuccessful(transfer.TransferVersion(pr, nil, cv, target, Must(standard.New()))) + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +transferring version "acme.org/routingslip:1.0.0"... + updating volatile properties of "acme.org/routingslip:1.0.0" +...adding component version... +`)) + + tcv = Must(target.LookupComponentVersion(COMPONENT, VERSION)) + finalize.Close(tcv, "target") + label := Must(routingslip.Get(tcv)) + Expect(len(label)).To(Equal(2)) + Expect(len(label[ORG])).To(Equal(2)) + Expect(len(label[LOCAL])).To(Equal(1)) + fmt.Printf("*** routing slips:\n%s\n", Must(runtime.DefaultYAMLEncoding.Marshal(label))) + }, + Entry("with direct mode", false), + Entry("with composition mode", true), + ) +}) diff --git a/api/ocm/extensions/labels/routingslip/types/comment/cli.go b/api/ocm/extensions/labels/routingslip/types/comment/cli.go new file mode 100644 index 000000000..dd370e8cf --- /dev/null +++ b/api/ocm/extensions/labels/routingslip/types/comment/cli.go @@ -0,0 +1,30 @@ +package comment + +import ( + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + "ocm.software/ocm/api/utils/cobrautils/flagsets" +) + +func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { + return flagsets.NewConfigOptionTypeSetHandler( + Type, AddConfig, + options.CommentOption, + ) +} + +func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { + flagsets.AddFieldByOptionP(opts, options.CommentOption, config, "comment") + return nil +} + +var usage = ` +An unstructured comment as entry in a routing slip. +` + +var formatV1 = ` +The type specific specification fields are: + +- **comment** *string* + + Any text as entry in a routing slip. +` diff --git a/api/ocm/extensions/labels/routingslip/types/comment/entry.go b/api/ocm/extensions/labels/routingslip/types/comment/entry.go new file mode 100644 index 000000000..f59b7b27e --- /dev/null +++ b/api/ocm/extensions/labels/routingslip/types/comment/entry.go @@ -0,0 +1,45 @@ +package comment + +import ( + "fmt" + + "ocm.software/ocm/api/ocm/extensions/labels/routingslip/spi" + "ocm.software/ocm/api/utils/runtime" +) + +// Type is the access type for a blob in an OCI repository. +const ( + Type = "comment" + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +func init() { + spi.Register(spi.NewEntryType[*Entry](Type, spi.WithDescription(usage))) + spi.Register(spi.NewEntryType[*Entry](TypeV1, spi.WithFormatSpec(formatV1), spi.WithConfigHandler(ConfigHandler()))) +} + +// New creates a new Helm Chart accessor for helm repositories. +func New(comment string) *Entry { + return &Entry{ + ObjectVersionedType: runtime.NewVersionedTypedObject(Type), + Comment: comment, + } +} + +// Entry describes the access for a helm repository. +type Entry struct { + runtime.ObjectVersionedType `json:",inline"` + + // Comment is just a descriptive text in a routing slip- + Comment string `json:"comment"` +} + +var _ spi.Entry = (*Entry)(nil) + +func (a *Entry) Describe(ctx spi.Context) string { + return fmt.Sprintf("Comment: %s", a.Comment) +} + +func (a *Entry) Validate(spi.Context) error { + return nil +} diff --git a/api/ocm/extensions/labels/routingslip/types/init.go b/api/ocm/extensions/labels/routingslip/types/init.go new file mode 100644 index 000000000..b48624464 --- /dev/null +++ b/api/ocm/extensions/labels/routingslip/types/init.go @@ -0,0 +1,5 @@ +package types + +import ( + _ "ocm.software/ocm/api/ocm/extensions/labels/routingslip/types/comment" +) diff --git a/api/ocm/extensions/labels/routingslip/types/plugin/cmd_test.go b/api/ocm/extensions/labels/routingslip/types/plugin/cmd_test.go new file mode 100644 index 000000000..0c691ca9b --- /dev/null +++ b/api/ocm/extensions/labels/routingslip/types/plugin/cmd_test.go @@ -0,0 +1,74 @@ +package plugin_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/env" + . "ocm.software/ocm/api/ocm/plugin/testutils" + + "github.com/mandelsoft/goutils/sliceutils" + "github.com/mandelsoft/goutils/transformer" + "github.com/spf13/pflag" + + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + "ocm.software/ocm/api/ocm/extensions/attrs/plugincacheattr" + "ocm.software/ocm/api/ocm/extensions/labels/routingslip" + "ocm.software/ocm/api/ocm/plugin/registration" + "ocm.software/ocm/api/utils/cobrautils/flagsets" +) + +const ( + ARCH = "/tmp/ca" + VERSION = "v1" + COMP = "test.de/x" + PROVIDER = "acme.org" +) + +var _ = Describe("Test Environment", func() { + var env *Environment + var plugins TempPluginDir + + BeforeEach(func() { + env = NewEnvironment(TestData()) + + ctx := env.OCMContext() + plugins = Must(ConfigureTestPlugins(env, "testdata")) + registry := plugincacheattr.Get(ctx) + Expect(registration.RegisterExtensions(ctx)).To(Succeed()) + p := registry.Get("test") + Expect(p).NotTo(BeNil()) + }) + + AfterEach(func() { + plugins.Cleanup() + env.Cleanup() + }) + + It("handles plugin based entry type", func() { + prov := routingslip.For(env.OCMContext()).CreateConfigTypeSetConfigProvider() + configopts := prov.CreateOptions() + Expect(sliceutils.Transform(configopts.Options(), transformer.GetName[flagsets.Option, string])).To(ConsistOf( + "entry", "comment", // default settings + "mediaType", "accessPath", // by plugin + )) + + fs := &pflag.FlagSet{} + fs.SortFlags = true + configopts.AddFlags(fs) + Expect("\n" + fs.FlagUsages()).To(Equal(` + --accessPath string file path + --comment string comment field value + --entry YAML routing slip entry specification (YAML) + --mediaType string media type for artifact blob representation +`)) + MustBeSuccessful(fs.Parse([]string{"--accessPath", "some path", "--" + options.MediatypeOption.GetName(), "media type"})) + prov.SetTypeName("test") + data := Must(prov.GetConfigFor(configopts)) + Expect(data).To(YAMLEqual(` +type: test +mediaType: media type +path: some path +`)) + }) +}) diff --git a/pkg/contexts/ocm/labels/routingslip/types/plugin/doc.go b/api/ocm/extensions/labels/routingslip/types/plugin/doc.go similarity index 100% rename from pkg/contexts/ocm/labels/routingslip/types/plugin/doc.go rename to api/ocm/extensions/labels/routingslip/types/plugin/doc.go diff --git a/api/ocm/extensions/labels/routingslip/types/plugin/entry.go b/api/ocm/extensions/labels/routingslip/types/plugin/entry.go new file mode 100644 index 000000000..dc10584da --- /dev/null +++ b/api/ocm/extensions/labels/routingslip/types/plugin/entry.go @@ -0,0 +1,27 @@ +package plugin + +import ( + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/labels/routingslip/spi" + "ocm.software/ocm/api/utils/runtime" +) + +type Entry struct { + runtime.UnstructuredVersionedTypedObject `json:",inline"` + handler *PluginHandler +} + +var _ spi.Entry = &Entry{} + +func (s *Entry) Describe(ctx cpi.Context) string { + return s.handler.Describe(s, ctx) +} + +func (s *Entry) Validate(ctx spi.Context) error { + _, err := s.handler.Validate(s) + return err +} + +func (s *Entry) Handler() *PluginHandler { + return s.handler +} diff --git a/pkg/contexts/ocm/labels/routingslip/types/plugin/entry_test.go b/api/ocm/extensions/labels/routingslip/types/plugin/entry_test.go similarity index 75% rename from pkg/contexts/ocm/labels/routingslip/types/plugin/entry_test.go rename to api/ocm/extensions/labels/routingslip/types/plugin/entry_test.go index 9f98c9443..4a3b3f460 100644 --- a/pkg/contexts/ocm/labels/routingslip/types/plugin/entry_test.go +++ b/api/ocm/extensions/labels/routingslip/types/plugin/entry_test.go @@ -6,16 +6,16 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/testutils" - . "github.com/open-component-model/ocm/pkg/env" + . "ocm.software/ocm/api/helper/env" + . "ocm.software/ocm/api/ocm/plugin/testutils" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip/spi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/plugins" - "github.com/open-component-model/ocm/pkg/contexts/ocm/registration" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/labels/routingslip/spi" + "ocm.software/ocm/api/ocm/plugin/plugins" + "ocm.software/ocm/api/ocm/plugin/registration" + "ocm.software/ocm/api/utils/cobrautils/flagsets" ) var _ = Describe("setup plugin cache", func() { diff --git a/api/ocm/extensions/labels/routingslip/types/plugin/plugin.go b/api/ocm/extensions/labels/routingslip/types/plugin/plugin.go new file mode 100644 index 000000000..991761206 --- /dev/null +++ b/api/ocm/extensions/labels/routingslip/types/plugin/plugin.go @@ -0,0 +1,40 @@ +package plugin + +import ( + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/plugin" + "ocm.software/ocm/api/ocm/plugin/descriptor" + "ocm.software/ocm/api/ocm/plugin/ppi" +) + +type plug = plugin.Plugin + +// PluginHandler is a shared object between the AccessMethod implementation and the Entry implementation. The +// object knows the actual plugin and can therefore forward the method calls to corresponding cli commands. +type PluginHandler struct { + plug +} + +func NewPluginHandler(p plugin.Plugin) *PluginHandler { + return &PluginHandler{plug: p} +} + +func (p *PluginHandler) Describe(spec *Entry, ctx cpi.Context) string { + sspec := p.GetValueSetDescriptor(descriptor.PURPOSE_ROUTINGSLIP, spec.GetKind(), spec.GetVersion()) + if sspec == nil { + return "unknown type " + spec.GetType() + } + info, err := p.Validate(spec) + if err != nil { + return err.Error() + } + return info.Short +} + +func (p *PluginHandler) Validate(spec *Entry) (*ppi.ValueSetInfo, error) { + data, err := spec.GetRaw() + if err != nil { + return nil, err + } + return p.plug.ValidateValueSet(descriptor.PURPOSE_ROUTINGSLIP, data) +} diff --git a/pkg/contexts/ocm/labels/routingslip/types/plugin/suite_test.go b/api/ocm/extensions/labels/routingslip/types/plugin/suite_test.go similarity index 100% rename from pkg/contexts/ocm/labels/routingslip/types/plugin/suite_test.go rename to api/ocm/extensions/labels/routingslip/types/plugin/suite_test.go diff --git a/pkg/contexts/ocm/labels/routingslip/types/plugin/testdata/test b/api/ocm/extensions/labels/routingslip/types/plugin/testdata/test similarity index 100% rename from pkg/contexts/ocm/labels/routingslip/types/plugin/testdata/test rename to api/ocm/extensions/labels/routingslip/types/plugin/testdata/test diff --git a/api/ocm/extensions/labels/routingslip/types/plugin/type.go b/api/ocm/extensions/labels/routingslip/types/plugin/type.go new file mode 100644 index 000000000..e096992b4 --- /dev/null +++ b/api/ocm/extensions/labels/routingslip/types/plugin/type.go @@ -0,0 +1,70 @@ +package plugin + +import ( + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + "ocm.software/ocm/api/ocm/extensions/labels/routingslip/spi" + "ocm.software/ocm/api/ocm/plugin" + "ocm.software/ocm/api/ocm/plugin/descriptor" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/api/utils/runtime" +) + +type entryType struct { + spi.EntryType + plug plugin.Plugin + cliopts flagsets.ConfigOptionTypeSet +} + +var _ spi.EntryType = (*entryType)(nil) + +func NewType(name string, p plugin.Plugin, desc *plugin.ValueSetDescriptor) spi.EntryType { + format := desc.Format + if format != "" { + format = "\n" + format + } + + t := &entryType{ + plug: p, + } + + cfghdlr := flagsets.NewConfigOptionTypeSetHandler(name, t.AddConfig) + for _, o := range desc.CLIOptions { + var opt flagsets.ConfigOptionType + if o.Type == "" { + opt = options.DefaultRegistry.GetOptionType(o.Name) + if opt == nil { + p.Context().Logger(plugin.TAG).Warn("unknown option", "plugin", p.Name(), "valueset", name, "option", o.Name) + } + } else { + var err error + opt, err = options.DefaultRegistry.CreateOptionType(o.Type, o.Name, o.Description) + if err != nil { + p.Context().Logger(plugin.TAG).Warn("invalid option", "plugin", p.Name(), "valueset", name, "option", o.Name, "error", err.Error()) + } + } + if opt != nil { + cfghdlr.AddOptionType(opt) + } + } + aopts := []spi.EntryTypeOption{spi.WithDescription(desc.Description), spi.WithFormatSpec(format)} + if cfghdlr.Size() > 0 { + aopts = append(aopts, spi.WithConfigHandler(cfghdlr)) + t.cliopts = cfghdlr + } + t.EntryType = spi.NewEntryType[*Entry](name, aopts...) + return t +} + +func (t *entryType) Decode(data []byte, unmarshaler runtime.Unmarshaler) (spi.Entry, error) { + spec, err := t.EntryType.Decode(data, unmarshaler) + if err != nil { + return nil, err + } + spec.(*Entry).handler = NewPluginHandler(t.plug) + return spec, nil +} + +func (t *entryType) AddConfig(opts flagsets.ConfigOptions, cfg flagsets.Config) error { + opts = opts.FilterBy(t.cliopts.HasOptionType) + return t.plug.ComposeValueSet(descriptor.PURPOSE_ROUTINGSLIP, t.GetType(), opts, cfg) +} diff --git a/api/ocm/extensions/labels/routingslip/usage.go b/api/ocm/extensions/labels/routingslip/usage.go new file mode 100644 index 000000000..2d61401e3 --- /dev/null +++ b/api/ocm/extensions/labels/routingslip/usage.go @@ -0,0 +1,79 @@ +package routingslip + +import ( + "fmt" + "strings" + + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/api/utils/runtime" +) + +func EntryUsage(scheme EntryTypeScheme, cli bool) string { + s := ` +The following list describes the well-known entry types explicitly supported +by this version of the CLI, their versions and specification formats. Other +kinds of entries can be configured using the --entry option. +` + type method struct { + desc string + versions map[string]string + options flagsets.ConfigOptionTypeSetHandler + } + + descs := map[string]*method{} + + // gather info for kinds and versions + for _, n := range scheme.KnownTypeNames() { + kind, vers := runtime.KindVersion(n) + + info := descs[kind] + if info == nil { + info = &method{versions: map[string]string{}} + descs[kind] = info + } + + if vers == "" { + vers = "v1" + } + if _, ok := info.versions[vers]; !ok { + info.versions[vers] = "" + } + + t := scheme.GetType(n) + + if t.ConfigOptionTypeSetHandler() != nil { + info.options = t.ConfigOptionTypeSetHandler() + } + desc := t.Description() + if desc != "" { + info.desc = desc + } + + desc = t.Format() + if desc != "" { + info.versions[vers] = desc + } + } + + for _, t := range utils.StringMapKeys(descs) { + info := descs[t] + desc := strings.Trim(info.desc, "\n") + if desc != "" { + s = fmt.Sprintf("%s\n- Entry type %s\n\n%s\n\n", s, t, utils.IndentLines(desc, " ")) + + format := "" + for _, f := range utils.StringMapKeys(info.versions) { + desc = strings.Trim(info.versions[f], "\n") + if desc != "" { + format = fmt.Sprintf("%s\n- Version %s\n\n%s\n", format, f, utils.IndentLines(desc, " ")) + } + } + if format != "" { + s += fmt.Sprintf(" The following versions are supported:\n%s\n", strings.Trim(utils.IndentLines(format, " "), "\n")) + } + } + s += utils.IndentLines(flagsets.FormatConfigOptions(info.options), " ") + } + return s +} diff --git a/api/ocm/extensions/pubsub/attr.go b/api/ocm/extensions/pubsub/attr.go new file mode 100644 index 000000000..fc8757ed5 --- /dev/null +++ b/api/ocm/extensions/pubsub/attr.go @@ -0,0 +1,40 @@ +package pubsub + +import ( + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/ocm/cpi" +) + +const ATTR_PUBSUB_TYPES = "ocm.software/ocm/api/ocm/extensions/pubsub" + +type Attribute struct { + ProviderRegistry + TypeScheme +} + +func For(ctx cpi.ContextProvider) *Attribute { + if ctx == nil { + return &Attribute{ + ProviderRegistry: DefaultRegistry, + TypeScheme: DefaultTypeScheme, + } + } + return ctx.OCMContext().GetAttributes().GetOrCreateAttribute(ATTR_PUBSUB_TYPES, create).(*Attribute) +} + +func create(datacontext.Context) interface{} { + return &Attribute{ + ProviderRegistry: NewProviderRegistry(DefaultRegistry), + TypeScheme: NewTypeScheme(DefaultTypeScheme), + } +} + +func SetSchemeFor(ctx cpi.ContextProvider, registry TypeScheme) { + attr := For(ctx) + attr.TypeScheme = registry +} + +func SetProvidersFor(ctx cpi.ContextProvider, registry ProviderRegistry) { + attr := For(ctx) + attr.ProviderRegistry = registry +} diff --git a/pkg/contexts/ocm/pubsub/doc.go b/api/ocm/extensions/pubsub/doc.go similarity index 100% rename from pkg/contexts/ocm/pubsub/doc.go rename to api/ocm/extensions/pubsub/doc.go diff --git a/api/ocm/extensions/pubsub/interface.go b/api/ocm/extensions/pubsub/interface.go new file mode 100644 index 000000000..744a14a82 --- /dev/null +++ b/api/ocm/extensions/pubsub/interface.go @@ -0,0 +1,225 @@ +package pubsub + +import ( + "encoding/json" + "fmt" + "slices" + "sync" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/general" + "github.com/mandelsoft/goutils/generics" + "github.com/mandelsoft/goutils/optionutils" + "github.com/modern-go/reflect2" + + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils/errkind" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/api/utils/runtime/descriptivetype" +) + +const KIND_PUBSUBTYPE = "pub/sub" + +type Option = descriptivetype.Option + +func WithFormatSpec(fmt string) Option { + return descriptivetype.WithFormatSpec(fmt) +} + +func WithDesciption(desc string) Option { + return descriptivetype.WithDescription(desc) +} + +//////////////////////////////////////////////////////////////////////////////// + +type PubSubType descriptivetype.TypedObjectType[PubSubSpec] + +// PubSubSpec is the interface publish/subscribe specifications +// must fulfill. The main task is to map the specification +// to a concrete implementation of the pub/sub adapter +// which forwards events to the described system. +type PubSubSpec interface { + runtime.VersionedTypedObject + + PubSubMethod(repo cpi.Repository) (PubSubMethod, error) + Describe(ctx cpi.Context) string +} + +type ( + PubSubSpecDecoder = runtime.TypedObjectDecoder[PubSubSpec] + PubSubTypeProvider = runtime.KnownTypesProvider[PubSubSpec, PubSubType] +) + +// PubSubMethod is the handler able to publish +// an OCM component version event. +type PubSubMethod interface { + NotifyComponentVersion(version common.NameVersion) error +} + +// TypeScheme is the registry for specification types for +// PubSub types. A PubSub type is finally able to +// provide an implementation for notifying a dedicated +// PubSub instance. +type TypeScheme descriptivetype.TypeScheme[PubSubSpec, PubSubType] + +func NewTypeScheme(base ...TypeScheme) TypeScheme { + return descriptivetype.NewTypeScheme[PubSubSpec, PubSubType, TypeScheme]("PubSub type", nil, &UnknownPubSubSpec{}, false, base...) +} + +func NewStrictTypeScheme(base ...TypeScheme) runtime.VersionedTypeRegistry[PubSubSpec, PubSubType] { + return descriptivetype.NewTypeScheme[PubSubSpec, PubSubType, TypeScheme]("PubSub type", nil, &UnknownPubSubSpec{}, false, base...) +} + +// DefaultTypeScheme contains all globally known PubSub serializers. +var DefaultTypeScheme = NewTypeScheme() + +func RegisterType(atype PubSubType) { + DefaultTypeScheme.Register(atype) +} + +func CreatePubSubSpec(t runtime.TypedObject) (PubSubSpec, error) { + return DefaultTypeScheme.Convert(t) +} + +func NewPubSubType[I PubSubSpec](name string, opts ...Option) PubSubType { + t := descriptivetype.NewTypedObjectTypeObject[PubSubSpec](runtime.NewVersionedTypedObjectType[PubSubSpec, I](name)) + ta := descriptivetype.NewTypeObjectTarget[PubSubSpec](t) + optionutils.ApplyOptions[descriptivetype.OptionTarget](ta, opts...) + return t +} + +//////////////////////////////////////////////////////////////////////////////// + +type UnknownPubSubSpec struct { + runtime.UnstructuredVersionedTypedObject `json:",inline"` +} + +var ( + _ runtime.TypedObject = &UnknownPubSubSpec{} + _ runtime.Unknown = &UnknownPubSubSpec{} +) + +func (_ *UnknownPubSubSpec) IsUnknown() bool { + return true +} + +func (s *UnknownPubSubSpec) PubSubMethod(repository cpi.Repository) (PubSubMethod, error) { + return nil, errors.ErrUnknown(KIND_PUBSUBTYPE, s.GetType()) +} + +func (s *UnknownPubSubSpec) Describe(ctx cpi.Context) string { + return fmt.Sprintf("unknown PubSub specification type %q", s.GetType()) +} + +var _ PubSubSpec = &UnknownPubSubSpec{} + +//////////////////////////////////////////////////////////////////////////////// + +type Unwrapable interface { + Unwrap(ctx cpi.Context) []PubSubSpec +} + +type Evaluatable interface { + Evaluate(ctx cpi.Context) (PubSubSpec, error) +} + +//////////////////////////////////////////////////////////////////////////////// + +type GenericPubSubSpec struct { + runtime.UnstructuredVersionedTypedObject `json:",inline"` + + lock sync.Mutex + cached PubSubSpec + cachedData []byte +} + +var ( + _ PubSubSpec = &GenericPubSubSpec{} + _ Unwrapable = &GenericPubSubSpec{} + _ Evaluatable = &GenericPubSubSpec{} +) + +func ToGenericPubSubSpec(spec PubSubSpec) (*GenericPubSubSpec, error) { + if reflect2.IsNil(spec) { + return nil, nil + } + if g, ok := spec.(*GenericPubSubSpec); ok { + return g, nil + } + data, err := json.Marshal(spec) + if err != nil { + return nil, err + } + return newGenericPubSubSpec(data, runtime.DefaultJSONEncoding) +} + +func NewGenericPubSubSpec(data []byte, unmarshaler ...runtime.Unmarshaler) (PubSubSpec, error) { + return generics.CastPointerR[PubSubSpec](newGenericPubSubSpec(data, general.Optional(unmarshaler...))) +} + +func newGenericPubSubSpec(data []byte, unmarshaler runtime.Unmarshaler) (*GenericPubSubSpec, error) { + unstr := &runtime.UnstructuredVersionedTypedObject{} + if unmarshaler == nil { + unmarshaler = runtime.DefaultYAMLEncoding + } + err := unmarshaler.Unmarshal(data, unstr) + if err != nil { + return nil, err + } + return &GenericPubSubSpec{UnstructuredVersionedTypedObject: *unstr}, nil +} + +func (s *GenericPubSubSpec) Unwrap(ctx cpi.Context) []PubSubSpec { + eff, err := s.Evaluate(ctx) + if err != nil { + return nil + } + if u, ok := eff.(Unwrapable); ok { + return u.Unwrap(ctx) + } + return nil +} + +func (s *GenericPubSubSpec) Describe(ctx cpi.Context) string { + eff, err := s.Evaluate(ctx) + if err != nil { + return fmt.Sprintf("invalid access specification: %s", err.Error()) + } + return eff.Describe(ctx) +} + +func (s *GenericPubSubSpec) Evaluate(ctx cpi.Context) (PubSubSpec, error) { + s.lock.Lock() + defer s.lock.Unlock() + + if s.cached != nil && s.cachedData != nil { + if d, err := s.GetRaw(); err == nil { + if slices.Equal(d, s.cachedData) { + return s.cached, nil + } + } + s.cached = nil + s.cachedData = nil + } + raw, err := s.GetRaw() + if err != nil { + return nil, err + } + s.cached, err = For(ctx).TypeScheme.Decode(raw, runtime.DefaultJSONEncoding) + if err == nil { + s.cachedData = raw + } + return s.cached, err +} + +func (s *GenericPubSubSpec) PubSubMethod(repository cpi.Repository) (PubSubMethod, error) { + spec, err := s.Evaluate(repository.GetContext()) + if err != nil { + return nil, err + } + if _, ok := spec.(*GenericPubSubSpec); ok { + return nil, errors.ErrUnknown(errkind.KIND_ACCESSMETHOD, s.GetType()) + } + return spec.PubSubMethod(repository) +} diff --git a/api/ocm/extensions/pubsub/provider.go b/api/ocm/extensions/pubsub/provider.go new file mode 100644 index 000000000..81251ddbd --- /dev/null +++ b/api/ocm/extensions/pubsub/provider.go @@ -0,0 +1,99 @@ +package pubsub + +import ( + "sync" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/general" + "golang.org/x/exp/maps" + + "ocm.software/ocm/api/ocm/cpi" +) + +// ProviderRegistry holds handlers able to extract +// a PubSub specification for an OCM repository of a dedicated kind. +type ProviderRegistry interface { + Register(repoKind string, prov Provider) + KnownProviders() map[string]Provider + AddKnownProviders(registry ProviderRegistry) + + For(repo string) Provider +} + +var DefaultRegistry = NewProviderRegistry() + +func RegisterProvider(repokind string, prov Provider) { + DefaultRegistry.Register(repokind, prov) +} + +// A Provider is able to extract a pub sub configuration for +// an ocm repository (typically registered for a dedicated type of repository). +// It does not handle the pub sub system, but just the persistence of +// a pub sub specification configured for a dedicated type of repository. +type Provider interface { + GetPubSubSpec(repo cpi.Repository) (PubSubSpec, error) + SetPubSubSpec(repo cpi.Repository, spec PubSubSpec) error +} + +type NopProvider struct{} + +func (p NopProvider) GetPubSubSpec(repo cpi.Repository) (PubSubSpec, error) { + return nil, nil +} + +func (p NopProvider) SetPubSubSpec(repo cpi.Repository, spec PubSubSpec) error { + return errors.ErrNotSupported("pub/sub configuration") +} + +func NewProviderRegistry(base ...ProviderRegistry) ProviderRegistry { + return &providers{ + base: general.Optional(base...), + providers: map[string]Provider{}, + } +} + +type providers struct { + lock sync.Mutex + + base ProviderRegistry + providers map[string]Provider +} + +func (p *providers) Register(repoKind string, prov Provider) { + p.lock.Lock() + defer p.lock.Unlock() + + p.providers[repoKind] = prov +} + +func (p *providers) For(repo string) Provider { + p.lock.Lock() + defer p.lock.Unlock() + prov := p.providers[repo] + if prov != nil { + return prov + } + if p.base != nil { + return p.base.For(repo) + } + return nil +} + +func (p *providers) KnownProviders() map[string]Provider { + if p.base != nil { + m := p.base.KnownProviders() + for n, e := range p.providers { + if m[n] == nil { + m[n] = e + } + } + return m + } + return maps.Clone(p.providers) +} + +func (p *providers) AddKnownProviders(base ProviderRegistry) { + for n, e := range base.KnownProviders() { + p.providers[n] = e + } +} diff --git a/api/ocm/extensions/pubsub/providers/init.go b/api/ocm/extensions/pubsub/providers/init.go new file mode 100644 index 000000000..1f7eced2f --- /dev/null +++ b/api/ocm/extensions/pubsub/providers/init.go @@ -0,0 +1,5 @@ +package providers + +import ( + _ "ocm.software/ocm/api/ocm/extensions/pubsub/providers/ocireg" +) diff --git a/api/ocm/extensions/pubsub/providers/ocireg/provider.go b/api/ocm/extensions/pubsub/providers/ocireg/provider.go new file mode 100644 index 000000000..4a391dec6 --- /dev/null +++ b/api/ocm/extensions/pubsub/providers/ocireg/provider.go @@ -0,0 +1,180 @@ +package ocireg + +import ( + "encoding/json" + "fmt" + "path" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/cpi/repocpi" + "ocm.software/ocm/api/ocm/extensions/pubsub" + compound2 "ocm.software/ocm/api/ocm/extensions/pubsub/types/compound" + "ocm.software/ocm/api/ocm/extensions/repositories/genericocireg" + "ocm.software/ocm/api/ocm/extensions/repositories/genericocireg/componentmapping" + "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +const ( + ConfigMimeType = "application/vnd.ocm.software.repository.config.v1+json" + PubSubLayerMimeTye = "application/vnd.ocm.software.repository.config.pubsub.v1+json" +) + +const META = "meta" + +func init() { + pubsub.RegisterProvider(ocireg.Type, &Provider{}) +} + +type Provider struct{} + +var _ pubsub.Provider = (*Provider)(nil) + +func (p *Provider) GetPubSubSpec(repo repocpi.Repository) (pubsub.PubSubSpec, error) { + impl, err := repocpi.GetRepositoryImplementation(repo) + if err != nil { + return nil, err + } + gen, ok := impl.(*genericocireg.RepositoryImpl) + if !ok { + return nil, errors.ErrNotSupported("non-oci based ocm repository") + } + + ocirepo := path.Join(gen.Meta().SubPath, componentmapping.ComponentDescriptorNamespace) + acc, err := gen.OCIRepository().LookupArtifact(ocirepo, META) + if errors.IsErrNotFound(err) || errors.IsErrUnknown(err) { + return nil, nil + } + if err != nil { + return nil, errors.Wrapf(err, "cannot access meta data manifest version") + } + defer acc.Close() + m := acc.ManifestAccess() + if m == nil { + return nil, fmt.Errorf("meta data artifact is no manifest artifact") + } + if m.GetDescriptor().Config.MediaType != ConfigMimeType { + return nil, fmt.Errorf("meta data artifact has unexpected mime type %q", m.GetDescriptor().Config.MediaType) + } + compound, _ := compound2.New() + for _, l := range m.GetDescriptor().Layers { + if l.MediaType == PubSubLayerMimeTye { + var ps pubsub.GenericPubSubSpec + + blob, err := m.GetBlob(l.Digest) + if err != nil { + return nil, err + } + data, err := blob.Get() + blob.Close() + if err != nil { + return nil, err + } + err = json.Unmarshal(data, &ps) + if err != nil { + return nil, err + } + compound.Specifications = append(compound.Specifications, &ps) + } + } + return compound.Effective(), nil +} + +func (p *Provider) SetPubSubSpec(repo cpi.Repository, spec pubsub.PubSubSpec) error { + impl, err := repocpi.GetRepositoryImplementation(repo) + if err != nil { + return err + } + gen, ok := impl.(*genericocireg.RepositoryImpl) + if !ok { + return errors.ErrNotSupported("non-oci based ocm repository") + } + + var data []byte + if spec != nil { + data, err = json.Marshal(spec) + if err != nil { + return err + } + } + + ocirepo := path.Join(gen.Meta().SubPath, componentmapping.ComponentDescriptorNamespace) + ns, err := gen.OCIRepository().LookupNamespace(ocirepo) + if err != nil { + return err + } + defer ns.Close() + + acc, err := ns.GetArtifact(META) + if err != nil { + if errors.IsErrNotFound(err) || errors.IsErrUnknown(err) { + if spec == nil { + return nil + } + } else { + return err + } + } + if acc == nil { + acc, err = ns.NewArtifact() + if err != nil { + return err + } + m, err := acc.Manifest() + if err != nil { + return err + } + config := blobaccess.ForString(ConfigMimeType, "{}") + m.Config.MediaType = config.MimeType() + m.Config.Digest = config.Digest() + err = acc.AddBlob(config) + if err != nil { + return err + } + } + defer acc.Close() + + m := acc.ManifestAccess() + if m == nil { + return fmt.Errorf("meta data artifact is no manifest artifact") + } + if m.GetDescriptor().Config.MediaType != ConfigMimeType { + return fmt.Errorf("meta data artifact has unexpected mime type %q", m.GetDescriptor().Config.MediaType) + } + + blob := blobaccess.ForData(PubSubLayerMimeTye, data) + defer blob.Close() + + layers := m.GetDescriptor().Layers + for i := 0; i < len(layers); i++ { + l := layers[i] + if l.MediaType == PubSubLayerMimeTye { + if data != nil { + m.AddBlob(blob) + l.Digest = blob.Digest() + b, err := ns.AddArtifact(m, META) + if b != nil { + b.Close() + } + return err + } else { + layers = append(layers[:i], layers[i+1:]...) + i-- + } + } + } + m.GetDescriptor().Layers = layers + if data != nil { + _, err = m.AddLayer(blob, nil) + if err != nil { + return err + } + } + b, err := ns.AddArtifact(m, META) + if b != nil { + b.Close() + } + return err +} diff --git a/pkg/contexts/ocm/pubsub/providers/ocireg/provider_test.go b/api/ocm/extensions/pubsub/providers/ocireg/provider_test.go similarity index 77% rename from pkg/contexts/ocm/pubsub/providers/ocireg/provider_test.go rename to api/ocm/extensions/pubsub/providers/ocireg/provider_test.go index 0103755a1..986551e58 100644 --- a/pkg/contexts/ocm/pubsub/providers/ocireg/provider_test.go +++ b/api/ocm/extensions/pubsub/providers/ocireg/provider_test.go @@ -6,16 +6,16 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "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/common/accessio" - ocictf "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub" - "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub/providers/ocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/genericocireg/componentmapping" - "github.com/open-component-model/ocm/pkg/runtime" + . "ocm.software/ocm/api/helper/builder" + + ocictf "ocm.software/ocm/api/oci/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/pubsub" + "ocm.software/ocm/api/ocm/extensions/pubsub/providers/ocireg" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/extensions/repositories/genericocireg/componentmapping" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/runtime" ) const ARCH = "ctf" diff --git a/pkg/contexts/ocm/pubsub/providers/ocireg/suite_test.go b/api/ocm/extensions/pubsub/providers/ocireg/suite_test.go similarity index 100% rename from pkg/contexts/ocm/pubsub/providers/ocireg/suite_test.go rename to api/ocm/extensions/pubsub/providers/ocireg/suite_test.go diff --git a/pkg/contexts/ocm/pubsub/pubsub_test.go b/api/ocm/extensions/pubsub/pubsub_test.go similarity index 87% rename from pkg/contexts/ocm/pubsub/pubsub_test.go rename to api/ocm/extensions/pubsub/pubsub_test.go index c824eb532..cac6033e3 100644 --- a/pkg/contexts/ocm/pubsub/pubsub_test.go +++ b/api/ocm/extensions/pubsub/pubsub_test.go @@ -11,14 +11,14 @@ import ( "github.com/mandelsoft/goutils/sliceutils" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub" - "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub/types/compound" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/composition" - "github.com/open-component-model/ocm/pkg/runtime" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/pubsub" + "ocm.software/ocm/api/ocm/extensions/pubsub/types/compound" + "ocm.software/ocm/api/ocm/extensions/repositories/composition" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" ) const ( diff --git a/api/ocm/extensions/pubsub/setup.go b/api/ocm/extensions/pubsub/setup.go new file mode 100644 index 000000000..51479a769 --- /dev/null +++ b/api/ocm/extensions/pubsub/setup.go @@ -0,0 +1,34 @@ +package pubsub + +import ( + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/ocm/cpi" +) + +func init() { + datacontext.RegisterSetupHandler(datacontext.SetupHandlerFunction(setupContext)) +} + +func setupContext(mode datacontext.BuilderMode, ctx datacontext.Context) { + if octx, ok := ctx.(cpi.Context); ok { + switch mode { + case datacontext.MODE_SHARED: + fallthrough + case datacontext.MODE_DEFAULTED: + // do nothing, fallback to the default attribute lookup + case datacontext.MODE_EXTENDED: + SetSchemeFor(octx, NewTypeScheme(DefaultTypeScheme)) + SetProvidersFor(octx, NewProviderRegistry(DefaultRegistry)) + case datacontext.MODE_CONFIGURED: + s := NewTypeScheme(nil) + s.AddKnownTypes(DefaultTypeScheme) + SetSchemeFor(octx, s) + r := NewProviderRegistry(nil) + r.AddKnownProviders(DefaultRegistry) + SetProvidersFor(octx, r) + case datacontext.MODE_INITIAL: + SetSchemeFor(octx, NewTypeScheme()) + SetProvidersFor(octx, NewProviderRegistry()) + } + } +} diff --git a/pkg/contexts/ocm/pubsub/suite_test.go b/api/ocm/extensions/pubsub/suite_test.go similarity index 100% rename from pkg/contexts/ocm/pubsub/suite_test.go rename to api/ocm/extensions/pubsub/suite_test.go diff --git a/api/ocm/extensions/pubsub/types/compound/type.go b/api/ocm/extensions/pubsub/types/compound/type.go new file mode 100644 index 000000000..7505344d9 --- /dev/null +++ b/api/ocm/extensions/pubsub/types/compound/type.go @@ -0,0 +1,102 @@ +package compound + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/sliceutils" + + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/pubsub" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + Type = "compound" + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +func init() { + pubsub.RegisterType(pubsub.NewPubSubType[*Spec](Type, + pubsub.WithDesciption("A pub/sub system forwarding events to described sub-level systems."))) + pubsub.RegisterType(pubsub.NewPubSubType[*Spec](TypeV1, + pubsub.WithFormatSpec(`It is described by the following field: + +- **specifications** *list of pubsub specs* + + A list of nested sub-level specifications the events should be + forwarded to. +`))) +} + +// Spec provides a pub sub adapter registering events at its provider. +type Spec struct { + runtime.ObjectVersionedType + Specifications []*pubsub.GenericPubSubSpec `json:"specifications,omitempty"` +} + +var ( + _ pubsub.PubSubSpec = (*Spec)(nil) + _ pubsub.Unwrapable = (*Spec)(nil) +) + +func New(specs ...pubsub.PubSubSpec) (*Spec, error) { + var gen []*pubsub.GenericPubSubSpec + + for _, s := range specs { + g, err := pubsub.ToGenericPubSubSpec(s) + if err != nil { + return nil, err + } + gen = append(gen, g) + } + return &Spec{runtime.NewVersionedObjectType(Type), gen}, nil +} + +func (s *Spec) PubSubMethod(repo cpi.Repository) (pubsub.PubSubMethod, error) { + var meths []pubsub.PubSubMethod + + for _, e := range s.Specifications { + m, err := e.PubSubMethod(repo) + if err != nil { + return nil, err + } + meths = append(meths, m) + } + return &Method{meths}, nil +} + +func (s *Spec) Unwrap(ctx cpi.Context) []pubsub.PubSubSpec { + return sliceutils.Convert[pubsub.PubSubSpec](s.Specifications) +} + +func (s *Spec) Describe(_ cpi.Context) string { + return fmt.Sprintf("compound pub/sub specification with %d entries", len(s.Specifications)) +} + +func (s *Spec) Effective() pubsub.PubSubSpec { + switch len(s.Specifications) { + case 0: + return nil + case 1: + return s.Specifications[0] + default: + return s + } +} + +// Method finally registers events at contained methods. +type Method struct { + meths []pubsub.PubSubMethod +} + +var _ pubsub.PubSubMethod = (*Method)(nil) + +func (m *Method) NotifyComponentVersion(version common.NameVersion) error { + list := errors.ErrList() + for _, m := range m.meths { + list.Add(m.NotifyComponentVersion(version)) + } + return list.Result() +} diff --git a/api/ocm/extensions/pubsub/types/init.go b/api/ocm/extensions/pubsub/types/init.go new file mode 100644 index 000000000..2e14ba8e5 --- /dev/null +++ b/api/ocm/extensions/pubsub/types/init.go @@ -0,0 +1,6 @@ +package types + +import ( + _ "ocm.software/ocm/api/ocm/extensions/pubsub/types/compound" + _ "ocm.software/ocm/api/ocm/extensions/pubsub/types/redis" +) diff --git a/api/ocm/extensions/pubsub/types/redis/identity/identity.go b/api/ocm/extensions/pubsub/types/redis/identity/identity.go new file mode 100644 index 000000000..f0cdd18d4 --- /dev/null +++ b/api/ocm/extensions/pubsub/types/redis/identity/identity.go @@ -0,0 +1,120 @@ +package identity + +import ( + "fmt" + "strconv" + "strings" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/credentials/identity/hostpath" + "ocm.software/ocm/api/utils/listformat" +) + +const CONSUMER_TYPE = "Github" + +// identity properties. +const ( + ID_HOSTNAME = hostpath.ID_HOSTNAME + ID_PORT = hostpath.ID_PORT + ID_PATHPREFIX = hostpath.ID_PATHPREFIX + ID_CHANNEL = "channel" + ID_DATABASE = "database" +) + +// credential properties. +const ( + ATTR_USERNAME = cpi.ATTR_USERNAME + ATTR_PASSWORD = cpi.ATTR_PASSWORD +) + +func IdentityMatcher(request, cur, id cpi.ConsumerIdentity) bool { + match, better := hostpath.Match(CONSUMER_TYPE, request, cur, id) + if !match { + return false + } + + if request[ID_CHANNEL] != "" { + if id[ID_CHANNEL] != "" && id[ID_CHANNEL] != request[ID_CHANNEL] { + return false + } + } + if request[ID_DATABASE] != "" { + if id[ID_DATABASE] != "" && id[ID_DATABASE] != request[ID_DATABASE] { + return false + } + } + + // ok now it basically matches, check against current match + + if cur[ID_CHANNEL] == "" && request[ID_CHANNEL] != "" { + return true + } + if cur[ID_DATABASE] == "" && request[ID_DATABASE] != "" { + return true + } + return better +} + +func init() { + attrs := listformat.FormatListElements("", listformat.StringElementDescriptionList{ + ATTR_USERNAME, "Redis username", + ATTR_PASSWORD, "Redis password", + }) + cpi.RegisterStandardIdentity(CONSUMER_TYPE, IdentityMatcher, + `Redis PubSub credential matcher + +This matcher is a hostpath matcher with additional attributes: + +- *`+ID_CHANNEL+`* (required if set in pattern): the channel name +- *`+ID_DATABASE+`* the database number +`, + attrs) +} + +func PATCredentials(user, pass string) cpi.Credentials { + return cpi.DirectCredentials{ + ATTR_USERNAME: user, + ATTR_PASSWORD: pass, + } +} + +func GetConsumerId(serveraddr string, channel string, db int) cpi.ConsumerIdentity { + p := "" + host, port, err := ParseAddress(serveraddr) + if err == nil { + host = serveraddr + } + + id := cpi.ConsumerIdentity{ + cpi.ID_TYPE: CONSUMER_TYPE, + ID_HOSTNAME: host, + ID_CHANNEL: channel, + ID_DATABASE: fmt.Sprintf("%d", db), + } + if port != 0 { + id[ID_PORT] = fmt.Sprintf("%d", port) + } + if p != "" { + id[ID_PATHPREFIX] = p + } + return id +} + +func GetCredentials(ctx cpi.ContextProvider, serverurl string, channel string, db int) (cpi.Credentials, error) { + id := GetConsumerId(serverurl, channel, db) + return cpi.CredentialsForConsumer(ctx.CredentialsContext(), id, IdentityMatcher) +} + +func ParseAddress(addr string) (string, int, error) { + idx := strings.Index(addr, ":") + if idx < 0 { + return addr, 6379, nil + } + p, err := strconv.ParseInt(addr[idx+1:], 10, 32) + if err != nil { + return "", 0, errors.Wrapf(err, "invalid port in redis address") + } + return addr[:idx], int(p), nil +} diff --git a/api/ocm/extensions/pubsub/types/redis/redis_test.go b/api/ocm/extensions/pubsub/types/redis/redis_test.go new file mode 100644 index 000000000..8c1176fac --- /dev/null +++ b/api/ocm/extensions/pubsub/types/redis/redis_test.go @@ -0,0 +1,65 @@ +//go:build redis_test + +package redis_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/pubsub" + "ocm.software/ocm/api/ocm/extensions/pubsub/providers/ocireg" + "ocm.software/ocm/api/ocm/extensions/pubsub/types/redis" + "ocm.software/ocm/api/ocm/extensions/pubsub/types/redis/identity" + "ocm.software/ocm/api/ocm/extensions/repositories/composition" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessio" + common "ocm.software/ocm/api/utils/misc" +) + +const ( + ARCH = "ctf" + COMP = "acme.org/component" + VERS = "v1" +) + +var _ = Describe("Test Environment", func() { + var env *Builder + var repo ocm.Repository + + BeforeEach(func() { + env = NewBuilder() + env.OCMCommonTransport(ARCH, accessio.FormatDirectory) + attr := pubsub.For(env) + attr.ProviderRegistry.Register(ctf.Type, &ocireg.Provider{}) + + env.CredentialsContext().SetCredentialsForConsumer( + identity.GetConsumerId("localhost:6379", "ocm", 0), + credentials.NewCredentials(common.Properties{identity.ATTR_PASSWORD: "redis-test-0815"}), + ) + + repo = Must(ctf.Open(env, ctf.ACC_WRITABLE, ARCH, 0o600, env)) + }) + + AfterEach(func() { + if repo != nil { + MustBeSuccessful(repo.Close()) + } + env.Cleanup() + }) + + Context("local redis server", func() { + It("tests local server", func() { + MustBeSuccessful(pubsub.SetForRepo(repo, Must(redis.New("localhost:6379", "ocm", 0)))) + + cv := composition.NewComponentVersion(env, COMP, VERS) + defer Close(cv) + + Expect(repo.GetSpecification().GetKind()).To(Equal(ctf.Type)) + MustBeSuccessful(repo.AddComponentVersion(cv)) + }) + }) +}) diff --git a/pkg/contexts/ocm/pubsub/types/redis/suite_test.go b/api/ocm/extensions/pubsub/types/redis/suite_test.go similarity index 100% rename from pkg/contexts/ocm/pubsub/types/redis/suite_test.go rename to api/ocm/extensions/pubsub/types/redis/suite_test.go diff --git a/api/ocm/extensions/pubsub/types/redis/type.go b/api/ocm/extensions/pubsub/types/redis/type.go new file mode 100644 index 000000000..35113a45c --- /dev/null +++ b/api/ocm/extensions/pubsub/types/redis/type.go @@ -0,0 +1,95 @@ +package redis + +import ( + "context" + "fmt" + + "github.com/redis/go-redis/v9" + + credcpi "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/pubsub" + "ocm.software/ocm/api/ocm/extensions/pubsub/types/redis/identity" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + Type = "redis" + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +func init() { + pubsub.RegisterType(pubsub.NewPubSubType[*Spec](Type, + pubsub.WithDesciption("a redis pubsub sytsem."))) + pubsub.RegisterType(pubsub.NewPubSubType[*Spec](TypeV1, + pubsub.WithFormatSpec(`It is describe by the following field: + +- **serverAddr** *Address of redis server* +- **channel** *pubsub channel* +- **database** *database number* + + Publishing using the redis pubsub API. For every change a string message + with the format : is published. If multiple repositories + should be used, each repository should be configured with a different + channel. +`))) +} + +// Spec provides a pub sub adapter registering events at its provider. +type Spec struct { + runtime.ObjectVersionedType + ServerAddr string `json:"serverAddr"` + Channel string `json:"channel"` + Database int `json:"database"` +} + +var _ pubsub.PubSubSpec = (*Spec)(nil) + +func New(serverurl, channel string, db int) (*Spec, error) { + return &Spec{ + runtime.NewVersionedObjectType(Type), + serverurl, channel, db, + }, nil +} + +func (s *Spec) PubSubMethod(repo cpi.Repository) (pubsub.PubSubMethod, error) { + _, _, err := identity.ParseAddress(s.ServerAddr) + if err != nil { + return nil, err + } + + creds, err := identity.GetCredentials(repo.GetContext(), s.ServerAddr, s.Channel, s.Database) + if err != nil { + return nil, err + } + return &Method{s, creds}, nil +} + +func (s *Spec) Describe(_ cpi.Context) string { + return fmt.Sprintf("redis pubsub system %s channel %s, database %d", s.ServerAddr, s.Channel, s.Database) +} + +// Method finally publishes events. +type Method struct { + spec *Spec + creds credcpi.Credentials +} + +var _ pubsub.PubSubMethod = (*Method)(nil) + +func (m *Method) NotifyComponentVersion(version common.NameVersion) error { + // TODO: ipdate to credential provider interface + opts := &redis.Options{ + Addr: m.spec.ServerAddr, + DB: m.spec.Database, + } + if m.creds != nil { + opts.Username = m.creds.GetProperty(identity.ATTR_USERNAME) + opts.Password = m.creds.GetProperty(identity.ATTR_PASSWORD) + } + + rdb := redis.NewClient(opts) + defer rdb.Close() + return rdb.Publish(context.Background(), m.spec.Channel, version.String()).Err() +} diff --git a/api/ocm/extensions/pubsub/utils.go b/api/ocm/extensions/pubsub/utils.go new file mode 100644 index 000000000..31b2abc4b --- /dev/null +++ b/api/ocm/extensions/pubsub/utils.go @@ -0,0 +1,68 @@ +package pubsub + +import ( + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/maputils" + + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils/listformat" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" +) + +func SetForRepo(repo cpi.Repository, spec PubSubSpec) error { + prov := For(repo.GetContext()).For(repo.GetSpecification().GetKind()) + if prov != nil { + return prov.SetPubSubSpec(repo, spec) + } + return errors.ErrNotSupported("pub/sub config") +} + +func SpecForRepo(repo cpi.Repository) (PubSubSpec, error) { + prov := For(repo.GetContext()).For(repo.GetSpecification().GetKind()) + if prov != nil { + return prov.GetPubSubSpec(repo) + } + return nil, nil +} + +func SpecForData(ctx cpi.ContextProvider, data []byte) (PubSubSpec, error) { + return For(ctx).TypeScheme.Decode(data, runtime.DefaultYAMLEncoding) +} + +func PubSubForRepo(repo cpi.Repository) (PubSubMethod, error) { + spec, err := SpecForRepo(repo) + if spec == nil || err != nil { + return nil, err + } + return spec.PubSubMethod(repo) +} + +func Notify(repo cpi.Repository, nv common.NameVersion) error { + m, err := PubSubForRepo(repo) + if m == nil || err != nil { + return err + } + return m.NotifyComponentVersion(nv) +} + +func PubSubUsage(scheme TypeScheme, providers ProviderRegistry, cli bool) string { + s := ` +The following list describes the supported publish/subscribe system types, their +specification versions, and formats: +` + if len(scheme.KnownTypes()) == 0 { + s += "There are currently no known pub/sub types!" + } else { + s += scheme.Describe() + } + + list := maputils.OrderedKeys(providers.KnownProviders()) + if len(list) == 0 { + s += "There are currently no persistence providers!" + } else { + s += "There are persistence providers for the following repository types:\n" + s += listformat.FormatList("", list...) + } + return s +} diff --git a/pkg/contexts/ocm/repositories/comparch/README.md b/api/ocm/extensions/repositories/comparch/README.md similarity index 100% rename from pkg/contexts/ocm/repositories/comparch/README.md rename to api/ocm/extensions/repositories/comparch/README.md diff --git a/pkg/contexts/ocm/repositories/comparch/accessmethod_localfs.go b/api/ocm/extensions/repositories/comparch/accessmethod_localfs.go similarity index 84% rename from pkg/contexts/ocm/repositories/comparch/accessmethod_localfs.go rename to api/ocm/extensions/repositories/comparch/accessmethod_localfs.go index 2ec284ca0..f5e5f08f2 100644 --- a/pkg/contexts/ocm/repositories/comparch/accessmethod_localfs.go +++ b/api/ocm/extensions/repositories/comparch/accessmethod_localfs.go @@ -4,13 +4,13 @@ import ( "io" "sync" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/vfsattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/repocpi" - "github.com/open-component-model/ocm/pkg/refmgmt" + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/cpi/repocpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/refmgmt" ) //////////////////////////////////////////////////////////////////////////////// diff --git a/pkg/contexts/ocm/repositories/comparch/accessmethod_test.go b/api/ocm/extensions/repositories/comparch/accessmethod_test.go similarity index 78% rename from pkg/contexts/ocm/repositories/comparch/accessmethod_test.go rename to api/ocm/extensions/repositories/comparch/accessmethod_test.go index edc68c168..3fedace6d 100644 --- a/pkg/contexts/ocm/repositories/comparch/accessmethod_test.go +++ b/api/ocm/extensions/repositories/comparch/accessmethod_test.go @@ -9,15 +9,15 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/common/accessobj" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "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/compdesc" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/comparch" - "github.com/open-component-model/ocm/pkg/runtime" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localfsblob" + "ocm.software/ocm/api/ocm/extensions/repositories/comparch" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/runtime" ) var DefaultContext = ocm.New() diff --git a/pkg/contexts/ocm/repositories/comparch/comparch_test.go b/api/ocm/extensions/repositories/comparch/comparch_test.go similarity index 89% rename from pkg/contexts/ocm/repositories/comparch/comparch_test.go rename to api/ocm/extensions/repositories/comparch/comparch_test.go index 08892b749..f8f6cfe90 100644 --- a/pkg/contexts/ocm/repositories/comparch/comparch_test.go +++ b/api/ocm/extensions/repositories/comparch/comparch_test.go @@ -6,7 +6,7 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/testhelper" + . "ocm.software/ocm/api/ocm/testhelper" "github.com/mandelsoft/filepath/pkg/filepath" "github.com/mandelsoft/goutils/finalizer" @@ -14,20 +14,20 @@ import ( "github.com/mandelsoft/vfs/pkg/osfs" "github.com/mandelsoft/vfs/pkg/vfs" - "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/ocm" - "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/contexts/ocm/digester/digesters/blob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/comparch" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/env" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/signing/hasher/sha256" - "github.com/open-component-model/ocm/pkg/utils/tarutils" + "ocm.software/ocm/api/helper/env" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/digester/digesters/blob" + "ocm.software/ocm/api/ocm/extensions/repositories/comparch" + "ocm.software/ocm/api/tech/signing/hasher/sha256" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/api/utils/tarutils" ) const ( diff --git a/pkg/contexts/ocm/repositories/comparch/componentarchive.go b/api/ocm/extensions/repositories/comparch/componentarchive.go similarity index 87% rename from pkg/contexts/ocm/repositories/comparch/componentarchive.go rename to api/ocm/extensions/repositories/comparch/componentarchive.go index d1c144662..b3cf1488d 100644 --- a/pkg/contexts/ocm/repositories/comparch/componentarchive.go +++ b/api/ocm/extensions/repositories/comparch/componentarchive.go @@ -4,18 +4,18 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/common/accessobj" - ocicpi "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localfsblob" - ocmhdlr "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/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/cpi/repocpi" - "github.com/open-component-model/ocm/pkg/errkind" - "github.com/open-component-model/ocm/pkg/refmgmt" + ocicpi "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/cpi/repocpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localfsblob" + ocmhdlr "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/ocm" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/errkind" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/refmgmt" ) //////////////////////////////////////////////////////////////////////////////// diff --git a/api/ocm/extensions/repositories/comparch/format.go b/api/ocm/extensions/repositories/comparch/format.go new file mode 100644 index 000000000..5548cc899 --- /dev/null +++ b/api/ocm/extensions/repositories/comparch/format.go @@ -0,0 +1,135 @@ +package comparch + +import ( + "sync" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" +) + +// ComponentDescriptorFileName is the name of the component-descriptor file. +const ComponentDescriptorFileName = compdesc.ComponentDescriptorFileName + +// BlobsDirectoryName is the name of the blob directory in the tar. +const BlobsDirectoryName = "blobs" + +var accessObjectInfo = &accessobj.DefaultAccessObjectInfo{ + DescriptorFileName: ComponentDescriptorFileName, + ObjectTypeName: "artifactset", + ElementDirectoryName: BlobsDirectoryName, + ElementTypeName: "blob", + DescriptorHandlerFactory: NewStateHandler, +} + +type Object = ComponentArchive + +type FormatHandler interface { + accessio.Option + + Format() accessio.FileFormat + + Open(ctx cpi.ContextProvider, acc accessobj.AccessMode, path string, opts accessio.Options) (*Object, error) + Create(ctx cpi.ContextProvider, path string, opts accessio.Options, mode vfs.FileMode) (*Object, error) + Write(obj *Object, path string, opts accessio.Options, mode vfs.FileMode) error +} + +type formatHandler struct { + accessobj.FormatHandler +} + +var ( + FormatDirectory = RegisterFormat(accessobj.FormatDirectory) + FormatTAR = RegisterFormat(accessobj.FormatTAR) + FormatTGZ = RegisterFormat(accessobj.FormatTGZ) +) + +//////////////////////////////////////////////////////////////////////////////// + +var ( + fileFormats = map[accessio.FileFormat]*formatHandler{} + lock sync.RWMutex +) + +func RegisterFormat(f accessobj.FormatHandler) *formatHandler { + lock.Lock() + defer lock.Unlock() + h := &formatHandler{f} + fileFormats[f.Format()] = h + return h +} + +func GetFormats() []string { + lock.RLock() + defer lock.RUnlock() + return accessio.GetFormatsFor(fileFormats) +} + +func GetFormat(name accessio.FileFormat) FormatHandler { + lock.RLock() + defer lock.RUnlock() + h, ok := fileFormats[name] + if ok { + return h + } + return nil +} + +//////////////////////////////////////////////////////////////////////////////// + +func Open(ctx cpi.ContextProvider, acc accessobj.AccessMode, path string, mode vfs.FileMode, opts ...accessio.Option) (*Object, error) { + o, create, err := accessobj.HandleAccessMode(acc, path, nil, opts...) + if err != nil { + return nil, err + } + h, ok := fileFormats[*o.GetFileFormat()] + if !ok { + return nil, errors.ErrUnknown(accessobj.KIND_FILEFORMAT, o.GetFileFormat().String()) + } + if create { + return h.Create(ctx, path, o, mode) + } + return h.Open(ctx, acc, path, o) +} + +func Create(ctx cpi.ContextProvider, acc accessobj.AccessMode, path string, mode vfs.FileMode, opts ...accessio.Option) (*Object, error) { + o, err := accessio.AccessOptions(nil, opts...) + if err != nil { + return nil, err + } + o.DefaultFormat(accessio.FormatDirectory) + h, ok := fileFormats[*o.GetFileFormat()] + if !ok { + return nil, errors.ErrUnknown(accessobj.KIND_FILEFORMAT, o.GetFileFormat().String()) + } + return h.Create(ctx, path, o, mode) +} + +//////////////////////////////////////////////////////////////////////////////// + +func (h *formatHandler) Open(ctx cpi.ContextProvider, acc accessobj.AccessMode, path string, opts accessio.Options) (*Object, error) { + obj, err := h.FormatHandler.Open(accessObjectInfo, acc, path, opts) + if err != nil { + return nil, err + } + spec, err := NewRepositorySpec(acc, path, opts) + return _Wrap(ctx, obj, spec, err) +} + +func (h *formatHandler) Create(ctx cpi.ContextProvider, path string, opts accessio.Options, mode vfs.FileMode) (*Object, error) { + obj, err := h.FormatHandler.Create(accessObjectInfo, path, opts, mode) + if err != nil { + return nil, err + } + spec, err := NewRepositorySpec(accessobj.ACC_CREATE, path, opts) + return _Wrap(ctx, obj, spec, err) +} + +// WriteToFilesystem writes the current object to a filesystem. +func (h *formatHandler) Write(obj *Object, path string, opts accessio.Options, mode vfs.FileMode) error { + return h.FormatHandler.Write(obj.container.fsacc.Access(), path, opts, mode) +} diff --git a/api/ocm/extensions/repositories/comparch/repository.go b/api/ocm/extensions/repositories/comparch/repository.go new file mode 100644 index 000000000..53672a9a4 --- /dev/null +++ b/api/ocm/extensions/repositories/comparch/repository.go @@ -0,0 +1,313 @@ +package comparch + +import ( + "fmt" + "strings" + "sync" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/cpi/repocpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localfsblob" + ocmhdlr "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/ocm" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/errkind" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/refmgmt" +) + +type RepositoryImpl struct { + lock sync.RWMutex + bridge repocpi.RepositoryBridge + arch *ComponentArchive + nonref cpi.Repository +} + +var _ repocpi.RepositoryImpl = (*RepositoryImpl)(nil) + +func NewRepository(ctxp cpi.ContextProvider, s *RepositorySpec) (cpi.Repository, error) { + ctx := datacontext.InternalContextRef(ctxp.OCMContext()) + if s.GetPathFileSystem() == nil { + s.SetPathFileSystem(vfsattr.Get(ctx)) + } + a, err := Open(ctx, s.AccessMode, s.FilePath, 0o700, s) + if err != nil { + return nil, err + } + return a.AsRepository(), nil +} + +func newRepository(a *ComponentArchive) (main, nonref cpi.Repository) { + // close main cv abstraction on repository close -------v + impl := &RepositoryImpl{ + arch: a, + } + r := repocpi.NewRepository(impl, "comparch") + return r, impl.nonref +} + +func (r *RepositoryImpl) Close() error { + return r.arch.container.Close() +} + +func (r *RepositoryImpl) IsReadOnly() bool { + return r.arch.IsReadOnly() +} + +func (r *RepositoryImpl) SetReadOnly() { + r.arch.SetReadOnly() +} + +func (r *RepositoryImpl) SetBridge(base repocpi.RepositoryBridge) { + r.bridge = base + r.nonref = repocpi.NewNoneRefRepositoryView(base) +} + +func (r *RepositoryImpl) GetContext() cpi.Context { + return r.arch.GetContext() +} + +func (r *RepositoryImpl) ComponentLister() cpi.ComponentLister { + return r +} + +func (r *RepositoryImpl) matchPrefix(prefix string, closure bool) bool { + if r.arch.GetName() != prefix { + if prefix != "" && !strings.HasSuffix(prefix, "/") { + prefix += "/" + } + if !closure || !strings.HasPrefix(r.arch.GetName(), prefix) { + return false + } + } + return true +} + +func (r *RepositoryImpl) NumComponents(prefix string) (int, error) { + r.lock.RLock() + defer r.lock.RUnlock() + if r.arch == nil { + return -1, accessio.ErrClosed + } + if !r.matchPrefix(prefix, true) { + return 0, nil + } + return 1, nil +} + +func (r *RepositoryImpl) GetComponents(prefix string, closure bool) ([]string, error) { + r.lock.RLock() + defer r.lock.RUnlock() + if r.arch == nil { + return nil, accessio.ErrClosed + } + if !r.matchPrefix(prefix, closure) { + return []string{}, nil + } + return []string{r.arch.GetName()}, nil +} + +func (r *RepositoryImpl) Get() *ComponentArchive { + r.lock.RLock() + defer r.lock.RUnlock() + if r.arch != nil { + return r.arch + } + return nil +} + +func (r *RepositoryImpl) GetSpecification() cpi.RepositorySpec { + return r.arch.spec +} + +func (r *RepositoryImpl) ExistsComponentVersion(name string, ref string) (bool, error) { + r.lock.RLock() + defer r.lock.RUnlock() + if r.arch == nil { + return false, accessio.ErrClosed + } + return r.arch.GetName() == name && r.arch.GetVersion() == ref, nil +} + +func (r *RepositoryImpl) LookupComponent(name string) (*repocpi.ComponentAccessInfo, error) { + r.lock.RLock() + defer r.lock.RUnlock() + if r.arch == nil { + return nil, accessio.ErrClosed + } + if r.arch.GetName() != name { + return nil, errors.ErrNotFound(errkind.KIND_COMPONENT, name, Type) + } + return newComponentAccess(r) +} + +//////////////////////////////////////////////////////////////////////////////// + +type ComponentAccessImpl struct { + base repocpi.ComponentAccessBridge + repo *RepositoryImpl +} + +var _ repocpi.ComponentAccessImpl = (*ComponentAccessImpl)(nil) + +func newComponentAccess(r *RepositoryImpl) (*repocpi.ComponentAccessInfo, error) { + impl := &ComponentAccessImpl{ + repo: r, + } + return &repocpi.ComponentAccessInfo{impl, "component archive", true}, nil +} + +func (c *ComponentAccessImpl) Close() error { + return nil +} + +func (c *ComponentAccessImpl) SetBridge(base repocpi.ComponentAccessBridge) { + c.base = base +} + +func (c *ComponentAccessImpl) GetParentBridge() repocpi.RepositoryViewManager { + return c.repo.bridge +} + +func (c *ComponentAccessImpl) GetContext() cpi.Context { + return c.repo.GetContext() +} + +func (c *ComponentAccessImpl) GetName() string { + return c.repo.arch.GetName() +} + +func (c *ComponentAccessImpl) IsReadOnly() bool { + return c.repo.arch.IsReadOnly() +} + +func (c *ComponentAccessImpl) ListVersions() ([]string, error) { + return []string{c.repo.arch.GetVersion()}, nil +} + +func (c *ComponentAccessImpl) HasVersion(vers string) (bool, error) { + return vers == c.repo.arch.GetVersion(), nil +} + +func (c *ComponentAccessImpl) LookupVersion(version string) (*repocpi.ComponentVersionAccessInfo, error) { + if version != c.repo.arch.GetVersion() { + return nil, errors.ErrNotFound(cpi.KIND_COMPONENTVERSION, fmt.Sprintf("%s:%s", c.GetName(), c.repo.arch.GetVersion())) + } + return newComponentVersionAccess(c, version, false) +} + +func (c *ComponentAccessImpl) NewVersion(version string, overrides ...bool) (*repocpi.ComponentVersionAccessInfo, error) { + if version != c.repo.arch.GetVersion() { + return nil, errors.ErrNotSupported(cpi.KIND_COMPONENTVERSION, version, fmt.Sprintf("component archive %s:%s", c.GetName(), c.repo.arch.GetVersion())) + } + if !utils.Optional(overrides...) { + return nil, errors.ErrAlreadyExists(cpi.KIND_COMPONENTVERSION, fmt.Sprintf("%s:%s", c.GetName(), c.repo.arch.GetVersion())) + } + return newComponentVersionAccess(c, version, false) +} + +//////////////////////////////////////////////////////////////////////////////// + +type ComponentVersionContainer struct { + impl repocpi.ComponentVersionAccessBridge + + comp *ComponentAccessImpl + + descriptor *compdesc.ComponentDescriptor +} + +var _ repocpi.ComponentVersionAccessImpl = (*ComponentVersionContainer)(nil) + +func newComponentVersionAccess(comp *ComponentAccessImpl, version string, persistent bool) (*repocpi.ComponentVersionAccessInfo, error) { + c, err := newComponentVersionContainer(comp) + if err != nil { + return nil, err + } + return &repocpi.ComponentVersionAccessInfo{c, true, persistent}, nil +} + +func newComponentVersionContainer(comp *ComponentAccessImpl) (*ComponentVersionContainer, error) { + return &ComponentVersionContainer{ + comp: comp, + descriptor: comp.repo.arch.GetDescriptor(), + }, nil +} + +func (c *ComponentVersionContainer) SetBridge(impl repocpi.ComponentVersionAccessBridge) { + c.impl = impl +} + +func (c *ComponentVersionContainer) GetParentBridge() repocpi.ComponentAccessBridge { + return c.comp.base +} + +func (c *ComponentVersionContainer) Close() error { + return nil +} + +func (c *ComponentVersionContainer) GetContext() cpi.Context { + return c.comp.GetContext() +} + +func (c *ComponentVersionContainer) Repository() cpi.Repository { + return c.comp.repo.arch.nonref +} + +func (c *ComponentVersionContainer) IsReadOnly() bool { + return c.comp.repo.arch.IsReadOnly() +} + +func (c *ComponentVersionContainer) SetReadOnly() { + c.comp.repo.arch.SetReadOnly() +} + +func (c *ComponentVersionContainer) Update() error { + desc := c.comp.repo.arch.GetDescriptor() + *desc = *c.descriptor.Copy() + return c.comp.repo.arch.container.Update() +} + +func (c *ComponentVersionContainer) SetDescriptor(cd *compdesc.ComponentDescriptor) error { + *c.descriptor = *cd + return c.Update() +} + +func (c *ComponentVersionContainer) GetDescriptor() *compdesc.ComponentDescriptor { + return c.descriptor +} + +func (c *ComponentVersionContainer) GetBlob(name string) (cpi.DataAccess, error) { + return c.comp.repo.arch.container.GetBlob(name) +} + +func (c *ComponentVersionContainer) GetStorageContext() cpi.StorageContext { + return ocmhdlr.New(c.Repository(), c.comp.GetName(), &BlobSink{c.comp.repo.arch.container.fsacc}, Type) +} + +func (c *ComponentVersionContainer) AddBlob(blob cpi.BlobAccess, refName string, global cpi.AccessSpec) (cpi.AccessSpec, error) { + if blob == nil { + return nil, errors.New("a resource has to be defined") + } + err := c.comp.repo.arch.container.fsacc.AddBlob(blob) + if err != nil { + return nil, err + } + return localblob.New(common.DigestToFileName(blob.Digest()), refName, blob.MimeType(), global), nil +} + +func (c *ComponentVersionContainer) AccessMethod(a cpi.AccessSpec, cv refmgmt.ExtendedAllocatable) (cpi.AccessMethod, error) { + if a.GetKind() == localblob.Type || a.GetKind() == localfsblob.Type { + accessSpec, err := c.GetContext().AccessSpecForSpec(a) + if err != nil { + return nil, err + } + return newLocalFilesystemBlobAccessMethod(accessSpec.(*localblob.AccessSpec), c, cv) + } + return nil, errors.ErrNotSupported(errkind.KIND_ACCESSMETHOD, a.GetType(), "component archive") +} diff --git a/api/ocm/extensions/repositories/comparch/state.go b/api/ocm/extensions/repositories/comparch/state.go new file mode 100644 index 000000000..4c9b22f46 --- /dev/null +++ b/api/ocm/extensions/repositories/comparch/state.go @@ -0,0 +1,34 @@ +package comparch + +import ( + "reflect" + + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/utils/accessobj" +) + +type StateHandler struct{} + +var _ accessobj.StateHandler = &StateHandler{} + +func NewStateHandler(fs vfs.FileSystem) accessobj.StateHandler { + return &StateHandler{} +} + +func (i StateHandler) Initial() interface{} { + return compdesc.New("", "") +} + +func (i StateHandler) Encode(d interface{}) ([]byte, error) { + return compdesc.Encode(d.(*compdesc.ComponentDescriptor)) +} + +func (i StateHandler) Decode(data []byte) (interface{}, error) { + return compdesc.Decode(data) +} + +func (i StateHandler) Equivalent(a, b interface{}) bool { + return reflect.DeepEqual(a, b) +} diff --git a/pkg/contexts/ocm/repositories/comparch/suite_test.go b/api/ocm/extensions/repositories/comparch/suite_test.go similarity index 100% rename from pkg/contexts/ocm/repositories/comparch/suite_test.go rename to api/ocm/extensions/repositories/comparch/suite_test.go diff --git a/pkg/contexts/ocm/repositories/comparch/testdata/common/blobs/sha256.3ed99e50092c619823e2c07941c175ea2452f1455f570c55510586b387ec2ff2 b/api/ocm/extensions/repositories/comparch/testdata/common/blobs/sha256.3ed99e50092c619823e2c07941c175ea2452f1455f570c55510586b387ec2ff2 similarity index 100% rename from pkg/contexts/ocm/repositories/comparch/testdata/common/blobs/sha256.3ed99e50092c619823e2c07941c175ea2452f1455f570c55510586b387ec2ff2 rename to api/ocm/extensions/repositories/comparch/testdata/common/blobs/sha256.3ed99e50092c619823e2c07941c175ea2452f1455f570c55510586b387ec2ff2 diff --git a/pkg/contexts/ocm/repositories/comparch/testdata/common/component-descriptor.yaml b/api/ocm/extensions/repositories/comparch/testdata/common/component-descriptor.yaml similarity index 100% rename from pkg/contexts/ocm/repositories/comparch/testdata/common/component-descriptor.yaml rename to api/ocm/extensions/repositories/comparch/testdata/common/component-descriptor.yaml diff --git a/pkg/contexts/ocm/repositories/comparch/testdata/descriptor/component-descriptor.yaml b/api/ocm/extensions/repositories/comparch/testdata/descriptor/component-descriptor.yaml similarity index 100% rename from pkg/contexts/ocm/repositories/comparch/testdata/descriptor/component-descriptor.yaml rename to api/ocm/extensions/repositories/comparch/testdata/descriptor/component-descriptor.yaml diff --git a/pkg/contexts/ocm/repositories/comparch/testdata/directory/blobs/root/testfile b/api/ocm/extensions/repositories/comparch/testdata/directory/blobs/root/testfile similarity index 100% rename from pkg/contexts/ocm/repositories/comparch/testdata/directory/blobs/root/testfile rename to api/ocm/extensions/repositories/comparch/testdata/directory/blobs/root/testfile diff --git a/pkg/contexts/ocm/repositories/comparch/testdata/directory/component-descriptor.yaml b/api/ocm/extensions/repositories/comparch/testdata/directory/component-descriptor.yaml similarity index 100% rename from pkg/contexts/ocm/repositories/comparch/testdata/directory/component-descriptor.yaml rename to api/ocm/extensions/repositories/comparch/testdata/directory/component-descriptor.yaml diff --git a/api/ocm/extensions/repositories/comparch/type.go b/api/ocm/extensions/repositories/comparch/type.go new file mode 100644 index 000000000..c9a4f94a4 --- /dev/null +++ b/api/ocm/extensions/repositories/comparch/type.go @@ -0,0 +1,74 @@ +package comparch + +import ( + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + Type = "ComponentArchive" + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +func init() { + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](Type, nil)) + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1, nil)) +} + +type RepositorySpec struct { + runtime.ObjectVersionedType `json:",inline"` + accessio.StandardOptions `json:",omitempty"` + + // FileFormat is the format of the repository file + FilePath string `json:"filePath"` + // AccessMode can be set to request readonly access or creation + AccessMode accessobj.AccessMode `json:"accessMode,omitempty"` +} + +var ( + _ accessio.Option = (*RepositorySpec)(nil) + _ cpi.RepositorySpec = (*RepositorySpec)(nil) + _ cpi.IntermediateRepositorySpecAspect = (*RepositorySpec)(nil) +) + +// NewRepositorySpec creates a new RepositorySpec. +func NewRepositorySpec(acc accessobj.AccessMode, filePath string, opts ...accessio.Option) (*RepositorySpec, error) { + spec := &RepositorySpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(Type), + FilePath: filePath, + AccessMode: acc, + } + + _, err := accessio.AccessOptions(&spec.StandardOptions, opts...) + if err != nil { + return nil, err + } + return spec, nil +} + +func (a *RepositorySpec) IsIntermediate() bool { + return true +} + +func (a *RepositorySpec) GetType() string { + return Type +} + +func (a *RepositorySpec) Repository(ctx cpi.Context, creds credentials.Credentials) (cpi.Repository, error) { + return NewRepository(ctx, a) +} + +func (a *RepositorySpec) AsUniformSpec(cpi.Context) *cpi.UniformRepositorySpec { + opts := &accessio.StandardOptions{} + opts.Default() + p, err := vfs.Canonical(opts.GetPathFileSystem(), a.FilePath, false) + if err != nil { + return &cpi.UniformRepositorySpec{Type: a.GetKind(), SubPath: a.FilePath} + } + return &cpi.UniformRepositorySpec{Type: a.GetKind(), SubPath: p} +} diff --git a/api/ocm/extensions/repositories/comparch/uniform.go b/api/ocm/extensions/repositories/comparch/uniform.go new file mode 100644 index 000000000..00a3ca215 --- /dev/null +++ b/api/ocm/extensions/repositories/comparch/uniform.go @@ -0,0 +1,59 @@ +package comparch + +import ( + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" +) + +const AltType = "ca" + +func init() { + h := &repospechandler{} + cpi.RegisterRepositorySpecHandler(h, "") + cpi.RegisterRepositorySpecHandler(h, Type) + cpi.RegisterRepositorySpecHandler(h, AltType) + for _, f := range GetFormats() { + cpi.RegisterRepositorySpecHandler(h, f) + } +} + +type repospechandler struct{} + +func explicit(t string) bool { + return t == Type || t == AltType +} + +func (h *repospechandler) MapReference(ctx cpi.Context, u *cpi.UniformRepositorySpec) (cpi.RepositorySpec, error) { + path := u.Info + if u.Info == "" { + if u.Host == "" || u.Type == "" { + return nil, nil + } + path = u.Host + } + fs := vfsattr.Get(ctx) + + typ, _ := accessobj.MapType(u.Type, Type, accessio.FormatNone, true, "ca") + hint, f := accessobj.MapType(u.TypeHint, Type, accessio.FormatDirectory, false, "ca") + if !u.CreateIfMissing { + hint = "" + } + create, ok, err := accessobj.CheckFile(Type, hint, explicit(accessio.TypeForTypeSpec(u.Type)), path, fs, ComponentDescriptorFileName) + if !ok || (err != nil && typ == "") { + if err != nil { + return nil, err + } + if !ok { + return nil, nil + } + } + mode := accessobj.ACC_WRITABLE + createHint := accessio.FormatNone + if create { + mode |= accessobj.ACC_CREATE + createHint = f + } + return NewRepositorySpec(mode, path, createHint, accessio.PathFileSystem(fs)) +} diff --git a/api/ocm/extensions/repositories/composition/cache.go b/api/ocm/extensions/repositories/composition/cache.go new file mode 100644 index 000000000..a946b352e --- /dev/null +++ b/api/ocm/extensions/repositories/composition/cache.go @@ -0,0 +1,66 @@ +package composition + +import ( + "sync" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/finalizer" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils/refmgmt" +) + +const ATTR_REPOS = "ocm.software/ocm/api/ocm/extensions/repositories/composition" + +type Repositories struct { + lock sync.Mutex + repos map[string]cpi.Repository +} + +var _ finalizer.Finalizable = (*Repositories)(nil) + +func newRepositories(datacontext.Context) interface{} { + return &Repositories{ + repos: map[string]cpi.Repository{}, + } +} + +func (r *Repositories) GetRepository(name string) cpi.Repository { + r.lock.Lock() + defer r.lock.Unlock() + + return r.repos[name] +} + +func (r *Repositories) SetRepository(name string, repo cpi.Repository) { + r.lock.Lock() + defer r.lock.Unlock() + + old := r.repos[name] + if old != nil { + refmgmt.AsLazy(old).Close() + } + r.repos[name] = repo +} + +func (r *Repositories) Finalize() error { + r.lock.Lock() + defer r.lock.Unlock() + + list := errors.ErrListf("composition repositories") + for n, r := range r.repos { + list.Addf(nil, refmgmt.AsLazy(r).Close(), "repository %s", n) + } + + r.repos = map[string]cpi.Repository{} + return list.Result() +} + +func Cleanup(ctx cpi.ContextProvider) error { + repos := ctx.OCMContext().GetAttributes().GetAttribute(ATTR_REPOS) + if repos != nil { + return repos.(*Repositories).Finalize() + } + return nil +} diff --git a/api/ocm/extensions/repositories/composition/close_test.go b/api/ocm/extensions/repositories/composition/close_test.go new file mode 100644 index 000000000..887f1261c --- /dev/null +++ b/api/ocm/extensions/repositories/composition/close_test.go @@ -0,0 +1,151 @@ +package composition_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/oci/testhelper" + + "github.com/mandelsoft/goutils/finalizer" + + "ocm.software/ocm/api/helper/builder" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/relativeociref" + "ocm.software/ocm/api/ocm/extensions/repositories/comparch" + "ocm.software/ocm/api/ocm/extensions/repositories/composition" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/api/utils/refmgmt" +) + +const ( + OCIPATH = "/tmp/oci" + OCIHOST = "alias" +) + +const RES = "ref" + +var _ = Describe("cached access method blob", func() { + var env *builder.Builder + + BeforeEach(func() { + env = builder.NewBuilder() + }) + + AfterEach(func() { + env.Cleanup() + }) + + Context("ocireg", func() { + BeforeEach(func() { + env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { + OCIManifest1(env) + }) + FakeOCIRepo(env, OCIPATH, OCIHOST) + + env.OCMCommonTransport(OCIPATH, accessio.FormatDirectory, func() { + env.ComponentVersion(COMPONENT, VERSION, func() { + env.Resource(RES, VERSION, "testtyp", v1.LocalRelation, func() { + env.Access(relativeociref.New(OCINAMESPACE + ":" + OCIVERSION)) + }) + }) + }) + }) + + It("caches blobs on close", func() { + var finalize finalizer.Finalizer + defer Defer(finalize.Finalize) + + srcfinalize := finalize.Nested() + + ctfrepo := Must(ctf.Open(env, accessobj.ACC_READONLY, OCIPATH, 0o700, env)) + + refmgmt.AsLazy(ctfrepo) + + srcfinalize.Close(ctfrepo, "ctf") + srccv := Must(ctfrepo.LookupComponentVersion(COMPONENT, VERSION)) + srcfinalize.Close(srccv, "src cv") + + res := Must(srccv.GetResource(v1.NewIdentity(RES))) + srcblob := Must(res.BlobAccess()) + finalize.Close(srcblob, "source blob") + Expect(srcblob.MimeType()).To(Equal("application/vnd.oci.image.manifest.v1+tar+gzip")) + + // now close the original environment + // the blob access must be cached now and decoupled from the providing + // repository. + MustBeSuccessful(srcfinalize.Finalize()) + + Expect(srcblob.MimeType()).To(Equal("application/vnd.oci.image.manifest.v1+tar+gzip")) + }) + + It("caches blobs on close", func() { + var finalize finalizer.Finalizer + defer Defer(finalize.Finalize) + + srcfinalize := finalize.Nested() + + ctfrepo := Must(ctf.Open(env, accessobj.ACC_READONLY, OCIPATH, 0o700, env)) + srcfinalize.Close(ctfrepo, "ctf") + srccv := Must(ctfrepo.LookupComponentVersion(COMPONENT, VERSION)) + srcfinalize.Close(srccv, "src cv") + + res := Must(srccv.GetResource(v1.NewIdentity(RES))) + + // copy to composition repo + repo := composition.NewRepository(env) + finalize.Close(repo, "composition repository") + MustBeSuccessful(repo.AddComponentVersion(srccv)) + + // now close thenoriginal environment + // the blob access must be cached now and decoupled from the providing + // repository. + MustBeSuccessful(srcfinalize.Finalize()) + + cv := Must(repo.LookupComponentVersion(COMPONENT, VERSION)) + finalize.Close(cv, "composition cv") + + res = Must(cv.GetResource(v1.NewIdentity(RES))) + + m := Must(res.AccessMethod()) + finalize.Close(m, "copied method") + + Expect(m.MimeType()).To(Equal("application/vnd.oci.image.manifest.v1+tar+gzip")) + }) + }) + + Context("comparch", func() { + BeforeEach(func() { + env.ComponentArchive(OCIPATH, accessio.FormatTar, COMPONENT, VERSION, func() { + env.Resource(RES, VERSION, "testtyp", v1.LocalRelation, func() { + env.BlobStringData(mime.MIME_TEXT, "testdata") + }) + }) + }) + + It("caches blobs on close", func() { + var finalize finalizer.Finalizer + defer Defer(finalize.Finalize) + + srcfinalize := finalize.Nested() + + ctfrepo := Must(comparch.Open(env, accessobj.ACC_READONLY, OCIPATH, 0o700, env)) + srcfinalize.Close(ctfrepo, "ctf") + srccv := ctfrepo + + res := Must(srccv.GetResource(v1.NewIdentity(RES))) + srcblob := Must(res.BlobAccess()) + finalize.Close(srcblob, "source blob") + Expect(srcblob.MimeType()).To(Equal(mime.MIME_TEXT)) + + // now close the original environment + // the blob access must be cached now and decoupled from the providing + // repository. + MustBeSuccessful(srcfinalize.Finalize()) + + Expect(srcblob.MimeType()).To(Equal(mime.MIME_TEXT)) + }) + }) +}) diff --git a/api/ocm/extensions/repositories/composition/gc_test.go b/api/ocm/extensions/repositories/composition/gc_test.go new file mode 100644 index 000000000..2d31eb54e --- /dev/null +++ b/api/ocm/extensions/repositories/composition/gc_test.go @@ -0,0 +1,31 @@ +package composition_test + +import ( + "runtime" + "time" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/ocm" + me "ocm.software/ocm/api/ocm/extensions/repositories/composition" + "ocm.software/ocm/api/utils/refmgmt" +) + +var _ = Describe("repository", func() { + It("finalizes with context", func() { + ctx := ocm.New(datacontext.MODE_EXTENDED) + + repo := me.NewRepository(ctx, "test") + MustBeSuccessful(repo.Close()) + + ctx = nil + runtime.GC() + time.Sleep(time.Second) + runtime.GC() + time.Sleep(time.Second) + Expect(refmgmt.ReferenceCount(repo)).To(Equal(0)) + }) +}) diff --git a/api/ocm/extensions/repositories/composition/repository.go b/api/ocm/extensions/repositories/composition/repository.go new file mode 100644 index 000000000..d5c22e9a9 --- /dev/null +++ b/api/ocm/extensions/repositories/composition/repository.go @@ -0,0 +1,205 @@ +package composition + +import ( + "fmt" + "reflect" + "sync" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/general" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/repositories/virtual" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + common "ocm.software/ocm/api/utils/misc" +) + +//////////////////////////////////////////////////////////////////////////////// + +func NewRepository(ctxp cpi.ContextProvider, names ...string) cpi.Repository { + var repositories *Repositories + + ctx := datacontext.InternalContextRef(ctxp.OCMContext()) + name := general.Optional(names...) + if name != "" { + repositories = ctx.GetAttributes().GetOrCreateAttribute(ATTR_REPOS, newRepositories).(*Repositories) + if repo := repositories.GetRepository(name); repo != nil { + repo, _ = repo.Dup() + return repo + } + } + repo := virtual.NewRepository(ctx, NewAccess(name)) + if repositories != nil { + repositories.SetRepository(name, repo) + repo, _ = repo.Dup() + } + return repo +} + +type Index = virtual.Index[common.NameVersion] + +type Access struct { + lock sync.Mutex + name string + index *Index + blobs map[string]blobaccess.BlobAccess + readonly bool +} + +var _ virtual.Access = (*Access)(nil) + +func NewAccess(name string) *Access { + return &Access{ + name: name, + index: virtual.NewIndex[common.NameVersion](), + blobs: map[string]blobaccess.BlobAccess{}, + } +} + +func (a *Access) GetSpecification() cpi.RepositorySpec { + return NewRepositorySpec(a.name) +} + +func (a *Access) IsReadOnly() bool { + return a.readonly +} + +func (a *Access) SetReadOnly() { + a.readonly = true +} + +func (a *Access) ComponentLister() cpi.ComponentLister { + a.lock.Lock() + defer a.lock.Unlock() + + return a.index +} + +func (a *Access) ExistsComponentVersion(name string, version string) (bool, error) { + a.lock.Lock() + defer a.lock.Unlock() + + e := a.index.Get(name, version) + return e != nil, nil +} + +func (a *Access) ListVersions(comp string) ([]string, error) { + a.lock.Lock() + defer a.lock.Unlock() + + return a.index.GetVersions(comp), nil +} + +func (a *Access) GetComponentVersion(comp, version string) (virtual.VersionAccess, error) { + var cd *compdesc.ComponentDescriptor + + a.lock.Lock() + defer a.lock.Unlock() + + i := a.index.Get(comp, version) + if i == nil { + cd = compdesc.New(comp, version) + err := a.index.Add(cd, common.VersionedElementKey(cd)) + if err != nil { + return nil, err + } + } else { + cd = i.CD() + } + return &VersionAccess{a, cd.GetName(), cd.GetVersion(), a.IsReadOnly(), cd.Copy()}, nil +} + +func (a *Access) GetBlob(name string) (blobaccess.BlobAccess, error) { + a.lock.Lock() + defer a.lock.Unlock() + b := a.blobs[name] + if b == nil { + return nil, errors.ErrNotFound(blobaccess.KIND_BLOB, name) + } + return b.Dup() +} + +func (a *Access) AddBlob(blob blobaccess.BlobAccess) (string, error) { + digest := blob.Digest() + if digest == blobaccess.BLOB_UNKNOWN_DIGEST { + return "", fmt.Errorf("unknown digest") + } + a.lock.Lock() + defer a.lock.Unlock() + b := a.blobs[digest.Encoded()] + if b == nil { + b, err := blob.Dup() + if err != nil { + return "", err + } + a.blobs[digest.Encoded()] = b + } + return digest.Encoded(), nil +} + +func (a *Access) Close() error { + list := errors.ErrorList{} + for _, b := range a.blobs { + list.Add(b.Close()) + } + return list.Result() +} + +var _ virtual.Access = (*Access)(nil) + +type VersionAccess struct { + access *Access + comp string + vers string + readonly bool + desc *compdesc.ComponentDescriptor +} + +func (v *VersionAccess) GetDescriptor() *compdesc.ComponentDescriptor { + return v.desc +} + +func (v *VersionAccess) GetBlob(name string) (cpi.DataAccess, error) { + return v.access.GetBlob(name) +} + +func (v *VersionAccess) AddBlob(blob cpi.BlobAccess) (string, error) { + if v.readonly { + return "", accessio.ErrReadOnly + } + return v.access.AddBlob(blob) +} + +func (v *VersionAccess) Update() error { + v.access.lock.Lock() + defer v.access.lock.Unlock() + + if v.readonly { + return accessio.ErrReadOnly + } + if v.desc.GetName() != v.comp || v.desc.GetVersion() != v.vers { + return errors.ErrInvalid(cpi.KIND_COMPONENTVERSION, common.VersionedElementKey(v.desc).String()) + } + i := v.access.index.Get(v.comp, v.vers) + if !reflect.DeepEqual(v.desc, i.CD()) { + v.access.index.Set(v.desc, i.Info()) + } + return nil +} + +func (v *VersionAccess) Close() error { + return v.Update() +} + +func (v *VersionAccess) IsReadOnly() bool { + return v.readonly +} + +func (v *VersionAccess) SetReadOnly() { + v.readonly = true +} + +var _ virtual.VersionAccess = (*VersionAccess)(nil) diff --git a/api/ocm/extensions/repositories/composition/repository_test.go b/api/ocm/extensions/repositories/composition/repository_test.go new file mode 100644 index 000000000..449494747 --- /dev/null +++ b/api/ocm/extensions/repositories/composition/repository_test.go @@ -0,0 +1,143 @@ +package composition_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/mandelsoft/goutils/finalizer" + "github.com/mandelsoft/vfs/pkg/memoryfs" + + "ocm.software/ocm/api/helper/builder" + "ocm.software/ocm/api/helper/env" + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + me "ocm.software/ocm/api/ocm/extensions/repositories/composition" + ocmutils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/bpi" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/api/utils/refmgmt" +) + +const ( + COMPONENT = "acme.org/testcomp" + VERSION = "1.0.0" +) + +var _ = Describe("repository", func() { + ctx := ocm.DefaultContext() + + It("handles cvs", func() { + finalize := finalizer.Finalizer{} + defer Defer(finalize.Finalize) + + nested := finalize.Nested() + + repo := me.NewRepository(ctx) + finalize.Close(repo, "source repo") + + Expect(repo.GetSpecification().GetKind()).To(Equal(me.Type)) + + c := Must(repo.LookupComponent(COMPONENT)) + finalize.Close(c, "src comp") + + cv := Must(c.NewVersion(VERSION)) + nested.Close(cv, "src vers") + + cv.GetDescriptor().Provider.Name = "acme.org" + // wrap a non-closer access into a ref counting access to check cleanup + blob := bpi.NewBlobAccessForBase(blobaccess.ForString(mime.MIME_TEXT, "testdata")) + nested.Close(blob, "blob") + MustBeSuccessful(cv.SetResourceBlob(ocm.NewResourceMeta("test", resourcetypes.PLAIN_TEXT, metav1.LocalRelation), blob, "", nil)) + MustBeSuccessful(c.AddVersion(cv)) + + MustBeSuccessful(nested.Finalize()) + + cv = Must(c.LookupVersion(VERSION)) + finalize.Close(cv, "query") + rs := Must(cv.GetResourcesByName("test")) + Expect(len(rs)).To(Equal(1)) + data := Must(ocmutils.GetResourceData(rs[0])) + Expect(string(data)).To(Equal("testdata")) + }) + + It("supports env builder", func() { + env := builder.NewBuilder(env.FileSystem(memoryfs.New(), "")) + + env.OCMCompositionRepository("test", func() { + env.Component(COMPONENT, func() { + env.Version(VERSION, func() { + env.Provider("acme.org") + env.Resource("text", VERSION, "special", metav1.LocalRelation, func() { + env.BlobStringData(mime.MIME_TEXT, "testdata") + }) + }) + }) + }) + + var finalize finalizer.Finalizer + defer Defer(finalize.Finalize, "final") + + repo := me.NewRepository(env, "test") + finalize.Close(repo, "repo") + + Expect(refmgmt.ReferenceCount(repo)).To(Equal(2)) + + cv := Must(repo.LookupComponentVersion(COMPONENT, VERSION)) + finalize.Close(cv, "vers") + + res := Must(cv.GetResource(metav1.NewIdentity("text"))) + + data := Must(ocmutils.GetResourceData(res)) + Expect(string(data)).To(Equal("testdata")) + Expect(refmgmt.ReferenceCount(repo)).To(Equal(3)) + + MustBeSuccessful(finalize.Finalize()) + Expect(refmgmt.ReferenceCount(repo)).To(Equal(1)) + }) + + It("readonly mode on repo", func() { + env := builder.NewBuilder(env.FileSystem(memoryfs.New(), "")) + + env.OCMCompositionRepository("test", func() { + env.Component(COMPONENT, func() { + env.Version(VERSION, func() { + env.Provider("acme.org") + env.Resource("text", VERSION, "special", metav1.LocalRelation, func() { + env.BlobStringData(mime.MIME_TEXT, "testdata") + }) + }) + }) + }) + + var finalize finalizer.Finalizer + defer Defer(finalize.Finalize, "final") + + sess := ocm.NewSession(nil) + repo := me.NewRepository(env, "test") + sess.AddCloser(repo) + finalize.Close(sess, "repo") + + repo.SetReadOnly() + Expect(repo.IsReadOnly()).To(BeTrue()) + + cv := Must(repo.LookupComponentVersion(COMPONENT, VERSION)) + cl := accessio.OnceCloser(cv) + sess.AddCloser(cl) + + Expect(cv.IsReadOnly()).To(BeTrue()) + + cv.GetDescriptor().Provider.Name = "acme.org" + ExpectError(cl.Close()).To(MatchError(accessobj.ErrReadOnly)) + }) + + It("provides early error", func() { + repo := me.NewRepository(ctx) + cv := me.NewComponentVersion(ctx, "a", "1.0") + ExpectError(repo.AddComponentVersion(cv)).To(MatchError("component.name: Does not match pattern '^[a-z][-a-z0-9]*([.][a-z][-a-z0-9]*)*[.][a-z]{2,}(/[a-z][-a-z0-9_]*([.][a-z][-a-z0-9_]*)*)+$'")) + }) +}) diff --git a/pkg/contexts/ocm/repositories/composition/suite_test.go b/api/ocm/extensions/repositories/composition/suite_test.go similarity index 100% rename from pkg/contexts/ocm/repositories/composition/suite_test.go rename to api/ocm/extensions/repositories/composition/suite_test.go diff --git a/api/ocm/extensions/repositories/composition/type.go b/api/ocm/extensions/repositories/composition/type.go new file mode 100644 index 000000000..f636ebc76 --- /dev/null +++ b/api/ocm/extensions/repositories/composition/type.go @@ -0,0 +1,42 @@ +package composition + +import ( + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/internal" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + Type = "Composition" + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +func init() { + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](Type, nil)) + cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1, nil)) +} + +type RepositorySpec struct { + runtime.ObjectVersionedTypedObject + Name string `json:"name"` +} + +var _ cpi.RepositorySpec = (*RepositorySpec)(nil) + +func NewRepositorySpec(name string) *RepositorySpec { + return &RepositorySpec{ + ObjectVersionedTypedObject: runtime.NewVersionedTypedObject(Type), + Name: name, + } +} + +func (r RepositorySpec) AsUniformSpec(context internal.Context) *cpi.UniformRepositorySpec { + return nil +} + +func (r *RepositorySpec) Repository(ctx cpi.Context, credentials credentials.Credentials) (internal.Repository, error) { + return NewRepository(ctx, r.Name), nil +} + +var _ cpi.RepositorySpec = (*RepositorySpec)(nil) diff --git a/api/ocm/extensions/repositories/composition/version.go b/api/ocm/extensions/repositories/composition/version.go new file mode 100644 index 000000000..4ac4f0e97 --- /dev/null +++ b/api/ocm/extensions/repositories/composition/version.go @@ -0,0 +1,24 @@ +package composition + +import ( + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils/refmgmt" +) + +func NewComponentVersion(ctx cpi.ContextProvider, name, vers string) cpi.ComponentVersionAccess { + repo := NewRepository(ctx) + defer repo.Close() + if !refmgmt.Lazy(repo) { + panic("wrong composition repo implementation") + } + c, err := repo.LookupComponent(name) + if err != nil { + panic("wrong composition repo implementation: " + err.Error()) + } + defer c.Close() + cv, err := c.NewVersion(vers) + if err != nil { + panic("wrong composition repo implementation: " + err.Error()) + } + return cv +} diff --git a/api/ocm/extensions/repositories/composition/version_test.go b/api/ocm/extensions/repositories/composition/version_test.go new file mode 100644 index 000000000..44b30fffd --- /dev/null +++ b/api/ocm/extensions/repositories/composition/version_test.go @@ -0,0 +1,73 @@ +package composition_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/mandelsoft/goutils/finalizer" + + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + me "ocm.software/ocm/api/ocm/extensions/repositories/composition" + ocmutils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/bpi" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/api/utils/refmgmt" +) + +var _ = Describe("version", func() { + ctx := ocm.DefaultContext() + + It("handles anonymous version", func() { + finalize := finalizer.Finalizer{} + defer Defer(finalize.Finalize) + + nested := finalize.Nested() + + // compose new version + cv := me.NewComponentVersion(ctx, COMPONENT, VERSION) + cv.GetDescriptor().Provider.Name = "acme.org" + finalize.Close(cv, "composed version") + + // wrap a non-closer access into a ref counting access to check cleanup + blob := bpi.NewBlobAccessForBase(blobaccess.ForString(mime.MIME_TEXT, "testdata")) + nested.Close(blob, "blob") + MustBeSuccessful(cv.SetResourceBlob(ocm.NewResourceMeta("test", resourcetypes.PLAIN_TEXT, metav1.LocalRelation), blob, "", nil)) + + // add version to repository + repo1 := me.NewRepository(ctx) + finalize.Close(repo1, "target repo1") + c := Must(repo1.LookupComponent(COMPONENT)) + finalize.Close(c, "src comp") + MustBeSuccessful(c.AddVersion(cv)) + MustBeSuccessful(nested.Finalize()) + + // check result + cv = Must(c.LookupVersion(VERSION)) + Expect(refmgmt.ReferenceCount(cv)).To(Equal(1)) + nested.Close(cv, "query") + rs := Must(cv.GetResourcesByName("test")) + Expect(len(rs)).To(Equal(1)) + data := Must(ocmutils.GetResourceData(rs[0])) + Expect(string(data)).To(Equal("testdata")) + Expect(refmgmt.ReferenceCount(cv)).To(Equal(1)) + + // add this version again + repo2 := me.NewRepository(ctx) + finalize.Close(repo2, "target repo2") + MustBeSuccessful(repo2.AddComponentVersion(cv)) + Expect(refmgmt.ReferenceCount(cv)).To(Equal(1)) + MustBeSuccessful(nested.Finalize()) + + // check result + cv = Must(repo2.LookupComponentVersion(COMPONENT, VERSION)) + finalize.Close(cv, "query") + rs = Must(cv.GetResourcesByName("test")) + Expect(len(rs)).To(Equal(1)) + data = Must(ocmutils.GetResourceData(rs[0])) + Expect(string(data)).To(Equal("testdata")) + }) +}) diff --git a/pkg/contexts/ocm/repositories/ctf/README.md b/api/ocm/extensions/repositories/ctf/README.md similarity index 100% rename from pkg/contexts/ocm/repositories/ctf/README.md rename to api/ocm/extensions/repositories/ctf/README.md diff --git a/api/ocm/extensions/repositories/ctf/ctf_test.go b/api/ocm/extensions/repositories/ctf/ctf_test.go new file mode 100644 index 000000000..e91ae5996 --- /dev/null +++ b/api/ocm/extensions/repositories/ctf/ctf_test.go @@ -0,0 +1,85 @@ +package ctf_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/mandelsoft/vfs/pkg/memoryfs" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/repositories/comparch" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessio" +) + +var _ = Describe("access method", func() { + var fs vfs.FileSystem + var ctx ocm.Context + + BeforeEach(func() { + ctx = ocm.New(datacontext.MODE_EXTENDED) + fs = memoryfs.New() + vfsattr.Set(ctx.AttributesContext(), fs) + }) + + Context("create", func() { + It("create ctf", func() { + MustBeSuccessful(fs.Mkdir("test", 0o700)) + + spec := Must(ocm.ParseRepoToSpec(ctx, "+ctf::test/repository")) + Expect(ctf.NewRepositorySpec(ctf.ACC_CREATE, "test/repository", accessio.FormatDirectory, accessio.PathFileSystem(fs))).To(DeepEqual(spec)) + }) + + It("create directory", func() { + MustBeSuccessful(fs.Mkdir("test", 0o700)) + + spec := Must(ocm.ParseRepoToSpec(ctx, "+directory::test/repository")) + Expect(ctf.NewRepositorySpec(ctf.ACC_CREATE, "test/repository", accessio.FormatDirectory, accessio.PathFileSystem(fs))).To(DeepEqual(spec)) + }) + + It("create tgz", func() { + MustBeSuccessful(fs.Mkdir("test", 0o700)) + + spec := Must(ocm.ParseRepoToSpec(ctx, "+tgz::test/repository")) + Expect(ctf.NewRepositorySpec(ctf.ACC_CREATE, "test/repository", accessio.FormatTGZ, accessio.PathFileSystem(fs))).To(DeepEqual(spec)) + }) + + It("create ca", func() { + MustBeSuccessful(fs.Mkdir("test", 0o700)) + + spec := Must(ocm.ParseRepoToSpec(ctx, "+ca::test/repository")) + Expect(comparch.NewRepositorySpec(ctf.ACC_CREATE, "test/repository", accessio.FormatDirectory, accessio.PathFileSystem(fs))).To(DeepEqual(spec)) + }) + }) + + Context("read", func() { + It("read ctf", func() { + ExpectError(ocm.ParseRepoToSpec(ctx, "test/repository")).To(MatchError(`repository specification "test/repository" is invalid: repository "test/repository" is unknown`)) + }) + + It("read ctf", func() { + MustBeSuccessful(fs.Mkdir("test", 0o700)) + + spec := Must(ocm.ParseRepoToSpec(ctx, "ctf::test/repository")) + Expect(ctf.NewRepositorySpec(ctf.ACC_WRITABLE, "test/repository", accessio.PathFileSystem(fs))).To(DeepEqual(spec)) + }) + + It("read ctf", func() { + MustBeSuccessful(fs.Mkdir("test", 0o700)) + + spec := Must(ocm.ParseRepoToSpec(ctx, "tgz::test/repository")) + Expect(ctf.NewRepositorySpec(ctf.ACC_WRITABLE, "test/repository", accessio.PathFileSystem(fs))).To(DeepEqual(spec)) + }) + + It("read ca", func() { + MustBeSuccessful(fs.Mkdir("test", 0o700)) + + spec := Must(ocm.ParseRepoToSpec(ctx, "ca::test/repository")) + Expect(comparch.NewRepositorySpec(ctf.ACC_WRITABLE, "test/repository", accessio.PathFileSystem(fs))).To(DeepEqual(spec)) + }) + }) +}) diff --git a/api/ocm/extensions/repositories/ctf/format.go b/api/ocm/extensions/repositories/ctf/format.go new file mode 100644 index 000000000..929e42877 --- /dev/null +++ b/api/ocm/extensions/repositories/ctf/format.go @@ -0,0 +1,87 @@ +package ctf + +import ( + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/oci/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/repositories/genericocireg" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" +) + +var ( + FormatDirectory = ctf.FormatDirectory + FormatTAR = ctf.FormatTAR + FormatTGZ = ctf.FormatTGZ +) + +type Object = ctf.Object + +type FormatHandler = ctf.FormatHandler + +//////////////////////////////////////////////////////////////////////////////// + +func GetFormats() []string { + return ctf.GetFormats() +} + +func GetFormat(name accessio.FileFormat) FormatHandler { + return ctf.GetFormat(name) +} + +//////////////////////////////////////////////////////////////////////////////// + +const ( + ACC_CREATE = accessobj.ACC_CREATE + ACC_WRITABLE = accessobj.ACC_WRITABLE + ACC_READONLY = accessobj.ACC_READONLY +) + +func Open(ctx cpi.ContextProvider, acc accessobj.AccessMode, path string, mode vfs.FileMode, opts ...accessio.Option) (cpi.Repository, error) { + r, err := ctf.Open(cpi.FromProvider(ctx), acc, path, mode, opts...) + if err != nil { + return nil, err + } + return genericocireg.NewRepository(cpi.FromProvider(ctx), nil, r), nil +} + +func Create(ctx cpi.ContextProvider, acc accessobj.AccessMode, path string, mode vfs.FileMode, opts ...accessio.Option) (cpi.Repository, error) { + r, err := ctf.Create(cpi.FromProvider(ctx), acc, path, mode, opts...) + if err != nil { + return nil, err + } + return genericocireg.NewRepository(cpi.FromProvider(ctx), nil, r), nil +} + +//////////////////////////////////////////////////////////////////////////////// + +type CTFOptions struct { + genericocireg.ComponentRepositoryMeta +} + +type CTFOption interface { + accessio.Option + ApplyCTFOption(opts *CTFOptions) +} + +// RepositoryPrefix set the OCI repository prefix used to store the component +// versions. +func RepositoryPrefix(path string) accessio.Option { + return &optPrefix{path} +} + +type optPrefix struct { + prefix string +} + +var _ CTFOption = (*optPrefix)(nil) + +// ApplyOption does nothing, because this is no standard option. +func (o *optPrefix) ApplyOption(options accessio.Options) error { + return nil +} + +func (o *optPrefix) ApplyCTFOption(opts *CTFOptions) { + opts.SubPath = o.prefix +} diff --git a/api/ocm/extensions/repositories/ctf/repo_test.go b/api/ocm/extensions/repositories/ctf/repo_test.go new file mode 100644 index 000000000..1687970b9 --- /dev/null +++ b/api/ocm/extensions/repositories/ctf/repo_test.go @@ -0,0 +1,247 @@ +package ctf_test + +import ( + "bytes" + + . "github.com/mandelsoft/goutils/finalizer" + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/ocm/testhelper" + + "github.com/mandelsoft/logging" + "github.com/mandelsoft/vfs/pkg/memoryfs" + "github.com/mandelsoft/vfs/pkg/vfs" + "github.com/tonglil/buflogr" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/extensions/repositories/genericocireg" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess" + ocmlog "ocm.software/ocm/api/utils/logging" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/api/utils/refmgmt" +) + +const ( + COMPONENT = "github.com/mandelsoft/ocm" + VERSION = "1.0.0" +) + +var _ = Describe("access method", func() { + var fs vfs.FileSystem + ctx := ocm.DefaultContext() + + BeforeEach(func() { + fs = memoryfs.New() + }) + + It("adds naked component version and later lookup", func() { + final := Finalizer{} + defer Defer(final.Finalize) + + a := Must(ctf.Create(ctx, accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, "ctf", 0o700, accessio.PathFileSystem(fs))) + final.Close(a, "repository") + c := Must(a.LookupComponent(COMPONENT)) + final.Close(c, "component") + + cv := Must(c.NewVersion(VERSION)) + final.Close(cv, "version") + + MustBeSuccessful(c.AddVersion(cv)) + MustBeSuccessful(final.Finalize()) + + refmgmt.AllocLog.Trace("opening ctf") + a = Must(ctf.Open(ctx, accessobj.ACC_READONLY, "ctf", 0o700, accessio.PathFileSystem(fs))) + final.Close(a) + + refmgmt.AllocLog.Trace("lookup component") + c = Must(a.LookupComponent(COMPONENT)) + final.Close(c) + + refmgmt.AllocLog.Trace("lookup version") + cv = Must(c.LookupVersion(VERSION)) + final.Close(cv) + + refmgmt.AllocLog.Trace("closing") + MustBeSuccessful(final.Finalize()) + }) + + It("adds naked component version and later shortcut lookup", func() { + final := Finalizer{} + defer Defer(final.Finalize) + + a := Must(ctf.Create(ctx, accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, "ctf", 0o700, accessio.PathFileSystem(fs))) + final.Close(a, "repository") + c := Must(a.LookupComponent(COMPONENT)) + final.Close(c, "component") + + cv := Must(c.NewVersion(VERSION)) + final.Close(cv, "version") + + MustBeSuccessful(c.AddVersion(cv)) + MustBeSuccessful(final.Finalize()) + + refmgmt.AllocLog.Trace("opening ctf") + a = Must(ctf.Open(ctx, accessobj.ACC_READONLY, "ctf", 0o700, accessio.PathFileSystem(fs))) + final.Close(a) + + refmgmt.AllocLog.Trace("lookup component version") + cv = Must(a.LookupComponentVersion(COMPONENT, VERSION)) + final.Close(cv) + + refmgmt.AllocLog.Trace("closing") + MustBeSuccessful(final.Finalize()) + }) + + It("adds component version", func() { + final := Finalizer{} + defer Defer(final.Finalize) + + a := Must(ctf.Create(ctx, accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, "ctf", 0o700, accessio.PathFileSystem(fs))) + final.Close(a) + c := Must(a.LookupComponent(COMPONENT)) + final.Close(c) + + cv := Must(c.NewVersion(VERSION)) + final.Close(cv) + + // add resource + MustBeSuccessful(cv.SetResourceBlob(compdesc.NewResourceMeta("text1", resourcetypes.PLAIN_TEXT, metav1.LocalRelation), blobaccess.ForString(mime.MIME_TEXT, S_TESTDATA), "", nil)) + Expect(Must(cv.GetResource(compdesc.NewIdentity("text1"))).Meta().Digest).To(Equal(DS_TESTDATA)) + + // add resource with digest + meta := compdesc.NewResourceMeta("text2", resourcetypes.PLAIN_TEXT, metav1.LocalRelation) + meta.SetDigest(DS_TESTDATA) + MustBeSuccessful(cv.SetResourceBlob(meta, blobaccess.ForString(mime.MIME_TEXT, S_TESTDATA), "", nil)) + Expect(Must(cv.GetResource(compdesc.NewIdentity("text2"))).Meta().Digest).To(Equal(DS_TESTDATA)) + + // reject resource with wrong digest + meta = compdesc.NewResourceMeta("text3", resourcetypes.PLAIN_TEXT, metav1.LocalRelation) + meta.SetDigest(TextResourceDigestSpec("fake")) + Expect(cv.SetResourceBlob(meta, blobaccess.ForString(mime.MIME_TEXT, S_TESTDATA), "", nil)).To(MatchError("unable to set resource: digest mismatch: " + D_TESTDATA + " != fake")) + + MustBeSuccessful(c.AddVersion(cv)) + MustBeSuccessful(final.Finalize()) + + a = Must(ctf.Open(ctx, accessobj.ACC_READONLY, "ctf", 0o700, accessio.PathFileSystem(fs))) + final.Close(a) + + cv = Must(a.LookupComponentVersion(COMPONENT, VERSION)) + final.Close(cv) + }) + + It("adds omits unadded new component version", func() { + final := Finalizer{} + defer Defer(final.Finalize) + + a := Must(ctf.Create(ctx, accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, "ctf", 0o700, accessio.PathFileSystem(fs))) + final.Close(a) + c := Must(a.LookupComponent(COMPONENT)) + final.Close(c) + + cv := Must(c.NewVersion(VERSION)) + final.Close(cv) + + MustBeSuccessful(final.Finalize()) + + a = Must(ctf.Open(ctx, accessobj.ACC_READONLY, "ctf", 0o700, accessio.PathFileSystem(fs))) + final.Close(a) + + _, err := a.LookupComponentVersion(COMPONENT, VERSION) + + Expect(err).To(MatchError(ContainSubstring("component version \"github.com/mandelsoft/ocm:1.0.0\" not found: oci artifact \"1.0.0\" not found in component-descriptors/github.com/mandelsoft/ocm"))) + }) + + It("provides error for invalid bloc access", func() { + final := Finalizer{} + defer Defer(final.Finalize) + + a := Must(ctf.Create(ctx, accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, "ctf", 0o700, accessio.PathFileSystem(fs))) + final.Close(a) + c := Must(a.LookupComponent(COMPONENT)) + final.Close(c) + + cv := Must(c.NewVersion(VERSION)) + final.Close(cv) + + // add resource + Expect(ErrorFrom((cv.SetResourceBlob(compdesc.NewResourceMeta("text1", resourcetypes.PLAIN_TEXT, metav1.LocalRelation), blobaccess.ForFile(mime.MIME_TEXT, "non-existing-file"), "", nil)))).To(MatchError(`file "non-existing-file" not found`)) + + MustBeSuccessful(final.Finalize()) + }) + + It("logs diff", func() { + r := Must(ctf.Open(ctx, ctf.ACC_CREATE, "test.ctf", 0o700, accessio.FormatDirectory, accessio.PathFileSystem(fs))) + defer Close(r, "repo") + + c := Must(r.LookupComponent("acme.org/test")) + defer Close(c, "comp") + + cv := Must(c.NewVersion("v1")) + + ocmlog.PushContext(nil) + ocmlog.Context().AddRule(logging.NewConditionRule(logging.DebugLevel, genericocireg.TAG_CDDIFF)) + var buf bytes.Buffer + def := buflogr.NewWithBuffer(&buf) + ocmlog.Context().SetBaseLogger(def) + defer ocmlog.Context().ResetRules() + defer ocmlog.PopContext() + + MustBeSuccessful(c.AddVersion(cv)) + MustBeSuccessful(cv.Close()) + + cv = Must(c.LookupVersion("v1")) + cv.GetDescriptor().Provider.Name = "acme.org" + MustBeSuccessful(cv.Close()) + Expect("\n" + buf.String()).To(Equal(` +V[4] component descriptor has been changed realm ocm realm ocm/oci/mapping diff [ComponentSpec.ObjectMeta.Provider.Name: acme != acme.org] +V[4] component descriptor has been changed realm ocm realm ocm/oci/mapping diff [ComponentSpec.ObjectMeta.Provider.Name: acme != acme.org] +`)) + }) + + It("handles readonly mode", func() { + r := Must(ctf.Open(ctx, ctf.ACC_CREATE, "test.ctf", 0o700, accessio.FormatDirectory, accessio.PathFileSystem(fs))) + defer Close(r, "repo") + + c := Must(r.LookupComponent("acme.org/test")) + defer Close(c, "comp") + + cv := Must(c.NewVersion("v1")) + + MustBeSuccessful(c.AddVersion(cv)) + MustBeSuccessful(cv.Close()) + + cv = Must(c.LookupVersion("v1")) + cv.SetReadOnly() + Expect(cv.IsReadOnly()).To(BeTrue()) + cv.GetDescriptor().Provider.Name = "acme.org" + ExpectError(cv.Close()).To(MatchError(accessio.ErrReadOnly)) + }) + + It("handles readonly mode on repo", func() { + r := Must(ctf.Open(ctx, ctf.ACC_CREATE, "test.ctf", 0o700, accessio.FormatDirectory, accessio.PathFileSystem(fs))) + defer Close(r, "repo") + + c := Must(r.LookupComponent("acme.org/test")) + defer Close(c, "comp") + + cv := Must(c.NewVersion("v1")) + + MustBeSuccessful(c.AddVersion(cv)) + MustBeSuccessful(cv.Close()) + + r.SetReadOnly() + cv = Must(c.LookupVersion("v1")) + Expect(cv.IsReadOnly()).To(BeTrue()) + cv.GetDescriptor().Provider.Name = "acme.org" + ExpectError(cv.Close()).To(MatchError(accessio.ErrReadOnly)) + + ExpectError(c.NewVersion("v2")).To(MatchError(accessio.ErrReadOnly)) + }) +}) diff --git a/pkg/contexts/ocm/repositories/ctf/suite_test.go b/api/ocm/extensions/repositories/ctf/suite_test.go similarity index 100% rename from pkg/contexts/ocm/repositories/ctf/suite_test.go rename to api/ocm/extensions/repositories/ctf/suite_test.go diff --git a/api/ocm/extensions/repositories/ctf/type.go b/api/ocm/extensions/repositories/ctf/type.go new file mode 100644 index 000000000..6299fa23c --- /dev/null +++ b/api/ocm/extensions/repositories/ctf/type.go @@ -0,0 +1,18 @@ +package ctf + +import ( + "ocm.software/ocm/api/oci/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/extensions/repositories/genericocireg" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" +) + +const Type = ctf.Type + +func NewRepositorySpec(acc accessobj.AccessMode, path string, opts ...accessio.Option) (*genericocireg.RepositorySpec, error) { + spec, err := ctf.NewRepositorySpec(acc, path, opts...) + if err != nil { + return nil, err + } + return genericocireg.NewRepositorySpec(spec, nil), nil +} diff --git a/api/ocm/extensions/repositories/ctf/uniform.go b/api/ocm/extensions/repositories/ctf/uniform.go new file mode 100644 index 000000000..64f1d3a7e --- /dev/null +++ b/api/ocm/extensions/repositories/ctf/uniform.go @@ -0,0 +1,44 @@ +package ctf + +import ( + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/repositories/genericocireg" + "ocm.software/ocm/api/utils/accessio" +) + +func SupportedFormats() []accessio.FileFormat { + return ctf.SupportedFormats() +} + +func init() { + h := &repospechandler{} + cpi.RegisterRepositorySpecHandler(h, "") + cpi.RegisterRepositorySpecHandler(h, ctf.Type) + cpi.RegisterRepositorySpecHandler(h, ctf.AltType) + for _, f := range SupportedFormats() { + cpi.RegisterRepositorySpecHandler(h, string(f)) + } +} + +type repospechandler struct{} + +func (h *repospechandler) MapReference(ctx cpi.Context, u *cpi.UniformRepositorySpec) (cpi.RepositorySpec, error) { + if u.Info == "" { + if u.Host == "" || u.Type == "" { + return nil, nil + } + } + spec, err := ctf.MapReference(ctx.OCIContext(), &oci.UniformRepositorySpec{ + Type: u.Type, + Host: u.Host, + Info: u.Info, + CreateIfMissing: u.CreateIfMissing, + TypeHint: u.TypeHint, + }) + if err != nil || spec == nil { + return nil, err + } + return genericocireg.NewRepositorySpec(spec, nil), nil +} diff --git a/api/ocm/extensions/repositories/genericocireg/accessmethod_localblob.go b/api/ocm/extensions/repositories/genericocireg/accessmethod_localblob.go new file mode 100644 index 000000000..94a0a8596 --- /dev/null +++ b/api/ocm/extensions/repositories/genericocireg/accessmethod_localblob.go @@ -0,0 +1,113 @@ +package genericocireg + +import ( + "io" + "sync" + + "github.com/mandelsoft/goutils/errors" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/refmgmt" +) + +type localBlobAccessMethod struct { + lock sync.Mutex + err error + data blobaccess.DataAccess + spec *localblob.AccessSpec + namespace oci.NamespaceAccess + artifact oci.ArtifactAccess +} + +var _ accspeccpi.AccessMethodImpl = (*localBlobAccessMethod)(nil) + +func newLocalBlobAccessMethod(a *localblob.AccessSpec, ns oci.NamespaceAccess, art oci.ArtifactAccess, ref refmgmt.ExtendedAllocatable) (accspeccpi.AccessMethod, error) { + return accspeccpi.AccessMethodForImplementation(newLocalBlobAccessMethodImpl(a, ns, art, ref)) +} + +func newLocalBlobAccessMethodImpl(a *localblob.AccessSpec, ns oci.NamespaceAccess, art oci.ArtifactAccess, ref refmgmt.ExtendedAllocatable) (*localBlobAccessMethod, error) { + m := &localBlobAccessMethod{ + spec: a, + namespace: ns, + artifact: art, + } + ref.BeforeCleanup(refmgmt.CleanupHandlerFunc(m.cache)) + return m, nil +} + +func (m *localBlobAccessMethod) cache() { + if m.artifact != nil { + _, m.err = m.getBlob() + } +} + +func (_ *localBlobAccessMethod) IsLocal() bool { + return true +} + +func (m *localBlobAccessMethod) GetKind() string { + return m.spec.GetKind() +} + +func (m *localBlobAccessMethod) AccessSpec() accspeccpi.AccessSpec { + return m.spec +} + +func (m *localBlobAccessMethod) Close() error { + m.lock.Lock() + defer m.lock.Unlock() + + m.artifact = nil + m.namespace = nil + if m.data != nil { + tmp := m.data + m.data = nil + return tmp.Close() + } + return nil +} + +func (m *localBlobAccessMethod) getBlob() (blobaccess.DataAccess, error) { + m.lock.Lock() + defer m.lock.Unlock() + + if m.data != nil { + return m.data, nil + } + if artdesc.IsOCIMediaType(m.spec.MediaType) { + // may be we should always store the blob, additionally to the + // exploded form to make things easier. + + if m.spec.LocalReference == "" { + // TODO: synthesize the artifact blob + return nil, errors.ErrNotImplemented("artifact blob synthesis") + } + } + _, data, err := m.namespace.GetBlobData(digest.Digest(m.spec.LocalReference)) + if err != nil { + return nil, err + } + m.data = data + return m.data, err +} + +func (m *localBlobAccessMethod) Reader() (io.ReadCloser, error) { + blob, err := m.getBlob() + if err != nil { + return nil, err + } + return blob.Reader() +} + +func (m *localBlobAccessMethod) Get() ([]byte, error) { + return blobaccess.BlobData(m.getBlob()) +} + +func (m *localBlobAccessMethod) MimeType() string { + return m.spec.MediaType +} diff --git a/pkg/contexts/ocm/repositories/genericocireg/accessmethod_localoclblob.go b/api/ocm/extensions/repositories/genericocireg/accessmethod_localoclblob.go similarity index 75% rename from pkg/contexts/ocm/repositories/genericocireg/accessmethod_localoclblob.go rename to api/ocm/extensions/repositories/genericocireg/accessmethod_localoclblob.go index 15a57690a..811bd6765 100644 --- a/pkg/contexts/ocm/repositories/genericocireg/accessmethod_localoclblob.go +++ b/api/ocm/extensions/repositories/genericocireg/accessmethod_localoclblob.go @@ -3,10 +3,10 @@ package genericocireg import ( "github.com/opencontainers/go-digest" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/refmgmt" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/utils/refmgmt" ) type localOCIBlobAccessMethod struct { diff --git a/api/ocm/extensions/repositories/genericocireg/component.go b/api/ocm/extensions/repositories/genericocireg/component.go new file mode 100644 index 000000000..5d96b32db --- /dev/null +++ b/api/ocm/extensions/repositories/genericocireg/component.go @@ -0,0 +1,173 @@ +package genericocireg + +import ( + "strings" + + "github.com/Masterminds/semver/v3" + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/general" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/cpi/repocpi" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/refmgmt" +) + +const META_SEPARATOR = ".build-" + +//////////////////////////////////////////////////////////////////////////////// + +type componentAccessImpl struct { + bridge repocpi.ComponentAccessBridge + repo *RepositoryImpl + name string + namespace oci.NamespaceAccess +} + +func newComponentAccess(repo *RepositoryImpl, name string, main bool) (*repocpi.ComponentAccessInfo, error) { + mapped, err := repo.MapComponentNameToNamespace(name) + if err != nil { + return nil, err + } + namespace, err := repo.ocirepo.LookupNamespace(mapped) + if err != nil { + return nil, err + } + impl := &componentAccessImpl{ + repo: repo, + name: name, + namespace: namespace, + } + return &repocpi.ComponentAccessInfo{impl, "OCM component[OCI]", main}, nil +} + +func (c *componentAccessImpl) Close() error { + refmgmt.AllocLog.Trace("closing component [OCI]", "name", c.name) + err := c.namespace.Close() + refmgmt.AllocLog.Trace("closed component [OCI]", "name", c.name) + return err +} + +func (c *componentAccessImpl) SetBridge(base repocpi.ComponentAccessBridge) { + c.bridge = base +} + +func (c *componentAccessImpl) GetParentBridge() repocpi.RepositoryViewManager { + return c.repo.bridge +} + +func (c *componentAccessImpl) GetContext() cpi.Context { + return c.repo.GetContext() +} + +func (c *componentAccessImpl) GetName() string { + return c.name +} + +//////////////////////////////////////////////////////////////////////////////// + +func toTag(v string) (string, error) { + _, err := semver.NewVersion(v) + if err != nil { + return "", err + } + return strings.ReplaceAll(v, "+", META_SEPARATOR), nil +} + +func toVersion(t string) string { + next := 0 + for { + if idx := strings.Index(t[next:], META_SEPARATOR); idx >= 0 { + next += idx + len(META_SEPARATOR) + } else { + break + } + } + if next == 0 { + return t + } + return t[:next-len(META_SEPARATOR)] + "+" + t[next:] +} + +func (c *componentAccessImpl) IsReadOnly() bool { + return c.repo.IsReadOnly() +} + +func (c *componentAccessImpl) ListVersions() ([]string, error) { + tags, err := c.namespace.ListTags() + if err != nil { + return nil, err + } + result := make([]string, 0, len(tags)) + for _, t := range tags { + // omit reported digests (typically for ctf) + if ok, _ := artdesc.IsDigest(t); !ok { + result = append(result, toVersion(t)) + } + } + return result, err +} + +func (c *componentAccessImpl) HasVersion(vers string) (bool, error) { + tags, err := c.namespace.ListTags() + if err != nil { + return false, err + } + for _, t := range tags { + // omit reported digests (typically for ctf) + if ok, _ := artdesc.IsDigest(t); !ok { + if vers == t { + return true, nil + } + } + } + return false, err +} + +func (c *componentAccessImpl) LookupVersion(version string) (*repocpi.ComponentVersionAccessInfo, error) { + tag, err := toTag(version) + if err != nil { + return nil, err + } + acc, err := c.namespace.GetArtifact(tag) + if err != nil { + if errors.IsErrNotFound(err) { + return nil, cpi.ErrComponentVersionNotFoundWrap(err, c.name, version) + } + return nil, err + } + m := accessobj.ACC_WRITABLE + if c.IsReadOnly() { + m = accessobj.ACC_READONLY + } + return newComponentVersionAccess(m, c, version, acc, true) +} + +func (c *componentAccessImpl) NewVersion(version string, overrides ...bool) (*repocpi.ComponentVersionAccessInfo, error) { + if c.IsReadOnly() { + return nil, accessio.ErrReadOnly + } + override := general.Optional(overrides...) + tag, err := toTag(version) + if err != nil { + return nil, err + } + acc, err := c.namespace.GetArtifact(tag) + if err == nil { + if override { + return newComponentVersionAccess(accessobj.ACC_CREATE, c, version, acc, false) + } + return nil, errors.ErrAlreadyExists(cpi.KIND_COMPONENTVERSION, c.name+"/"+version) + } + if !errors.IsErrNotFoundKind(err, oci.KIND_OCIARTIFACT) { + return nil, err + } + acc, err = c.namespace.NewArtifact() + if err != nil { + return nil, err + } + return newComponentVersionAccess(accessobj.ACC_CREATE, c, version, acc, false) +} diff --git a/pkg/contexts/ocm/repositories/genericocireg/componentmapping/constants.go b/api/ocm/extensions/repositories/genericocireg/componentmapping/constants.go similarity index 97% rename from pkg/contexts/ocm/repositories/genericocireg/componentmapping/constants.go rename to api/ocm/extensions/repositories/genericocireg/componentmapping/constants.go index 31d237462..7938c6013 100644 --- a/pkg/contexts/ocm/repositories/genericocireg/componentmapping/constants.go +++ b/api/ocm/extensions/repositories/genericocireg/componentmapping/constants.go @@ -1,7 +1,7 @@ package componentmapping import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" + "ocm.software/ocm/api/ocm/compdesc" ) // ComponentDescriptorFileName is the filename of the component descriptor in a tar file used to store diff --git a/api/ocm/extensions/repositories/genericocireg/componentversion.go b/api/ocm/extensions/repositories/genericocireg/componentversion.go new file mode 100644 index 000000000..18317783e --- /dev/null +++ b/api/ocm/extensions/repositories/genericocireg/componentversion.go @@ -0,0 +1,316 @@ +package genericocireg + +import ( + "fmt" + "path" + "strings" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/set" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/cpi/repocpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localociblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + "ocm.software/ocm/api/ocm/extensions/accessmethods/relativeociref" + "ocm.software/ocm/api/ocm/extensions/attrs/compatattr" + ocihdlr "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/oci" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/errkind" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/refmgmt" +) + +// newComponentVersionAccess creates a component access for the artifact access, if this fails the artifact acess is closed. +func newComponentVersionAccess(mode accessobj.AccessMode, comp *componentAccessImpl, version string, access oci.ArtifactAccess, persistent bool) (*repocpi.ComponentVersionAccessInfo, error) { + c, err := newComponentVersionContainer(mode, comp, version, access) + if err != nil { + return nil, err + } + return &repocpi.ComponentVersionAccessInfo{c, true, persistent}, nil +} + +// ////////////////////////////////////////////////////////////////////////////// + +type ComponentVersionContainer struct { + bridge repocpi.ComponentVersionAccessBridge + + comp *componentAccessImpl + version string + access oci.ArtifactAccess + manifest oci.ManifestAccess + state accessobj.State +} + +var _ repocpi.ComponentVersionAccessImpl = (*ComponentVersionContainer)(nil) + +func newComponentVersionContainer(mode accessobj.AccessMode, comp *componentAccessImpl, version string, access oci.ArtifactAccess) (*ComponentVersionContainer, error) { + m := access.ManifestAccess() + if m == nil { + return nil, errors.ErrInvalid("artifact type") + } + state, err := NewState(mode, comp.name, version, m, compatattr.Get(comp.GetContext())) + if err != nil { + access.Close() + return nil, err + } + + return &ComponentVersionContainer{ + comp: comp, + version: version, + access: access, + manifest: m, + state: state, + }, nil +} + +func (c *ComponentVersionContainer) SetBridge(impl repocpi.ComponentVersionAccessBridge) { + c.bridge = impl +} + +func (c *ComponentVersionContainer) GetParentBridge() repocpi.ComponentAccessBridge { + return c.comp.bridge +} + +func (c *ComponentVersionContainer) Close() error { + if c.manifest == nil { + return accessio.ErrClosed + } + c.manifest = nil + return c.access.Close() +} + +func (c *ComponentVersionContainer) SetReadOnly() { + c.state.SetReadOnly() +} + +func (c *ComponentVersionContainer) Check() error { + if c.version != c.GetDescriptor().Version { + return errors.ErrInvalid("component version", c.GetDescriptor().Version) + } + if c.comp.name != c.GetDescriptor().Name { + return errors.ErrInvalid("component name", c.GetDescriptor().Name) + } + return nil +} + +func (c *ComponentVersionContainer) Repository() cpi.Repository { + return c.comp.repo.nonref +} + +func (c *ComponentVersionContainer) GetContext() cpi.Context { + return c.comp.GetContext() +} + +func (c *ComponentVersionContainer) IsReadOnly() bool { + return c.state.IsReadOnly() +} + +func (c *ComponentVersionContainer) IsClosed() bool { + return c.manifest == nil +} + +func (c *ComponentVersionContainer) AccessMethod(a cpi.AccessSpec, cv refmgmt.ExtendedAllocatable) (cpi.AccessMethod, error) { + accessSpec, err := c.comp.GetContext().AccessSpecForSpec(a) + if err != nil { + return nil, err + } + + switch a.GetKind() { + case localblob.Type: + return newLocalBlobAccessMethod(accessSpec.(*localblob.AccessSpec), c.comp.namespace, c.access, cv) + case localociblob.Type: + return newLocalOCIBlobAccessMethod(accessSpec.(*localblob.AccessSpec), c.comp.namespace, c.access, cv) + case relativeociref.Type: + m, err := ociartifact.NewMethod(c.GetContext(), a, accessSpec.(*relativeociref.AccessSpec).Reference, c.comp.repo.ocirepo) + if err == nil { + impl := accspeccpi.GetAccessMethodImplementation(m).(ociartifact.AccessMethodImpl) + cv.BeforeCleanup(refmgmt.CleanupHandlerFunc(impl.Cache)) + } + return m, err + } + + return nil, errors.ErrNotSupported(errkind.KIND_ACCESSMETHOD, a.GetType(), "oci registry") +} + +func (c *ComponentVersionContainer) SetDescriptor(cd *compdesc.ComponentDescriptor) error { + cur := c.GetDescriptor() + *cur = *cd + return c.Update() +} + +func (c *ComponentVersionContainer) Update() error { + logger := Logger(c.GetContext()).WithValues("cv", common.NewNameVersion(c.comp.name, c.version)) + err := c.Check() + if err != nil { + return fmt.Errorf("check failed: %w", err) + } + + if c.state.HasChanged() { + logger.Debug("update component version") + desc := c.GetDescriptor() + layers := set.Set[int]{} + for i := range c.manifest.GetDescriptor().Layers { + layers.Add(i) + } + for i, r := range desc.Resources { + s, l, err := c.evalLayer(r.Access) + if err != nil { + return fmt.Errorf("failed resource layer evaluation: %w", err) + } + if l > 0 { + layers.Delete(l) + } + if s != r.Access { + desc.Resources[i].Access = s + } + } + for i, r := range desc.Sources { + s, l, err := c.evalLayer(r.Access) + if err != nil { + return fmt.Errorf("failed source layer evaluation: %w", err) + } + if l > 0 { + layers.Delete(l) + } + if s != r.Access { + desc.Sources[i].Access = s + } + } + m := c.manifest.GetDescriptor() + i := len(m.Layers) - 1 + + for i > 0 { + if layers.Contains(i) { + logger.Debug("removing unused layer", "layer", i) + m.Layers = append(m.Layers[:i], m.Layers[i+1:]...) + } + i-- + } + if _, err := c.state.Update(); err != nil { + return fmt.Errorf("failed to update state: %w", err) + } + + logger.Debug("add oci artifact") + tag, err := toTag(c.version) + if err != nil { + return err + } + if _, err := c.comp.namespace.AddArtifact(c.manifest, tag); err != nil { + return fmt.Errorf("unable to add artifact: %w", err) + } + } + + return nil +} + +func (c *ComponentVersionContainer) evalLayer(s compdesc.AccessSpec) (compdesc.AccessSpec, int, error) { + var d *artdesc.Descriptor + + spec, err := c.GetContext().AccessSpecForSpec(s) + if err != nil { + return s, 0, err + } + if a, ok := spec.(*localblob.AccessSpec); ok { + if ok, _ := artdesc.IsDigest(a.LocalReference); !ok { + return s, 0, errors.ErrInvalid("digest", a.LocalReference) + } + d = &artdesc.Descriptor{Digest: digest.Digest(a.LocalReference), MediaType: a.GetMimeType()} + } + if d != nil { + // find layer + layers := c.manifest.GetDescriptor().Layers + max := len(layers) - 1 + for i := range layers { + l := layers[len(layers)-1-i] + if i < max && l.Digest == d.Digest && (d.Digest == "" || d.Digest == l.Digest) { + return s, len(layers) - 1 - i, nil + } + } + return s, 0, fmt.Errorf("resource access %s: no layer found for local blob %s[%s]", spec.Describe(c.GetContext()), d.Digest, d.MediaType) + } + return s, 0, nil +} + +func (c *ComponentVersionContainer) GetDescriptor() *compdesc.ComponentDescriptor { + return c.state.GetState().(*compdesc.ComponentDescriptor) +} + +func (c *ComponentVersionContainer) GetBlob(name string) (cpi.DataAccess, error) { + return c.manifest.GetBlob(digest.Digest(name)) +} + +func (c *ComponentVersionContainer) GetStorageContext() cpi.StorageContext { + return ocihdlr.New(c.comp.GetName(), c.Repository(), c.comp.repo.ocirepo.GetSpecification().GetKind(), c.comp.repo.ocirepo, c.comp.namespace, c.manifest) +} + +func (c *ComponentVersionContainer) AddBlob(blob cpi.BlobAccess, refName string, global cpi.AccessSpec) (cpi.AccessSpec, error) { + if blob == nil { + return nil, errors.New("a resource has to be defined") + } + + err := c.manifest.AddBlob(blob) + if err != nil { + return nil, err + } + err = ocihdlr.AssureLayer(c.manifest.GetDescriptor(), blob) + if err != nil { + return nil, err + } + return localblob.New(blob.Digest().String(), refName, blob.MimeType(), global), nil +} + +// AssureGlobalRef provides a global manifest for a local OCI Artifact. +func (c *ComponentVersionContainer) AssureGlobalRef(d digest.Digest, url, name string) (cpi.AccessSpec, error) { + blob, err := c.manifest.GetBlob(d) + if err != nil { + return nil, err + } + var namespace oci.NamespaceAccess + var version string + var tag string + if name == "" { + namespace = c.comp.namespace + } else { + i := strings.LastIndex(name, ":") + if i > 0 { + version = name[i+1:] + name = name[:i] + tag = version + } + namespace, err = c.comp.repo.ocirepo.LookupNamespace(name) + if err != nil { + return nil, err + } + } + set, err := artifactset.OpenFromBlob(accessobj.ACC_READONLY, blob) + if err != nil { + return nil, err + } + defer set.Close() + digest := set.GetMain() + if version == "" { + version = digest.String() + } + art, err := set.GetArtifact(digest.String()) + if err != nil { + return nil, err + } + err = artifactset.TransferArtifact(art, namespace, oci.AsTags(tag)...) + if err != nil { + return nil, err + } + + ref := path.Join(url+namespace.GetNamespace()) + ":" + version + + global := ociartifact.New(ref) + return global, nil +} diff --git a/api/ocm/extensions/repositories/genericocireg/cred_test.go b/api/ocm/extensions/repositories/genericocireg/cred_test.go new file mode 100644 index 000000000..89091147f --- /dev/null +++ b/api/ocm/extensions/repositories/genericocireg/cred_test.go @@ -0,0 +1,49 @@ +package genericocireg_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/mandelsoft/goutils/finalizer" + + "ocm.software/ocm/api/credentials" + ociidentity "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" + common "ocm.software/ocm/api/utils/misc" +) + +var _ = Describe("consumer id handling", func() { + It("creates a dummy component", func() { + var finalize finalizer.Finalizer + defer Defer(finalize.Finalize) + + ctx := ocm.New(datacontext.MODE_EXTENDED) + credctx := ctx.CredentialsContext() + + creds := ociidentity.SimpleCredentials("test", "password") + spec := ocireg.NewRepositorySpec("ghcr.io/open-component-model/test") + + id := credentials.GetProvidedConsumerId(spec, credentials.StringUsageContext(COMPONENT)) + Expect(id).To(Equal(credentials.NewConsumerIdentity(ociidentity.CONSUMER_TYPE, ociidentity.ID_HOSTNAME, "ghcr.io", ociidentity.ID_PATHPREFIX, "open-component-model/test/component-descriptors/"+COMPONENT))) + + id = credentials.GetProvidedConsumerId(spec) + Expect(id).To(Equal(credentials.NewConsumerIdentity(ociidentity.CONSUMER_TYPE, ociidentity.ID_HOSTNAME, "ghcr.io", ociidentity.ID_PATHPREFIX, "open-component-model/test"))) + + credctx.SetCredentialsForConsumer(id, creds) + + repo := finalizer.ClosingWith(&finalize, Must(DefaultContext.RepositoryForSpec(spec))) + + id = credentials.GetProvidedConsumerId(repo, credentials.StringUsageContext(COMPONENT)) + Expect(id).To(Equal(credentials.NewConsumerIdentity(ociidentity.CONSUMER_TYPE, ociidentity.ID_HOSTNAME, "ghcr.io", ociidentity.ID_PATHPREFIX, "open-component-model/test/component-descriptors/"+COMPONENT))) + + creds = Must(credentials.CredentialsForConsumer(credctx, id)) + + Expect(creds.Properties()).To(Equal(common.Properties{ + ociidentity.ATTR_USERNAME: "test", + ociidentity.ATTR_PASSWORD: "password", + })) + }) +}) diff --git a/api/ocm/extensions/repositories/genericocireg/excludes.go b/api/ocm/extensions/repositories/genericocireg/excludes.go new file mode 100644 index 000000000..fba2a5c61 --- /dev/null +++ b/api/ocm/extensions/repositories/genericocireg/excludes.go @@ -0,0 +1,13 @@ +package genericocireg + +import ( + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/oci/extensions/repositories/docker" + "ocm.software/ocm/api/oci/extensions/repositories/empty" +) + +var Excludes = []string{ + docker.Type, + artifactset.Type, + empty.Type, +} diff --git a/pkg/contexts/ocm/repositories/genericocireg/fallback_test.go b/api/ocm/extensions/repositories/genericocireg/fallback_test.go similarity index 89% rename from pkg/contexts/ocm/repositories/genericocireg/fallback_test.go rename to api/ocm/extensions/repositories/genericocireg/fallback_test.go index 89d0d4b04..e3c20de6b 100644 --- a/pkg/contexts/ocm/repositories/genericocireg/fallback_test.go +++ b/api/ocm/extensions/repositories/genericocireg/fallback_test.go @@ -6,7 +6,7 @@ import ( "github.com/mandelsoft/goutils/testutils" - "github.com/open-component-model/ocm/pkg/contexts/ocm" + "ocm.software/ocm/api/ocm" ) var _ = Describe("decode fallback", func() { diff --git a/api/ocm/extensions/repositories/genericocireg/info.go b/api/ocm/extensions/repositories/genericocireg/info.go new file mode 100644 index 000000000..73fdadf24 --- /dev/null +++ b/api/ocm/extensions/repositories/genericocireg/info.go @@ -0,0 +1,71 @@ +package genericocireg + +import ( + "encoding/json" + + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/oci/ociutils" + "ocm.software/ocm/api/ocm/extensions/repositories/genericocireg/componentmapping" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" +) + +func init() { + ociutils.RegisterInfoHandler(componentmapping.ComponentDescriptorConfigMimeType, &handler{}) +} + +type handler struct{} + +type ComponentVersionInfo struct { + Error string `json:"error,omitempty"` + Description string `json:"description"` + Unparsed string `json:"unparsed,omitempty"` + Descriptor json.RawMessage `json:"descriptor,omitempty"` +} + +func (h handler) Info(m cpi.ManifestAccess, config []byte) interface{} { + info := &ComponentVersionInfo{ + Description: "component version", + } + acc := NewStateAccess(m) + data, err := blobaccess.BlobData(acc.Get()) + if err != nil { + info.Error = "cannot read component descriptor: " + err.Error() + return info + } + var raw interface{} + err = json.Unmarshal(data, &raw) + if err != nil { + info.Unparsed = string(data) + return info + } + info.Descriptor = data + return info +} + +func (h handler) Description(pr common.Printer, m cpi.ManifestAccess, config []byte) { + pr.Printf("component version:\n") + acc := NewStateAccess(m) + data, err := blobaccess.BlobData(acc.Get()) + if err != nil { + pr.Printf(" cannot read component descriptor: %s\n", err.Error()) + return + } + pr.Printf(" descriptor:\n") + var raw interface{} + err = runtime.DefaultYAMLEncoding.Unmarshal(data, &raw) + if err != nil { + pr.Printf(" data: %s\n", string(data)) + pr.Printf(" cannot get unmarshal component descriptor: %s\n", err.Error()) + return + } + + form, err := json.MarshalIndent(raw, " ", " ") + if err != nil { + pr.Printf(" data: %s\n", string(data)) + pr.Printf(" cannot get marshal component descriptor: %s\n", err.Error()) + return + } + pr.Printf("%s\n", string(form)) +} diff --git a/api/ocm/extensions/repositories/genericocireg/logging.go b/api/ocm/extensions/repositories/genericocireg/logging.go new file mode 100644 index 000000000..799ac291b --- /dev/null +++ b/api/ocm/extensions/repositories/genericocireg/logging.go @@ -0,0 +1,15 @@ +package genericocireg + +import ( + "github.com/mandelsoft/logging" + + ocmlog "ocm.software/ocm/api/utils/logging" +) + +var REALM = ocmlog.DefineSubRealm("OCM to OCI Registry Mapping", "oci", "mapping") + +var TAG_CDDIFF = logging.DefineTag("cd-diff", "component descriptor modification") + +func Logger(ctx logging.ContextProvider, messageContext ...logging.MessageContext) logging.Logger { + return ctx.LoggingContext().Logger(append([]logging.MessageContext{REALM}, messageContext...)) +} diff --git a/api/ocm/extensions/repositories/genericocireg/repo_test.go b/api/ocm/extensions/repositories/genericocireg/repo_test.go new file mode 100644 index 000000000..aecac07be --- /dev/null +++ b/api/ocm/extensions/repositories/genericocireg/repo_test.go @@ -0,0 +1,414 @@ +package genericocireg_test + +import ( + "fmt" + "path" + "reflect" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/mandelsoft/goutils/finalizer" + "github.com/mandelsoft/vfs/pkg/osfs" + "github.com/mandelsoft/vfs/pkg/vfs" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + ocicpi "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/oci/extensions/repositories/ctf" + "ocm.software/ocm/api/oci/extensions/repositories/ctf/testhelper" + "ocm.software/ocm/api/oci/extensions/repositories/ocireg" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/cpi/repocpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localociblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociblob" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/attrs/compatattr" + "ocm.software/ocm/api/ocm/extensions/attrs/keepblobattr" + storagecontext "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/oci" + handler "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/oci/ocirepo" + "ocm.software/ocm/api/ocm/extensions/digester/digesters/artifact" + "ocm.software/ocm/api/ocm/extensions/repositories/genericocireg" + "ocm.software/ocm/api/ocm/extensions/repositories/genericocireg/componentmapping" + ocmreg "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" + ocmutils "ocm.software/ocm/api/ocm/ocmutils" + ocmtesthelper "ocm.software/ocm/api/ocm/testhelper" + "ocm.software/ocm/api/tech/signing/hasher/sha256" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/mime" +) + +var DefaultContext = ocm.New() + +const ( + COMPONENT = "github.com/mandelsoft/ocm" + TESTBASE = "testbase.de" +) + +var _ = Describe("component repository mapping", func() { + var tempfs vfs.FileSystem + + var ocispec oci.RepositorySpec + var spec *genericocireg.RepositorySpec + + BeforeEach(func() { + t, err := osfs.NewTempFileSystem() + Expect(err).To(Succeed()) + tempfs = t + + // ocmlog.Context().AddRule(logging.NewConditionRule(logging.TraceLevel, accessio.ALLOC_REALM)) + + ocispec, err = ctf.NewRepositorySpec(accessobj.ACC_CREATE, "test", accessio.PathFileSystem(tempfs), accessobj.FormatDirectory) + Expect(err).To(Succeed()) + spec = genericocireg.NewRepositorySpec(ocispec, nil) + }) + + AfterEach(func() { + vfs.Cleanup(tempfs) + }) + + It("Don't Panik! When it's not a semver.org conform version. #756", func() { + repo := Must(DefaultContext.RepositoryForSpec(spec)) + comp := Must(repo.LookupComponent(COMPONENT)) + cva, err := comp.NewVersion("v1.two.zeo-2") + Expect(err).To(HaveOccurred()) + Expect(cva).To(BeNil()) + Expect(err.Error()).To(Equal("Invalid Semantic Version")) + }) + + It("creates a dummy component", func() { + var finalize finalizer.Finalizer + defer Defer(finalize.Finalize) + + repo := finalizer.ClosingWith(&finalize, Must(DefaultContext.RepositoryForSpec(spec))) + impl := Must(repocpi.GetRepositoryImplementation(repo)) + Expect(reflect.TypeOf(impl).String()).To(Equal("*genericocireg.RepositoryImpl")) + + comp := finalizer.ClosingWith(&finalize, Must(repo.LookupComponent(COMPONENT))) + vers := finalizer.ClosingWith(&finalize, Must(comp.NewVersion("v1"))) + MustBeSuccessful(comp.AddVersion(vers)) + + noref := vers.Repository() + Expect(noref).NotTo(BeNil()) + Expect(noref.IsClosed()).To(BeFalse()) + Expect(noref.Close()).To(Succeed()) + Expect(noref.IsClosed()).To(BeFalse()) + + MustBeSuccessful(finalize.Finalize()) + + Expect(noref.IsClosed()).To(BeTrue()) + Expect(noref.Close()).To(MatchError("closed")) + ExpectError(noref.LookupComponent("dummy")).To(MatchError("closed")) + + // access it again + repo = finalizer.ClosingWith(&finalize, Must(DefaultContext.RepositoryForSpec(spec))) + + ok := Must(repo.ExistsComponentVersion(COMPONENT, "v1")) + Expect(ok).To(BeTrue()) + + comp = finalizer.ClosingWith(&finalize, Must(repo.LookupComponent(COMPONENT))) + vers = finalizer.ClosingWith(&finalize, Must(comp.LookupVersion("v1"))) + Expect(vers.GetDescriptor()).To(Equal(compdesc.New(COMPONENT, "v1"))) + + MustBeSuccessful(finalize.Finalize()) + }) + + It("handles legacylocalociblob access method", func() { + var finalize finalizer.Finalizer + defer Defer(finalize.Finalize) + + blob := blobaccess.ForString(mime.MIME_OCTET, "anydata") + + // create repository + repo := finalizer.ClosingWith(&finalize, Must(DefaultContext.RepositoryForSpec(spec))) + impl := Must(repocpi.GetRepositoryImplementation(repo)) + Expect(reflect.TypeOf(impl).String()).To(Equal("*genericocireg.RepositoryImpl")) + + comp := finalizer.ClosingWith(&finalize, Must(repo.LookupComponent(COMPONENT))) + vers := finalizer.ClosingWith(&finalize, Must(comp.NewVersion("v1"))) + acc := Must(vers.AddBlob(blob, "", "", nil)) + + MustBeSuccessful(vers.SetResource(compdesc.NewResourceMeta("blob", resourcetypes.PLAIN_TEXT, metav1.LocalRelation), acc)) + MustBeSuccessful(comp.AddVersion(vers)) + + rs := Must(vers.GetResourceByIndex(0)) + acc = Must(rs.Access()) + + // check provided actual access to be local blob + Expect(acc.GetKind()).To(Equal(localblob.Type)) + l, ok := acc.(*localblob.AccessSpec) + Expect(ok).To(BeTrue()) + Expect(l.LocalReference).To(Equal(blob.Digest().String())) + Expect(l.GlobalAccess).To(BeNil()) + + acc = localociblob.New(digest.Digest(l.LocalReference)) + + MustBeSuccessful(vers.SetResource(compdesc.NewResourceMeta("blob", resourcetypes.PLAIN_TEXT, metav1.LocalRelation), acc)) + MustBeSuccessful(comp.AddVersion(vers)) + + rs = Must(vers.GetResourceByIndex(0)) + spec := Must(rs.Access()) + + Expect(spec.GetType()).To(Equal(localociblob.Type)) + + m := Must(rs.AccessMethod()) + finalize.Close(m) + Expect(m.MimeType()).To(Equal("application/octet-stream")) + data := Must(m.Get()) + Expect(string(data)).To(Equal("anydata")) + }) + + It("imports blobs", func() { + var finalize finalizer.Finalizer + defer Defer(finalize.Finalize) + + base := func(ctx *storagecontext.StorageContext) string { + return TESTBASE + } + ctx := ocm.WithBlobHandlers(ocm.DefaultBlobHandlers().Copy().Register(handler.NewBlobHandler(base))).New() + blob := blobaccess.ForString(mime.MIME_OCTET, ocmtesthelper.S_TESTDATA) + + // create repository + repo := finalizer.ClosingWith(&finalize, Must(ctx.RepositoryForSpec(spec))) + impl := Must(repocpi.GetRepositoryImplementation(repo)) + Expect(reflect.TypeOf(impl).String()).To(Equal("*genericocireg.RepositoryImpl")) + + comp := finalizer.ClosingWith(&finalize, Must(repo.LookupComponent(COMPONENT))) + vers := finalizer.ClosingWith(&finalize, Must(comp.NewVersion("v1"))) + acc := Must(vers.AddBlob(blob, "", "", nil)) + MustBeSuccessful(vers.SetResource(compdesc.NewResourceMeta("blob", resourcetypes.PLAIN_TEXT, metav1.LocalRelation), acc)) + MustBeSuccessful(comp.AddVersion(vers)) + + res := Must(vers.GetResourceByIndex(0)) + acc = Must(res.Access()) + + // check provided actual access to be local blob + Expect(acc.GetKind()).To(Equal(localblob.Type)) + l, ok := acc.(*localblob.AccessSpec) + Expect(ok).To(BeTrue()) + Expect(l.LocalReference).To(Equal(blob.Digest().String())) + Expect(l.GlobalAccess).NotTo(BeNil()) + + // check provided global access to be oci blob + g := Must(l.GlobalAccess.Evaluate(DefaultContext)) + o, ok := g.(*ociblob.AccessSpec) + Expect(ok).To(BeTrue()) + Expect(o.Digest).To(Equal(blob.Digest())) + Expect(o.Reference).To(Equal(TESTBASE + "/" + componentmapping.ComponentDescriptorNamespace + "/" + COMPONENT)) + + Expect(res.Meta().Digest).NotTo(BeNil()) + Expect(res.Meta().Digest.Value).To(Equal(ocmtesthelper.D_TESTDATA)) + }) + + It("imports artifact", func() { + var finalize finalizer.Finalizer + defer Defer(finalize.Finalize) + + mime := artdesc.ToContentMediaType(artdesc.MediaTypeImageManifest) + "+tar+gzip" + base := func(ctx *storagecontext.StorageContext) string { + return TESTBASE + } + ctx := ocm.WithBlobHandlers(ocm.DefaultBlobHandlers().Copy().Register(handler.NewArtifactHandler(base), cpi.ForMimeType(mime))).New() + keepblobattr.Set(ctx, true) + + // create artifactset + opts := Must(accessio.AccessOptions(nil, accessio.PathFileSystem(tempfs))) + r := Must(artifactset.FormatTGZ.Create("test.tgz", opts, 0o700)) + testhelper.DefaultManifestFill(r) + r.Annotate(artifactset.MAINARTIFACT_ANNOTATION, "sha256:"+testhelper.DIGEST_MANIFEST) + Expect(r.Close()).To(Succeed()) + + // create repository + repo := finalizer.ClosingWith(&finalize, Must(ctx.RepositoryForSpec(spec))) + impl := Must(repocpi.GetRepositoryImplementation(repo)) + Expect(reflect.TypeOf(impl).String()).To(Equal("*genericocireg.RepositoryImpl")) + ocirepo := genericocireg.GetOCIRepository(repo) + Expect(ocirepo).NotTo(BeNil()) + + nested := finalize.Nested() + comp := finalizer.ClosingWith(nested, Must(repo.LookupComponent(COMPONENT))) + vers := finalizer.ClosingWith(nested, Must(comp.NewVersion("v1"))) + blob := blobaccess.ForFile(mime, "test.tgz", tempfs) + + fmt.Printf("physical digest: %s\n", blob.Digest()) + acc := Must(vers.AddBlob(blob, "", "artifact1", nil)) + MustBeSuccessful(vers.SetResource(cpi.NewResourceMeta("image", resourcetypes.OCI_IMAGE, metav1.LocalRelation), acc)) + MustBeSuccessful(comp.AddVersion(vers)) + + res := Must(vers.GetResourceByIndex(0)) + acc = Must(res.Access()) + Expect(acc.GetKind()).To(Equal(localblob.Type)) + rd := res.Meta().Digest + Expect(rd).NotTo(BeNil()) + Expect(rd.Value).To(Equal(testhelper.DIGEST_MANIFEST)) + Expect(rd.NormalisationAlgorithm).To(Equal(artifact.OciArtifactDigestV1)) + Expect(rd.HashAlgorithm).To(Equal(sha256.Algorithm)) + + acc = acc.GlobalAccessSpec(ctx) + Expect(acc).NotTo(BeNil()) + Expect(acc.GetKind()).To(Equal(ociartifact.Type)) + o := acc.(*ociartifact.AccessSpec) + Expect(o.ImageReference).To(Equal(TESTBASE + "/artifact1@sha256:" + testhelper.DIGEST_MANIFEST)) + + acc = Must(vers.AddBlob(blob, "", "artifact2:v1", nil)) + MustBeSuccessful(vers.SetResource(cpi.NewResourceMeta("image2", resourcetypes.OCI_IMAGE, metav1.LocalRelation), acc, cpi.ModifyResource())) + MustBeSuccessful(comp.AddVersion(vers)) + + res = Must(vers.GetResourceByIndex(1)) + acc = Must(res.Access()) + acc = acc.GlobalAccessSpec(ctx) + Expect(acc).NotTo(BeNil()) + Expect(acc.GetKind()).To(Equal(ociartifact.Type)) + o = acc.(*ociartifact.AccessSpec) + Expect(o.ImageReference).To(Equal(TESTBASE + "/artifact2:v1")) + + MustBeSuccessful(nested.Finalize()) + + ns := finalizer.ClosingWith(nested, Must(ocirepo.LookupNamespace("artifact2"))) + art := finalizer.ClosingWith(nested, Must(ns.GetArtifact("v1"))) + testhelper.CheckArtifact(art) + + MustBeSuccessful(finalize.Finalize()) + }) + + It("removes old unused layers", func() { + var finalize finalizer.Finalizer + defer Defer(finalize.Finalize, "finalize open elements") + + repo := finalizer.ClosingWith(&finalize, Must(DefaultContext.RepositoryForSpec(spec))) + impl := Must(repocpi.GetRepositoryImplementation(repo)) + Expect(reflect.TypeOf(impl).String()).To(Equal("*genericocireg.RepositoryImpl")) + + nested := finalize.Nested() + + comp := finalizer.ClosingWith(nested, Must(repo.LookupComponent(COMPONENT))) + vers := finalizer.ClosingWith(nested, Must(comp.NewVersion("v1"))) + + m1 := compdesc.NewResourceMeta("rsc1", resourcetypes.PLAIN_TEXT, metav1.LocalRelation) + blob := blobaccess.ForString(mime.MIME_TEXT, ocmtesthelper.S_TESTDATA) + + MustBeSuccessful(vers.SetResourceBlob(m1, blob, "", nil)) + MustBeSuccessful(comp.AddVersion(vers)) + + MustBeSuccessful(nested.Finalize()) + + // modify resource in component + vers = finalizer.ClosingWith(nested, Must(repo.LookupComponentVersion(COMPONENT, "v1"))) + blob = blobaccess.ForString(mime.MIME_TEXT, "otherdata") + MustBeSuccessful(vers.SetResourceBlob(m1, blob, "", nil)) + MustBeSuccessful(vers.Update()) + MustBeSuccessful(nested.Finalize()) + + // check content + vers = finalizer.ClosingWith(nested, Must(repo.LookupComponentVersion(COMPONENT, "v1"))) + r := Must(vers.GetResource(metav1.NewIdentity("rsc1"))) + data := Must(ocmutils.GetResourceData(r)) + Expect(string(data)).To(Equal("otherdata")) + MustBeSuccessful(nested.Finalize()) + + MustBeSuccessful(finalize.Finalize()) + + ocirepo := Must(DefaultContext.OCIContext().RepositoryForSpec(ocispec)) + finalize.Close(ocirepo) + + art := Must(ocirepo.LookupArtifact("component-descriptors/"+COMPONENT, "v1")) + finalize.Close(art) + + Expect(art.GetDescriptor().IsManifest()).To(BeTrue()) + m := Must(art.GetDescriptor().Manifest()) + Expect(len(m.Layers)).To(Equal(2)) + }) + + Context("legacy mode", func() { + It("creates a legacy dummy component", func() { + ctx := ocm.New() + compatattr.Set(ctx, true) + + var finalize finalizer.Finalizer + defer Defer(finalize.Finalize) + + repo := finalizer.ClosingWith(&finalize, Must(ctx.RepositoryForSpec(spec))) + comp := finalizer.ClosingWith(&finalize, Must(repo.LookupComponent(COMPONENT))) + vers := finalizer.ClosingWith(&finalize, Must(comp.NewVersion("v1"))) + MustBeSuccessful(comp.AddVersion(vers)) + MustBeSuccessful(finalize.Finalize()) + + // access as OCI repository + + ocirepo := finalizer.ClosingWith(&finalize, Must(oci.DefaultContext().RepositoryForSpec(ocispec))) + ns := finalizer.ClosingWith(&finalize, Must(ocirepo.LookupNamespace(path.Join(componentmapping.ComponentDescriptorNamespace, COMPONENT)))) + art := finalizer.ClosingWith(&finalize, Must(ns.GetArtifact("v1"))) + m := Must(art.GetDescriptor().Manifest()) + Expect(m.Config.MediaType).To(Equal(componentmapping.LegacyComponentDescriptorConfigMimeType)) + Expect(len(m.Layers)).To(Equal(1)) + Expect(m.Layers[0].MediaType).To(Equal(componentmapping.LegacyComponentDescriptorTarMimeType)) + }) + + It("updates a legacy dummy component", func() { + ctx := ocm.New() + compatattr.Set(ctx, true) + + var finalize finalizer.Finalizer + defer Defer(finalize.Finalize) + + repo := finalizer.ClosingWith(&finalize, Must(ctx.RepositoryForSpec(spec))) + comp := finalizer.ClosingWith(&finalize, Must(repo.LookupComponent(COMPONENT))) + vers := finalizer.ClosingWith(&finalize, Must(comp.NewVersion("v1"))) + MustBeSuccessful(comp.AddVersion(vers)) + MustBeSuccessful(finalize.Finalize()) + + // update component in non-legacy context + + repo = finalizer.ClosingWith(&finalize, Must(DefaultContext.RepositoryForSpec(spec))) + comp = finalizer.ClosingWith(&finalize, Must(repo.LookupComponent(COMPONENT))) + vers = finalizer.ClosingWith(&finalize, Must(comp.LookupVersion("v1"))) + vers.GetDescriptor().Provider.Name = "acme.org" + MustBeSuccessful(comp.AddVersion(vers)) + MustBeSuccessful(finalize.Finalize()) + + // access as OCI repository + + ocirepo := finalizer.ClosingWith(&finalize, Must(oci.DefaultContext().RepositoryForSpec(ocispec))) + ns := finalizer.ClosingWith(&finalize, Must(ocirepo.LookupNamespace(path.Join(componentmapping.ComponentDescriptorNamespace, COMPONENT)))) + art := finalizer.ClosingWith(&finalize, Must(ns.GetArtifact("v1"))) + m := Must(art.GetDescriptor().Manifest()) + Expect(m.Config.MediaType).To(Equal(componentmapping.LegacyComponentDescriptorConfigMimeType)) + Expect(len(m.Layers)).To(Equal(1)) + Expect(m.Layers[0].MediaType).To(Equal(componentmapping.LegacyComponentDescriptorTarMimeType)) + MustBeSuccessful(finalize.Finalize()) + + repo = finalizer.ClosingWith(&finalize, Must(DefaultContext.RepositoryForSpec(spec))) + vers = finalizer.ClosingWith(&finalize, Must(repo.LookupComponentVersion(COMPONENT, "v1"))) + Expect(string(vers.GetDescriptor().Provider.Name)).To(Equal("acme.org")) + }) + }) + + Context("repo urls", func() { + It("creates scheme based repo", func() { + ctx := ocm.New() + + spec := ocmreg.NewRepositorySpec("http://127.0.0.1:5000/ocm") + repo := Must(ctx.RepositoryForSpec(spec)) + defer Close(repo, "repo") + + ocirepo := genericocireg.GetOCIRepository(repo) + Expect(ocirepo).NotTo(BeNil()) + impl := Must(ocicpi.GetRepositoryImplementation(ocirepo)) + + Expect(impl).NotTo(BeNil()) + + Expect(impl.(*ocireg.RepositoryImpl).GetBaseURL()).To(Equal("http://127.0.0.1:5000")) + Expect(impl.(*ocireg.RepositoryImpl).GetRef("repo/path", "1.0.0")).To(Equal("127.0.0.1:5000/repo/path:1.0.0")) + }) + }) +}) diff --git a/api/ocm/extensions/repositories/genericocireg/repository.go b/api/ocm/extensions/repositories/genericocireg/repository.go new file mode 100644 index 000000000..70661f76f --- /dev/null +++ b/api/ocm/extensions/repositories/genericocireg/repository.go @@ -0,0 +1,206 @@ +package genericocireg + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "path" + "strings" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/general" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/oci" + ocicpi "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/cpi/repocpi" + "ocm.software/ocm/api/ocm/extensions/repositories/genericocireg/componentmapping" +) + +type OCIBasedRepository interface { + OCIRepository() ocicpi.Repository +} + +func GetOCIRepository(r cpi.Repository) ocicpi.Repository { + if o, ok := r.(OCIBasedRepository); ok { + return o.OCIRepository() + } + impl, err := repocpi.GetRepositoryImplementation(r) + if err != nil { + return nil + } + if o, ok := impl.(OCIBasedRepository); ok { + return o.OCIRepository() + } + return nil +} + +type RepositoryImpl struct { + bridge repocpi.RepositoryBridge + ctx cpi.Context + meta ComponentRepositoryMeta + nonref cpi.Repository + ocirepo oci.Repository + readonly bool +} + +var ( + _ repocpi.RepositoryImpl = (*RepositoryImpl)(nil) + _ credentials.ConsumerIdentityProvider = (*RepositoryImpl)(nil) +) + +func NewRepository(ctxp cpi.ContextProvider, meta *ComponentRepositoryMeta, ocirepo oci.Repository) cpi.Repository { + ctx := datacontext.InternalContextRef(ctxp.OCMContext()) + impl := &RepositoryImpl{ + ctx: ctx, + meta: *DefaultComponentRepositoryMeta(meta), + ocirepo: ocirepo, + } + return repocpi.NewRepository(impl, "OCM repo[OCI]") +} + +func (r *RepositoryImpl) Close() error { + return r.ocirepo.Close() +} + +func (r *RepositoryImpl) IsReadOnly() bool { + // TODO: extend OCI to query ReadOnly mode + return r.readonly +} + +func (r *RepositoryImpl) SetReadOnly() { + r.readonly = true +} + +func (r *RepositoryImpl) SetBridge(base repocpi.RepositoryBridge) { + r.bridge = base + r.nonref = repocpi.NewNoneRefRepositoryView(base) +} + +func (r *RepositoryImpl) GetContext() cpi.Context { + return r.ctx +} + +func (r *RepositoryImpl) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { + prefix := r.meta.SubPath + if c, ok := general.Optional(uctx...).(credentials.StringUsageContext); ok { + prefix = path.Join(prefix, componentmapping.ComponentDescriptorNamespace, c.String()) + } + if p, ok := r.ocirepo.(credentials.ConsumerIdentityProvider); ok { + return p.GetConsumerId(credentials.StringUsageContext(prefix)) + } + return nil +} + +func (r *RepositoryImpl) GetIdentityMatcher() string { + if p, ok := r.ocirepo.(credentials.ConsumerIdentityProvider); ok { + return p.GetIdentityMatcher() + } + return "" +} + +func (r *RepositoryImpl) OCIRepository() ocicpi.Repository { + return r.ocirepo +} + +func (r *RepositoryImpl) Meta() ComponentRepositoryMeta { + return r.meta +} + +func (r *RepositoryImpl) GetSpecification() cpi.RepositorySpec { + return &RepositorySpec{ + RepositorySpec: r.ocirepo.GetSpecification(), + ComponentRepositoryMeta: r.meta, + } +} + +func (r *RepositoryImpl) ComponentLister() cpi.ComponentLister { + if r.meta.ComponentNameMapping != OCIRegistryURLPathMapping { + return nil + } + lister := r.ocirepo.NamespaceLister() + if lister == nil { + return nil + } + return r +} + +func (r *RepositoryImpl) NumComponents(prefix string) (int, error) { + lister := r.ocirepo.NamespaceLister() + if lister == nil { + return -1, errors.ErrNotSupported("component lister") + } + p := path.Join(r.meta.SubPath, componentmapping.ComponentDescriptorNamespace, prefix) + if strings.HasSuffix(prefix, "/") && !strings.HasSuffix(p, "/") { + p += "/" + } + return lister.NumNamespaces(p) +} + +func (r *RepositoryImpl) GetComponents(prefix string, closure bool) ([]string, error) { + lister := r.ocirepo.NamespaceLister() + if lister == nil { + return nil, errors.ErrNotSupported("component lister") + } + p := path.Join(r.meta.SubPath, componentmapping.ComponentDescriptorNamespace) + compprefix := len(p) + 1 + p = path.Join(p, prefix) + if strings.HasSuffix(prefix, "/") && !strings.HasSuffix(p, "/") { + p += "/" + } + tmp, err := lister.GetNamespaces(p, closure) + if err != nil { + return nil, err + } + result := make([]string, len(tmp)) + for i, r := range tmp { + result[i] = r[compprefix:] + } + return result, nil +} + +func (r *RepositoryImpl) GetOCIRepository() oci.Repository { + return r.ocirepo +} + +func (r *RepositoryImpl) ExistsComponentVersion(name string, version string) (bool, error) { + namespace, err := r.MapComponentNameToNamespace(name) + if err != nil { + return false, err + } + a, err := r.ocirepo.LookupArtifact(namespace, version) + if err != nil { + return false, err + } + defer a.Close() + desc, err := a.Manifest() + if err != nil { + return false, err + } + switch desc.Config.MediaType { + case componentmapping.ComponentDescriptorConfigMimeType, + componentmapping.LegacyComponentDescriptorConfigMimeType, + componentmapping.Legacy2ComponentDescriptorConfigMimeType: + return true, nil + } + return false, nil +} + +func (r *RepositoryImpl) LookupComponent(name string) (*repocpi.ComponentAccessInfo, error) { + return newComponentAccess(r, name, true) +} + +func (r *RepositoryImpl) MapComponentNameToNamespace(name string) (string, error) { + switch r.meta.ComponentNameMapping { + case OCIRegistryURLPathMapping, "": + return path.Join(r.meta.SubPath, componentmapping.ComponentDescriptorNamespace, name), nil + case OCIRegistryDigestMapping: + h := sha256.New() + _, _ = h.Write([]byte(name)) + return path.Join(r.meta.SubPath, hex.EncodeToString(h.Sum(nil))), nil + default: + return "", fmt.Errorf("unknown component name mapping method %s", r.meta.ComponentNameMapping) + } +} diff --git a/pkg/contexts/ocm/repositories/genericocireg/semver_test.go b/api/ocm/extensions/repositories/genericocireg/semver_test.go similarity index 92% rename from pkg/contexts/ocm/repositories/genericocireg/semver_test.go rename to api/ocm/extensions/repositories/genericocireg/semver_test.go index 51c20e538..e1da99a68 100644 --- a/pkg/contexts/ocm/repositories/genericocireg/semver_test.go +++ b/api/ocm/extensions/repositories/genericocireg/semver_test.go @@ -7,7 +7,7 @@ import ( "github.com/Masterminds/semver/v3" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/genericocireg" + "ocm.software/ocm/api/ocm/extensions/repositories/genericocireg" ) const META_SEPARATOR = genericocireg.META_SEPARATOR diff --git a/pkg/contexts/ocm/repositories/genericocireg/spec_test.go b/api/ocm/extensions/repositories/genericocireg/spec_test.go similarity index 87% rename from pkg/contexts/ocm/repositories/genericocireg/spec_test.go rename to api/ocm/extensions/repositories/genericocireg/spec_test.go index 05146ffef..13de703b3 100644 --- a/pkg/contexts/ocm/repositories/genericocireg/spec_test.go +++ b/api/ocm/extensions/repositories/genericocireg/spec_test.go @@ -8,11 +8,11 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/genericocireg" - ocmreg "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/runtime" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/extensions/repositories/ocireg" + "ocm.software/ocm/api/ocm/extensions/repositories/genericocireg" + ocmreg "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" + "ocm.software/ocm/api/utils/runtime" ) var DefaultOCIContext = oci.New() diff --git a/pkg/contexts/ocm/repositories/genericocireg/specnorm.go b/api/ocm/extensions/repositories/genericocireg/specnorm.go similarity index 100% rename from pkg/contexts/ocm/repositories/genericocireg/specnorm.go rename to api/ocm/extensions/repositories/genericocireg/specnorm.go diff --git a/api/ocm/extensions/repositories/genericocireg/state.go b/api/ocm/extensions/repositories/genericocireg/state.go new file mode 100644 index 000000000..bafcae058 --- /dev/null +++ b/api/ocm/extensions/repositories/genericocireg/state.go @@ -0,0 +1,284 @@ +package genericocireg + +import ( + "archive/tar" + "bytes" + "encoding/json" + "fmt" + "io" + "reflect" + "strings" + + "github.com/go-test/deep" + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/logging" + "github.com/opencontainers/go-digest" + ociv1 "github.com/opencontainers/image-spec/specs-go/v1" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/extensions/repositories/ctf/format" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/repositories/genericocireg/componentmapping" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + ocmlog "ocm.software/ocm/api/utils/logging" +) + +func NewState(mode accessobj.AccessMode, name, version string, access oci.ManifestAccess, compat ...bool) (accessobj.State, error) { + return accessobj.NewState(mode, NewStateAccess(access, compat...), NewStateHandler(name, version)) +} + +// StateAccess handles the component descriptor persistence in an OCI Manifest. +type StateAccess struct { + access oci.ManifestAccess + layerMedia string + compat bool +} + +var _ accessobj.StateAccess = (*StateAccess)(nil) + +func NewStateAccess(access oci.ManifestAccess, compat ...bool) accessobj.StateAccess { + return &StateAccess{ + compat: utils.Optional(compat...), + access: access, + } +} + +func (s *StateAccess) Get() (blobaccess.BlobAccess, error) { + mediaType := s.access.GetDescriptor().Config.MediaType + switch mediaType { + case componentmapping.ComponentDescriptorConfigMimeType, + componentmapping.LegacyComponentDescriptorConfigMimeType, + componentmapping.Legacy2ComponentDescriptorConfigMimeType: + return s.get() + case "": + return nil, errors.ErrNotFound(cpi.KIND_COMPONENTVERSION) + default: + return nil, errors.Newf("artifact is no component: %s", mediaType) + } +} + +func (s *StateAccess) get() (blobaccess.BlobAccess, error) { + var config ComponentDescriptorConfig + + data, err := blobaccess.BlobData(s.access.GetConfigBlob()) + if err != nil { + return nil, err + } + err = json.Unmarshal(data, &config) + if err != nil { + return nil, err + } + if config.ComponentDescriptorLayer == nil || config.ComponentDescriptorLayer.Digest == "" { + return nil, errors.ErrInvalid("component descriptor config") + } + switch config.ComponentDescriptorLayer.MediaType { + case componentmapping.ComponentDescriptorJSONMimeType, + componentmapping.LegacyComponentDescriptorJSONMimeType, + componentmapping.ComponentDescriptorYAMLMimeType, + componentmapping.LegacyComponentDescriptorYAMLMimeType: + s.layerMedia = "" + return s.access.GetBlob(config.ComponentDescriptorLayer.Digest) + case componentmapping.ComponentDescriptorTarMimeType, + componentmapping.LegacyComponentDescriptorTarMimeType, + componentmapping.Legacy2ComponentDescriptorTarMimeType: + d, err := s.access.GetBlob(config.ComponentDescriptorLayer.Digest) + if err != nil { + return nil, err + } + r, err := d.Reader() + if err != nil { + return nil, err + } + defer r.Close() + data, err := s.readComponentDescriptorFromTar(r) + if err != nil { + return nil, err + } + s.layerMedia = config.ComponentDescriptorLayer.MediaType + return blobaccess.ForData(componentmapping.ComponentDescriptorYAMLMimeType, data), nil + default: + return nil, errors.ErrInvalid("config mediatype", config.ComponentDescriptorLayer.MediaType) + } +} + +// readComponentDescriptorFromTar reads the component descriptor from a tar. +// The component is expected to be inside the tar at "/component-descriptor.yaml". +func (s *StateAccess) readComponentDescriptorFromTar(r io.Reader) ([]byte, error) { + tr := tar.NewReader(r) + for { + header, err := tr.Next() + if err != nil { + if errors.Is(err, io.EOF) { + return nil, errors.New("no component descriptor found in tar") + } + return nil, fmt.Errorf("unable to read tar: %w", err) + } + + if strings.TrimLeft(header.Name, "/") != compdesc.ComponentDescriptorFileName { + continue + } + + var data bytes.Buffer + //nolint:gosec // We don't know what size limit we could set, the tar + // archive can be an image layer and that can even reach the gigabyte range. + // For now, we acknowledge the risk. + // + // We checked other softwares and tried to figure out how they manage this, + // but it's handled the same way. + if _, err := io.Copy(&data, tr); err != nil { + return nil, fmt.Errorf("erro while reading component descriptor file from tar: %w", err) + } + return data.Bytes(), err + } +} + +func (s StateAccess) Digest() digest.Digest { + blob, err := s.access.GetConfigBlob() + if err != nil { + return "" + } + return blob.Digest() +} + +func (s *StateAccess) Put(data []byte) error { + desc := s.access.GetDescriptor() + mediaType := desc.Config.MediaType + if mediaType == "" { + if s.compat { + mediaType = componentmapping.LegacyComponentDescriptorConfigMimeType + } else { + mediaType = componentmapping.ComponentDescriptorConfigMimeType + } + desc.Config.MediaType = mediaType + } + + arch, err := s.writeComponentDescriptorTar(data) + if err != nil { + return err + } + config := ComponentDescriptorConfig{ + ComponentDescriptorLayer: artdesc.DefaultBlobDescriptor(arch), + } + + configdata, err := json.Marshal(&config) + if err != nil { + return err + } + + err = s.access.AddBlob(arch) + if err != nil { + return err + } + s.layerMedia = arch.MimeType() + configblob := blobaccess.ForData(mediaType, configdata) + err = s.access.AddBlob(configblob) + if err != nil { + return err + } + desc.Config = *artdesc.DefaultBlobDescriptor(configblob) + if len(desc.Layers) < 2 { + desc.Layers = []ociv1.Descriptor{*artdesc.DefaultBlobDescriptor(arch)} + } else { + desc.Layers[0] = *artdesc.DefaultBlobDescriptor(arch) + } + return nil +} + +// writeComponentDescriptorTar writes the component descriptor into a tar. +// The component is expected to be inside the tar at "/component-descriptor.yaml". +func (s *StateAccess) writeComponentDescriptorTar(data []byte) (cpi.BlobAccess, error) { + var buf bytes.Buffer + tw := tar.NewWriter(&buf) + err := tw.WriteHeader(&tar.Header{ + Typeflag: tar.TypeReg, + Name: componentmapping.ComponentDescriptorFileName, + Size: int64(len(data)), + ModTime: format.ModTime, + }) + + media := s.layerMedia + if media == "" { + if s.compat { + media = componentmapping.LegacyComponentDescriptorTarMimeType + } else { + media = componentmapping.ComponentDescriptorTarMimeType + } + } + if err != nil { + return nil, errors.Newf("unable to add component descriptor header: %s", err) + } + if _, err := io.Copy(tw, bytes.NewBuffer(data)); err != nil { + return nil, errors.Newf("unable to write component-descriptor to tar: %s", err) + } + if err := tw.Close(); err != nil { + return nil, errors.Newf("unable to close tar writer: %s", err) + } + return blobaccess.ForData(media, buf.Bytes()), nil +} + +// ComponentDescriptorConfig is a Component-Descriptor OCI configuration that is used to store the reference to the +// (pseudo-)layer used to store the Component-Descriptor in. +type ComponentDescriptorConfig struct { + ComponentDescriptorLayer *ociv1.Descriptor `json:"componentDescriptorLayer,omitempty"` +} + +//////////////////////////////////////////////////////////////////////////////// + +// StateHandler handles the encoding of a component descriptor. +type StateHandler struct { + name string + version string +} + +var _ accessobj.StateHandler = (*StateHandler)(nil) + +func NewStateHandler(name, version string) accessobj.StateHandler { + return &StateHandler{ + name: name, + version: version, + } +} + +func (i StateHandler) Initial() interface{} { + return compdesc.New(i.name, i.version) +} + +// Encode always provides a yaml representation. +func (i StateHandler) Encode(d interface{}) ([]byte, error) { + desc, ok := d.(*compdesc.ComponentDescriptor) + if !ok { + return nil, fmt.Errorf("failed to assert type %t to *compdesc.ComponentDescriptor", d) + } + desc.Name = i.name + desc.Version = i.version + return compdesc.Encode(desc) +} + +// Decode always accepts a yaml representation, and therefore json, also. +func (i StateHandler) Decode(data []byte) (interface{}, error) { + return compdesc.Decode(data) +} + +func (i StateHandler) Equivalent(a, b interface{}) bool { + if l := Logger(ocmlog.Context(), TAG_CDDIFF); l.Enabled(logging.DebugLevel) { + diff := deep.Equal(a, b) + if len(diff) > 0 { + l.Debug("component descriptor has been changed", "diff", diff) + return false + } + return true + } + + ea, err := i.Encode(a) + if err == nil { + eb, err := i.Encode(b) + if err == nil { + return bytes.Equal(ea, eb) + } + } + return reflect.DeepEqual(a, b) +} diff --git a/pkg/contexts/ocm/repositories/genericocireg/suite_test.go b/api/ocm/extensions/repositories/genericocireg/suite_test.go similarity index 100% rename from pkg/contexts/ocm/repositories/genericocireg/suite_test.go rename to api/ocm/extensions/repositories/genericocireg/suite_test.go diff --git a/api/ocm/extensions/repositories/genericocireg/type.go b/api/ocm/extensions/repositories/genericocireg/type.go new file mode 100644 index 000000000..9b43dcae6 --- /dev/null +++ b/api/ocm/extensions/repositories/genericocireg/type.go @@ -0,0 +1,191 @@ +package genericocireg + +import ( + "encoding/json" + "path" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/general" + "github.com/sirupsen/logrus" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/extensions/repositories/ocireg" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/repositories/genericocireg/componentmapping" + "ocm.software/ocm/api/utils/runtime" +) + +// ComponentNameMapping describes the method that is used to map the "Component Name", "Component Version"-tuples +// to OCI Image References. +type ComponentNameMapping string + +const ( + Type = ocireg.Type + + OCIRegistryURLPathMapping ComponentNameMapping = "urlPath" + OCIRegistryDigestMapping ComponentNameMapping = "sha256-digest" +) + +func init() { + cpi.DefaultDelegationRegistry().Register("OCI", New(10)) +} + +// delegation tries to resolve an ocm repository specification +// with an OCI repository specification supported by the OCI context +// of the OCM context. +type delegation struct { + prio int +} + +func New(prio int) cpi.RepositoryPriorityDecoder { + return &delegation{prio} +} + +var _ cpi.RepositoryPriorityDecoder = (*delegation)(nil) + +func (d *delegation) Decode(ctx cpi.Context, data []byte, unmarshal runtime.Unmarshaler) (cpi.RepositorySpec, error) { + if unmarshal == nil { + unmarshal = runtime.DefaultYAMLEncoding.Unmarshaler + } + + ospec, err := ctx.OCIContext().RepositoryTypes().Decode(data, unmarshal) + if err != nil { + return nil, err + } + if oci.IsUnknown(ospec) { + return nil, nil + } + + meta := &ComponentRepositoryMeta{} + err = unmarshal.Unmarshal(data, meta) + if err != nil { + return nil, errors.Wrapf(err, "cannot unmarshal component repository meta information") + } + return normalizers.Normalize(NewRepositorySpec(ospec, meta)), nil +} + +func (d *delegation) Priority() int { + return d.prio +} + +// ComponentRepositoryMeta describes config special for a mapping of +// a component repository to an oci registry. +// It is parsed in addition to an OCI based specification. +type ComponentRepositoryMeta struct { + // ComponentNameMapping describes the method that is used to map the "Component Name", "Component Version"-tuples + // to OCI Image References. + ComponentNameMapping ComponentNameMapping `json:"componentNameMapping,omitempty"` + SubPath string `json:"subPath,omitempty"` +} + +func NewComponentRepositoryMeta(subPath string, mapping ComponentNameMapping) *ComponentRepositoryMeta { + return DefaultComponentRepositoryMeta(&ComponentRepositoryMeta{ + ComponentNameMapping: mapping, + SubPath: subPath, + }) +} + +//////////////////////////////////////////////////////////////////////////////// + +type RepositorySpec struct { + oci.RepositorySpec + ComponentRepositoryMeta +} + +var ( + _ cpi.RepositorySpec = (*RepositorySpec)(nil) + _ cpi.PrefixProvider = (*RepositorySpec)(nil) + _ cpi.IntermediateRepositorySpecAspect = (*RepositorySpec)(nil) + _ json.Marshaler = (*RepositorySpec)(nil) + _ credentials.ConsumerIdentityProvider = (*RepositorySpec)(nil) +) + +func NewRepositorySpec(spec oci.RepositorySpec, meta *ComponentRepositoryMeta) *RepositorySpec { + s := &RepositorySpec{ + RepositorySpec: spec, + ComponentRepositoryMeta: *DefaultComponentRepositoryMeta(meta), + } + return normalizers.Normalize(s) +} + +func (a *RepositorySpec) PathPrefix() string { + return a.SubPath +} + +func (a *RepositorySpec) IsIntermediate() bool { + if s, ok := a.RepositorySpec.(oci.IntermediateRepositorySpecAspect); ok { + return s.IsIntermediate() + } + return false +} + +// TODO: host etc is missing + +func (a *RepositorySpec) AsUniformSpec(cpi.Context) *cpi.UniformRepositorySpec { + return &cpi.UniformRepositorySpec{Type: a.GetKind(), SubPath: a.SubPath} +} + +func (u *RepositorySpec) UnmarshalJSON(data []byte) error { + logrus.Debugf("unmarshal generic ocireg spec %s\n", string(data)) + ocispec := &oci.GenericRepositorySpec{} + if err := json.Unmarshal(data, ocispec); err != nil { + return err + } + compmeta := &ComponentRepositoryMeta{} + if err := json.Unmarshal(data, ocispec); err != nil { + return err + } + + u.RepositorySpec = ocispec + u.ComponentRepositoryMeta = *compmeta + + normalizers.Normalize(u) + return nil +} + +// MarshalJSON implements a custom json unmarshal method for an unstructured type. +// The oci.RepositorySpec object might already implement json.Marshaler, +// which would be inherited and omit marshaling the addend attributes of a +// cpi.RepositorySpec. +func (u RepositorySpec) MarshalJSON() ([]byte, error) { + ocispec, err := runtime.ToUnstructuredTypedObject(u.RepositorySpec) + if err != nil { + return nil, err + } + compmeta, err := runtime.ToUnstructuredObject(u.ComponentRepositoryMeta) + if err != nil { + return nil, err + } + return json.Marshal(compmeta.FlatMerge(ocispec.Object)) +} + +func (s *RepositorySpec) Repository(ctx cpi.Context, creds credentials.Credentials) (cpi.Repository, error) { + r, err := s.RepositorySpec.Repository(ctx.OCIContext(), creds) + if err != nil { + return nil, err + } + return NewRepository(ctx, &s.ComponentRepositoryMeta, r), nil +} + +func (s *RepositorySpec) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { + prefix := s.SubPath + if c, ok := general.Optional(uctx...).(credentials.StringUsageContext); ok { + prefix = path.Join(prefix, componentmapping.ComponentDescriptorNamespace, c.String()) + } + return credentials.GetProvidedConsumerId(s.RepositorySpec, credentials.StringUsageContext(prefix)) +} + +func (s *RepositorySpec) GetIdentityMatcher() string { + return credentials.GetProvidedIdentityMatcher(s.RepositorySpec) +} + +func DefaultComponentRepositoryMeta(meta *ComponentRepositoryMeta) *ComponentRepositoryMeta { + if meta == nil { + meta = &ComponentRepositoryMeta{} + } + if meta.ComponentNameMapping == "" { + meta.ComponentNameMapping = OCIRegistryURLPathMapping + } + return meta +} diff --git a/api/ocm/extensions/repositories/genericocireg/uniform.go b/api/ocm/extensions/repositories/genericocireg/uniform.go new file mode 100644 index 000000000..7cb534495 --- /dev/null +++ b/api/ocm/extensions/repositories/genericocireg/uniform.go @@ -0,0 +1,86 @@ +package genericocireg + +import ( + "fmt" + "strings" + + "ocm.software/ocm/api/oci/extensions/repositories/ocireg" + "ocm.software/ocm/api/oci/grammar" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/attrs/compatattr" +) + +func init() { + cpi.RegisterRepositorySpecHandler(&repospechandler{}, "*") + cpi.RegisterRefParseHandler(Type, HandleRef) + cpi.RegisterRefParseHandler(ocireg.ShortType, HandleRef) +} + +type repospechandler struct{} + +func (h *repospechandler) MapReference(ctx cpi.Context, u *cpi.UniformRepositorySpec) (cpi.RepositorySpec, error) { + var meta *ComponentRepositoryMeta + host := u.Host + subp := u.SubPath + + // This is checked because it can lead to confusion with the ocm notation. + if strings.Contains(subp, "//") { + return nil, fmt.Errorf("subpath %q cannot contain double slash (//)", subp) + } + if u.Type == Type { + if u.Info != "" && u.SubPath == "" { + idx := strings.Index(u.Info, grammar.RepositorySeparator) + if idx > 0 { + host = u.Info[:idx] + subp = u.Info[idx+1:] + } else { + host = u.Info + } + } else if u.Host == "" { + return nil, fmt.Errorf("host required for OCI based OCM reference") + } + } else { + if u.Type != "" || u.Info != "" || u.Host == "" { + return nil, nil + } + host = u.Host + } + if u.Scheme != "" { + host = u.Scheme + "://" + host + } + if subp != "" { + meta = NewComponentRepositoryMeta(subp, "") + } + if compatattr.Get(ctx) { + return NewRepositorySpec(ocireg.NewLegacyRepositorySpec(host), meta), nil + } + return NewRepositorySpec(ocireg.NewRepositorySpec(host), meta), nil +} + +func HandleRef(u *cpi.UniformRepositorySpec) error { + if u.Host == "" && u.Info != "" && u.SubPath == "" { + info := u.Info + scheme := "" + match := grammar.AnchoredSchemedRegexp.FindStringSubmatch(info) + if match != nil { + scheme = match[1] + info = match[2] + } + host := "" + subp := "" + idx := strings.Index(info, grammar.RepositorySeparator) + if idx > 0 { + host = info[:idx] + subp = info[idx+1:] + } else { + host = info + } + if grammar.HostPortRegexp.MatchString(host) || grammar.DomainPortRegexp.MatchString(host) { + u.Scheme = scheme + u.Host = host + u.SubPath = subp + u.Info = "" + } + } + return nil +} diff --git a/api/ocm/extensions/repositories/init.go b/api/ocm/extensions/repositories/init.go new file mode 100644 index 000000000..5471dab2e --- /dev/null +++ b/api/ocm/extensions/repositories/init.go @@ -0,0 +1,7 @@ +package repositories + +import ( + _ "ocm.software/ocm/api/ocm/extensions/repositories/comparch" + _ "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + _ "ocm.software/ocm/api/ocm/extensions/repositories/genericocireg" +) diff --git a/pkg/contexts/ocm/repositories/ocireg/README.md b/api/ocm/extensions/repositories/ocireg/README.md similarity index 100% rename from pkg/contexts/ocm/repositories/ocireg/README.md rename to api/ocm/extensions/repositories/ocireg/README.md diff --git a/pkg/contexts/ocm/repositories/ocireg/specnorm.go b/api/ocm/extensions/repositories/ocireg/specnorm.go similarity index 79% rename from pkg/contexts/ocm/repositories/ocireg/specnorm.go rename to api/ocm/extensions/repositories/ocireg/specnorm.go index 7be83b465..e47f95cc8 100644 --- a/pkg/contexts/ocm/repositories/ocireg/specnorm.go +++ b/api/ocm/extensions/repositories/ocireg/specnorm.go @@ -3,8 +3,8 @@ package ocireg import ( "strings" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/genericocireg" + "ocm.software/ocm/api/oci/extensions/repositories/ocireg" + "ocm.software/ocm/api/ocm/extensions/repositories/genericocireg" ) func init() { diff --git a/pkg/contexts/ocm/repositories/ocireg/specnorm_test.go b/api/ocm/extensions/repositories/ocireg/specnorm_test.go similarity index 95% rename from pkg/contexts/ocm/repositories/ocireg/specnorm_test.go rename to api/ocm/extensions/repositories/ocireg/specnorm_test.go index e20994ed9..c82a3e078 100644 --- a/pkg/contexts/ocm/repositories/ocireg/specnorm_test.go +++ b/api/ocm/extensions/repositories/ocireg/specnorm_test.go @@ -5,8 +5,8 @@ import ( . "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/repositories/ocireg" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" ) var _ = Describe("ref parsing", func() { diff --git a/pkg/contexts/ocm/repositories/ocireg/suite_test.go b/api/ocm/extensions/repositories/ocireg/suite_test.go similarity index 100% rename from pkg/contexts/ocm/repositories/ocireg/suite_test.go rename to api/ocm/extensions/repositories/ocireg/suite_test.go diff --git a/api/ocm/extensions/repositories/ocireg/type.go b/api/ocm/extensions/repositories/ocireg/type.go new file mode 100644 index 000000000..77d4e2e9f --- /dev/null +++ b/api/ocm/extensions/repositories/ocireg/type.go @@ -0,0 +1,43 @@ +package ocireg + +import ( + "ocm.software/ocm/api/oci/extensions/repositories/ocireg" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/repositories/genericocireg" + "ocm.software/ocm/api/utils" +) + +// ComponentNameMapping describes the method that is used to map the "Component Name", "Component Version"-tuples +// to OCI Image References. +type ComponentNameMapping = genericocireg.ComponentNameMapping + +const ( + Type = ocireg.Type + TypeV1 = ocireg.TypeV1 + + OCIRegistryURLPathMapping ComponentNameMapping = "urlPath" + OCIRegistryDigestMapping ComponentNameMapping = "sha256-digest" +) + +// ComponentRepositoryMeta describes config special for a mapping of +// a component repository to an oci registry. +type ComponentRepositoryMeta = genericocireg.ComponentRepositoryMeta + +// RepositorySpec describes a component repository backed by a oci registry. +type RepositorySpec = genericocireg.RepositorySpec + +// NewRepositorySpec creates a new RepositorySpec. +// If no ocm meta is given, the subPath part is extracted from the base URL. +// Otherwise, the given URL is used as OCI registry URL as it is. +func NewRepositorySpec(baseURL string, metas ...*ComponentRepositoryMeta) *RepositorySpec { + return genericocireg.NewRepositorySpec(ocireg.NewRepositorySpec(baseURL), utils.Optional(metas...)) +} + +func NewComponentRepositoryMeta(subPath string, mapping ...ComponentNameMapping) *ComponentRepositoryMeta { + return genericocireg.NewComponentRepositoryMeta(subPath, utils.OptionalDefaulted(OCIRegistryURLPathMapping, mapping...)) +} + +func NewRepository(ctx cpi.ContextProvider, baseURL string, metas ...*ComponentRepositoryMeta) (cpi.Repository, error) { + spec := NewRepositorySpec(baseURL, metas...) + return ctx.OCMContext().RepositoryForSpec(spec) +} diff --git a/api/ocm/extensions/repositories/virtual/access.go b/api/ocm/extensions/repositories/virtual/access.go new file mode 100644 index 000000000..70305adb5 --- /dev/null +++ b/api/ocm/extensions/repositories/virtual/access.go @@ -0,0 +1,34 @@ +package virtual + +import ( + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" +) + +type VersionAccess interface { + GetDescriptor() *compdesc.ComponentDescriptor + GetBlob(name string) (cpi.DataAccess, error) + AddBlob(blob cpi.BlobAccess) (string, error) + Update() error + Close() error + + IsReadOnly() bool + SetReadOnly() +} + +type Access interface { + ComponentLister() cpi.ComponentLister + + ExistsComponentVersion(name string, version string) (bool, error) + ListVersions(comp string) ([]string, error) + + GetComponentVersion(comp, version string) (VersionAccess, error) + + IsReadOnly() bool + SetReadOnly() + Close() error +} + +type RepositorySpecProvider interface { + GetSpecification() cpi.RepositorySpec +} diff --git a/api/ocm/extensions/repositories/virtual/accessmethod_localblob.go b/api/ocm/extensions/repositories/virtual/accessmethod_localblob.go new file mode 100644 index 000000000..96b88b75a --- /dev/null +++ b/api/ocm/extensions/repositories/virtual/accessmethod_localblob.go @@ -0,0 +1,64 @@ +package virtual + +import ( + "io" + "sync" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +type localBlobAccessMethod struct { + lock sync.Mutex + data blobaccess.DataAccess + spec *localblob.AccessSpec +} + +var _ accspeccpi.AccessMethodImpl = (*localBlobAccessMethod)(nil) + +func newLocalBlobAccessMethod(a *localblob.AccessSpec, data blobaccess.DataAccess) (*localBlobAccessMethod, error) { + return &localBlobAccessMethod{ + spec: a, + data: data, + }, nil +} + +func (_ *localBlobAccessMethod) IsLocal() bool { + return true +} + +func (m *localBlobAccessMethod) GetKind() string { + return m.spec.GetKind() +} + +func (m *localBlobAccessMethod) AccessSpec() accspeccpi.AccessSpec { + return m.spec +} + +func (m *localBlobAccessMethod) Close() error { + m.lock.Lock() + defer m.lock.Unlock() + + if m.data == nil { + return blobaccess.ErrClosed + } + list := errors.ErrorList{} + list.Add(m.data.Close()) + m.data = nil + return list.Result() +} + +func (m *localBlobAccessMethod) Reader() (io.ReadCloser, error) { + return m.data.Reader() +} + +func (m *localBlobAccessMethod) Get() (data []byte, ferr error) { + return blobaccess.BlobData(m.data) +} + +func (m *localBlobAccessMethod) MimeType() string { + return m.spec.MediaType +} diff --git a/api/ocm/extensions/repositories/virtual/component.go b/api/ocm/extensions/repositories/virtual/component.go new file mode 100644 index 000000000..511f55bbf --- /dev/null +++ b/api/ocm/extensions/repositories/virtual/component.go @@ -0,0 +1,84 @@ +package virtual + +import ( + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/general" + + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/cpi/repocpi" +) + +type componentAccessImpl struct { + bridge repocpi.ComponentAccessBridge + + repo *RepositoryImpl + name string +} + +var _ repocpi.ComponentAccessImpl = (*componentAccessImpl)(nil) + +func newComponentAccess(repo *RepositoryImpl, name string, main bool) (*repocpi.ComponentAccessInfo, error) { + impl := &componentAccessImpl{ + repo: repo, + name: name, + } + return &repocpi.ComponentAccessInfo{impl, "OCM component[Simple]", main}, nil +} + +func (c *componentAccessImpl) Close() error { + return nil +} + +func (c *componentAccessImpl) SetBridge(base repocpi.ComponentAccessBridge) { + c.bridge = base +} + +func (c *componentAccessImpl) GetParentBridge() repocpi.RepositoryViewManager { + return c.repo.bridge +} + +func (c *componentAccessImpl) GetContext() cpi.Context { + return c.repo.GetContext() +} + +func (c *componentAccessImpl) GetName() string { + return c.name +} + +func (c *componentAccessImpl) ListVersions() ([]string, error) { + return c.repo.access.ListVersions(c.name) +} + +func (c *componentAccessImpl) HasVersion(vers string) (bool, error) { + return c.repo.ExistsComponentVersion(c.name, vers) +} + +func (c *componentAccessImpl) IsReadOnly() bool { + return c.repo.access.IsReadOnly() +} + +func (c *componentAccessImpl) LookupVersion(version string) (*repocpi.ComponentVersionAccessInfo, error) { + ok, err := c.HasVersion(version) + if err != nil { + return nil, err + } + if !ok { + return nil, cpi.ErrComponentVersionNotFoundWrap(err, c.name, version) + } + return newComponentVersionAccess(c, version, true) +} + +func (c *componentAccessImpl) NewVersion(version string, overrides ...bool) (*repocpi.ComponentVersionAccessInfo, error) { + override := general.Optional(overrides...) + ok, err := c.HasVersion(version) + if err == nil && ok { + if override { + return newComponentVersionAccess(c, version, false) + } + return nil, errors.ErrAlreadyExists(cpi.KIND_COMPONENTVERSION, c.name+"/"+version) + } + if err != nil && !errors.IsErrNotFoundKind(err, cpi.KIND_COMPONENTVERSION) { + return nil, err + } + return newComponentVersionAccess(c, version, false) +} diff --git a/api/ocm/extensions/repositories/virtual/componentversion.go b/api/ocm/extensions/repositories/virtual/componentversion.go new file mode 100644 index 000000000..8382a1082 --- /dev/null +++ b/api/ocm/extensions/repositories/virtual/componentversion.go @@ -0,0 +1,154 @@ +package virtual + +import ( + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/cpi/repocpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localfsblob" + ocmhdlr "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/ocm" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/errkind" + "ocm.software/ocm/api/utils/refmgmt" +) + +// newComponentVersionAccess creates a component access for the artifact access, if this fails the artifact acess is closed. +func newComponentVersionAccess(comp *componentAccessImpl, version string, persistent bool) (*repocpi.ComponentVersionAccessInfo, error) { + access, err := comp.repo.access.GetComponentVersion(comp.GetName(), version) + if err != nil { + return nil, err + } + c, err := newComponentVersionContainer(comp, version, access) + if err != nil { + return nil, err + } + return &repocpi.ComponentVersionAccessInfo{c, true, persistent}, nil +} + +// ////////////////////////////////////////////////////////////////////////////// + +type ComponentVersionContainer struct { + bridge repocpi.ComponentVersionAccessBridge + + comp *componentAccessImpl + version string + access VersionAccess +} + +var _ repocpi.ComponentVersionAccessImpl = (*ComponentVersionContainer)(nil) + +func newComponentVersionContainer(comp *componentAccessImpl, version string, access VersionAccess) (*ComponentVersionContainer, error) { + return &ComponentVersionContainer{ + comp: comp, + version: version, + access: access, + }, nil +} + +func (c *ComponentVersionContainer) SetBridge(base repocpi.ComponentVersionAccessBridge) { + c.bridge = base +} + +func (c *ComponentVersionContainer) GetParentBridge() repocpi.ComponentAccessBridge { + return c.comp.bridge +} + +func (c *ComponentVersionContainer) Close() error { + if c.access == nil { + return accessio.ErrClosed + } + a := c.access + c.access = nil + return a.Close() +} + +func (c *ComponentVersionContainer) Check() error { + if c.version != c.GetDescriptor().Version { + return errors.ErrInvalid("component version", c.GetDescriptor().Version) + } + if c.comp.name != c.GetDescriptor().Name { + return errors.ErrInvalid("component name", c.GetDescriptor().Name) + } + return nil +} + +func (c *ComponentVersionContainer) Repository() cpi.Repository { + return c.comp.repo.nonref +} + +func (c *ComponentVersionContainer) GetContext() cpi.Context { + return c.comp.GetContext() +} + +func (c *ComponentVersionContainer) IsReadOnly() bool { + return c.access.IsReadOnly() +} + +func (c *ComponentVersionContainer) SetReadOnly() { + c.access.SetReadOnly() +} + +func (c *ComponentVersionContainer) IsClosed() bool { + return c.access == nil +} + +func (c *ComponentVersionContainer) AccessMethod(a cpi.AccessSpec, cv refmgmt.ExtendedAllocatable) (cpi.AccessMethod, error) { + accessSpec, err := c.comp.GetContext().AccessSpecForSpec(a) + if err != nil { + return nil, err + } + + switch a.GetKind() { // to be extended + case localfsblob.Type: + fallthrough + case localblob.Type: + blob, err := c.access.GetBlob(accessSpec.(*localblob.AccessSpec).LocalReference) + if err != nil { + return nil, err + } + + return accspeccpi.AccessMethodForImplementation(newLocalBlobAccessMethod(accessSpec.(*localblob.AccessSpec), blob)) + } + + return nil, errors.ErrNotSupported(errkind.KIND_ACCESSMETHOD, a.GetType(), "virtual registry") +} + +func (c *ComponentVersionContainer) Update() error { + return c.access.Update() +} + +func (c *ComponentVersionContainer) SetDescriptor(cd *compdesc.ComponentDescriptor) error { + cur := c.access.GetDescriptor() + *cur = *cd + return c.access.Update() +} + +func (c *ComponentVersionContainer) GetDescriptor() *compdesc.ComponentDescriptor { + return c.access.GetDescriptor() +} + +func (c *ComponentVersionContainer) GetBlob(name string) (cpi.DataAccess, error) { + return c.access.GetBlob(name) +} + +func (c *ComponentVersionContainer) GetStorageContext() cpi.StorageContext { + return ocmhdlr.New(c.Repository(), c.comp.GetName(), c.access, Type, c.access) +} + +func (c *ComponentVersionContainer) AddBlob(blob cpi.BlobAccess, refName string, global cpi.AccessSpec) (cpi.AccessSpec, error) { + if c.IsReadOnly() { + return nil, accessio.ErrReadOnly + } + if blob == nil { + return nil, errors.New("a resource has to be defined") + } + + ref, err := c.access.AddBlob(blob) + if err != nil { + return nil, err + } + return localblob.New(ref, refName, blob.MimeType(), global), nil +} diff --git a/pkg/contexts/ocm/repositories/virtual/example/doc.go b/api/ocm/extensions/repositories/virtual/example/doc.go similarity index 100% rename from pkg/contexts/ocm/repositories/virtual/example/doc.go rename to api/ocm/extensions/repositories/virtual/example/doc.go diff --git a/pkg/contexts/ocm/repositories/virtual/example/example.go b/api/ocm/extensions/repositories/virtual/example/example.go similarity index 92% rename from pkg/contexts/ocm/repositories/virtual/example/example.go rename to api/ocm/extensions/repositories/virtual/example/example.go index 7a8b11c21..e54cbf7ca 100644 --- a/pkg/contexts/ocm/repositories/virtual/example/example.go +++ b/api/ocm/extensions/repositories/virtual/example/example.go @@ -13,12 +13,12 @@ import ( "github.com/mandelsoft/vfs/pkg/projectionfs" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/blobaccess/file" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/common/accessio" - "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/repositories/virtual" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/repositories/virtual" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/blobaccess/file" + common "ocm.software/ocm/api/utils/misc" ) //////////////////////////////////////////////////////////////////////////////// diff --git a/api/ocm/extensions/repositories/virtual/index.go b/api/ocm/extensions/repositories/virtual/index.go new file mode 100644 index 000000000..bb3092ea2 --- /dev/null +++ b/api/ocm/extensions/repositories/virtual/index.go @@ -0,0 +1,108 @@ +package virtual + +import ( + "sync" + + "github.com/mandelsoft/goutils/errors" + + ocicpi "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils" + common "ocm.software/ocm/api/utils/misc" +) + +type IndexEntry[I interface{}] struct { + cd *compdesc.ComponentDescriptor + info I +} + +func (i *IndexEntry[I]) CD() *compdesc.ComponentDescriptor { + if i == nil { + return nil + } + return i.cd +} + +func (i *IndexEntry[I]) Info() I { + var zero I + if i == nil { + return zero + } + return i.info +} + +type Index[I interface{}] struct { + lock sync.Mutex + descriptors map[string]map[string]*IndexEntry[I] +} + +func NewIndex[I interface{}]() *Index[I] { + return &Index[I]{descriptors: map[string]map[string]*IndexEntry[I]{}} +} + +func (i *Index[I]) NumComponents(prefix string) (int, error) { + i.lock.Lock() + defer i.lock.Unlock() + + list := ocicpi.FilterByNamespacePrefix(prefix, utils.StringMapKeys(i.descriptors)) + return len(list), nil +} + +func (i *Index[I]) GetComponents(prefix string, closure bool) ([]string, error) { + i.lock.Lock() + defer i.lock.Unlock() + + return ocicpi.FilterChildren(closure, prefix, utils.StringMapKeys(i.descriptors)), nil +} + +func (i *Index[I]) GetVersions(comp string) []string { + i.lock.Lock() + defer i.lock.Unlock() + + vers := i.descriptors[comp] + if len(vers) == 0 { + return []string{} + } + return utils.StringMapKeys(vers) +} + +func (i *Index[I]) Get(comp, vers string) *IndexEntry[I] { + i.lock.Lock() + defer i.lock.Unlock() + + var e *IndexEntry[I] + set := i.descriptors[comp] + if len(vers) != 0 { + e = set[vers] + } + return e +} + +func (i *Index[I]) Add(cd *compdesc.ComponentDescriptor, info I) error { + i.lock.Lock() + defer i.lock.Unlock() + + set := i.descriptors[cd.Name] + if set == nil { + set = map[string]*IndexEntry[I]{} + i.descriptors[cd.Name] = set + } + if set[cd.Version] != nil { + return errors.ErrAlreadyExists(cpi.KIND_COMPONENTVERSION, common.VersionedElementKey(cd).String()) + } + set[cd.Version] = &IndexEntry[I]{cd, info} + return nil +} + +func (i *Index[I]) Set(cd *compdesc.ComponentDescriptor, info I) { + i.lock.Lock() + defer i.lock.Unlock() + + set := i.descriptors[cd.Name] + if set == nil { + set = map[string]*IndexEntry[I]{} + i.descriptors[cd.Name] = set + } + set[cd.Version] = &IndexEntry[I]{cd, info} +} diff --git a/api/ocm/extensions/repositories/virtual/repo_test.go b/api/ocm/extensions/repositories/virtual/repo_test.go new file mode 100644 index 000000000..19fc576fc --- /dev/null +++ b/api/ocm/extensions/repositories/virtual/repo_test.go @@ -0,0 +1,130 @@ +package virtual_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + . "ocm.software/ocm/api/helper/env" + + "github.com/mandelsoft/goutils/finalizer" + "github.com/mandelsoft/vfs/pkg/layerfs" + "github.com/mandelsoft/vfs/pkg/memoryfs" + "github.com/mandelsoft/vfs/pkg/projectionfs" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/compose" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/attrs/compositionmodeattr" + "ocm.software/ocm/api/ocm/extensions/repositories/virtual" + "ocm.software/ocm/api/ocm/extensions/repositories/virtual/example" + ocmutils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/mime" +) + +var _ = Describe("virtual repo", func() { + var env *Builder + var repo ocm.Repository + var access *example.Access + + // ocmlog.Context().AddRule(logging.NewConditionRule(logging.TraceLevel, accessio.ALLOC_REALM)) + + AfterEach(func() { + MustBeSuccessful(repo.Close()) + env.Cleanup() + }) + + Context("readonly", func() { + BeforeEach(func() { + env = NewBuilder(TestData()) + access = Must(example.NewAccess(Must(projectionfs.New(env, "testdata")), true)) + repo = virtual.NewRepository(env.OCMContext(), access) + }) + + It("handles list", func() { + lister := repo.ComponentLister() + Expect(lister).NotTo(BeNil()) + names := Must(lister.GetComponents("", true)) + Expect(names).To(ConsistOf([]string{"acme.org/component", "acme.org/component/ref"})) + }) + + It("handles get", func() { + comp := Must(repo.LookupComponent("acme.org/component")) + defer Close(comp, "component") + Expect(comp.ListVersions()).To(ConsistOf([]string{"v1.0.0"})) + Expect(comp.HasVersion("v1.0.0")).To(BeTrue()) + Expect(comp.HasVersion("v1.0.1")).To(BeFalse()) + vers := Must(comp.LookupVersion("v1.0.0")) + defer Close(vers, "version") + r := Must(vers.GetResourceByIndex(0)) + data := Must(ocmutils.GetResourceData(r)) + Expect(string(data)).To(Equal("my test data\n")) + + a := Must(r.Access()) + Expect(a.GetVersion()).To(Equal("v1")) + }) + }) + + Context("modifiable", func() { + BeforeEach(func() { + env = NewBuilder(TestData()) + + fs := Must(projectionfs.New(env, "testdata")) + fs = layerfs.New(memoryfs.New(), fs) + access = Must(example.NewAccess(fs, false)) + repo = virtual.NewRepository(env.OCMContext(), access) + }) + + DescribeTable("handles put", func(mode bool, typ string) { + var finalize finalizer.Finalizer + defer Defer(finalize.Finalize) + + compositionmodeattr.Set(env.OCMContext(), mode) + comp := Must(repo.LookupComponent("acme.org/component/new")) + finalize.Close(comp, "component") + Expect(comp.ListVersions()).To(ConsistOf([]string{})) + vers := Must(comp.NewVersion("v1.0.0", false)) + finalize.Close(vers, "version") + + blob := blobaccess.ForString(mime.MIME_TEXT, "new test data") + MustBeSuccessful(vers.SetResourceBlob(compdesc.NewResourceMeta("new", resourcetypes.PLAIN_TEXT, metav1.LocalRelation), blob, "", nil)) + + r := Must(vers.GetResourceByIndex(0)) + a := Must(r.Access()) + Expect(a.GetKind()).To(Equal(typ)) + + comp.AddVersion(vers) + r = Must(vers.GetResourceByIndex(0)) // re-read resource from component descriptor. + a = Must(r.Access()) + Expect(a.GetKind()).To(Equal(localblob.Type)) + + dig := "fe81d80611e39a10f1d7d12f98ce0bc6fe745d08fef007d8eebddc0a21d17827" + Expect(a.(*localblob.AccessSpec).LocalReference).To(Equal(dig)) + + MustBeSuccessful(finalize.Finalize()) + + MustBeSuccessful(access.Reset()) + + comp = Must(repo.LookupComponent("acme.org/component/new")) + finalize.Close(comp, "component") + Expect(comp.ListVersions()).To(ConsistOf([]string{"v1.0.0"})) + Expect(comp.HasVersion("v1.0.0")).To(BeTrue()) + Expect(comp.HasVersion("v1.0.1")).To(BeFalse()) + vers = Must(comp.LookupVersion("v1.0.0")) + finalize.Close(vers, "version") + r = Must(vers.GetResourceByIndex(0)) + data := Must(ocmutils.GetResourceData(r)) + Expect(string(data)).To(Equal("new test data")) + + a = Must(r.Access()) + Expect(a.GetVersion()).To(Equal("v1")) + }, + Entry("with direct mode", false, localblob.Type), + Entry("with composition mode", true, compose.Type), + ) + }) +}) diff --git a/api/ocm/extensions/repositories/virtual/repository.go b/api/ocm/extensions/repositories/virtual/repository.go new file mode 100644 index 000000000..ad1fd8ad6 --- /dev/null +++ b/api/ocm/extensions/repositories/virtual/repository.go @@ -0,0 +1,64 @@ +package virtual + +import ( + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/cpi/repocpi" +) + +type RepositoryImpl struct { + bridge repocpi.RepositoryBridge + ctx cpi.Context + access Access + nonref cpi.Repository +} + +var _ repocpi.RepositoryImpl = (*RepositoryImpl)(nil) + +func NewRepository(ctxp cpi.ContextProvider, acc Access) cpi.Repository { + impl := &RepositoryImpl{ + ctx: datacontext.InternalContextRef(ctxp.OCMContext()), + access: acc, + } + return repocpi.NewRepository(impl, "OCM repo[Simple]") +} + +func (r *RepositoryImpl) Close() error { + return r.access.Close() +} + +func (r *RepositoryImpl) IsReadOnly() bool { + return r.access.IsReadOnly() +} + +func (r *RepositoryImpl) SetReadOnly() { + r.access.SetReadOnly() +} + +func (r *RepositoryImpl) SetBridge(base repocpi.RepositoryBridge) { + r.bridge = base + r.nonref = repocpi.NewNoneRefRepositoryView(base) +} + +func (r *RepositoryImpl) GetContext() cpi.Context { + return r.ctx +} + +func (r *RepositoryImpl) GetSpecification() cpi.RepositorySpec { + if p, ok := r.access.(RepositorySpecProvider); ok { + return p.GetSpecification() + } + return NewRepositorySpec(r.access) +} + +func (r *RepositoryImpl) ComponentLister() cpi.ComponentLister { + return r.access.ComponentLister() +} + +func (r *RepositoryImpl) ExistsComponentVersion(name string, version string) (bool, error) { + return r.access.ExistsComponentVersion(name, version) +} + +func (r *RepositoryImpl) LookupComponent(name string) (*repocpi.ComponentAccessInfo, error) { + return newComponentAccess(r, name, true) +} diff --git a/pkg/contexts/ocm/repositories/virtual/suite_test.go b/api/ocm/extensions/repositories/virtual/suite_test.go similarity index 100% rename from pkg/contexts/ocm/repositories/virtual/suite_test.go rename to api/ocm/extensions/repositories/virtual/suite_test.go diff --git a/pkg/contexts/ocm/repositories/virtual/testdata/blobs/blob1 b/api/ocm/extensions/repositories/virtual/testdata/blobs/blob1 similarity index 100% rename from pkg/contexts/ocm/repositories/virtual/testdata/blobs/blob1 rename to api/ocm/extensions/repositories/virtual/testdata/blobs/blob1 diff --git a/pkg/contexts/ocm/repositories/virtual/testdata/blobs/blob2 b/api/ocm/extensions/repositories/virtual/testdata/blobs/blob2 similarity index 100% rename from pkg/contexts/ocm/repositories/virtual/testdata/blobs/blob2 rename to api/ocm/extensions/repositories/virtual/testdata/blobs/blob2 diff --git a/pkg/contexts/ocm/repositories/virtual/testdata/descriptors/cd1.yaml b/api/ocm/extensions/repositories/virtual/testdata/descriptors/cd1.yaml similarity index 100% rename from pkg/contexts/ocm/repositories/virtual/testdata/descriptors/cd1.yaml rename to api/ocm/extensions/repositories/virtual/testdata/descriptors/cd1.yaml diff --git a/pkg/contexts/ocm/repositories/virtual/testdata/descriptors/cd2.yaml b/api/ocm/extensions/repositories/virtual/testdata/descriptors/cd2.yaml similarity index 100% rename from pkg/contexts/ocm/repositories/virtual/testdata/descriptors/cd2.yaml rename to api/ocm/extensions/repositories/virtual/testdata/descriptors/cd2.yaml diff --git a/api/ocm/extensions/repositories/virtual/type.go b/api/ocm/extensions/repositories/virtual/type.go new file mode 100644 index 000000000..3784a2aea --- /dev/null +++ b/api/ocm/extensions/repositories/virtual/type.go @@ -0,0 +1,35 @@ +package virtual + +import ( + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/internal" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + Type = "Virtual" + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +type RepositorySpec struct { + runtime.ObjectVersionedTypedObject + Access Access `json:"-"` +} + +func NewRepositorySpec(acc Access) *RepositorySpec { + return &RepositorySpec{ + ObjectVersionedTypedObject: runtime.NewVersionedTypedObject(Type), + Access: acc, + } +} + +func (r RepositorySpec) AsUniformSpec(context internal.Context) *cpi.UniformRepositorySpec { + return nil +} + +func (r *RepositorySpec) Repository(ctx cpi.Context, credentials credentials.Credentials) (internal.Repository, error) { + return NewRepository(ctx, r.Access), nil +} + +var _ cpi.RepositorySpec = (*RepositorySpec)(nil) diff --git a/pkg/contexts/ocm/extraid/extra_identities.go b/api/ocm/extraid/extra_identities.go similarity index 83% rename from pkg/contexts/ocm/extraid/extra_identities.go rename to api/ocm/extraid/extra_identities.go index 9a2216769..3b171d80c 100644 --- a/pkg/contexts/ocm/extraid/extra_identities.go +++ b/api/ocm/extraid/extra_identities.go @@ -1,7 +1,7 @@ package extraid import ( - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" ) const ( diff --git a/api/ocm/gc_test.go b/api/ocm/gc_test.go new file mode 100644 index 000000000..8799f60f4 --- /dev/null +++ b/api/ocm/gc_test.go @@ -0,0 +1,34 @@ +package ocm_test + +import ( + "runtime" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + me "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/utils/runtimefinalizer" +) + +var _ = Describe("area test", func() { + It("can be garbage collected", func() { + ctx := me.New() + + r := runtimefinalizer.GetRuntimeFinalizationRecorder(ctx) + Expect(r).NotTo(BeNil()) + + runtime.GC() + time.Sleep(time.Second) + ctx.GetType() + Expect(r.Get()).To(BeNil()) + + ctx = nil + for i := 0; i < 100; i++ { + runtime.GC() + time.Sleep(time.Millisecond) + } + + Expect(r.Get()).To(ContainElement(ContainSubstring(me.CONTEXT_TYPE))) + }) +}) diff --git a/pkg/contexts/ocm/grammar/grammar.go b/api/ocm/grammar/grammar.go similarity index 98% rename from pkg/contexts/ocm/grammar/grammar.go rename to api/ocm/grammar/grammar.go index 1fa9e872d..3d54c922e 100644 --- a/pkg/contexts/ocm/grammar/grammar.go +++ b/api/ocm/grammar/grammar.go @@ -3,7 +3,7 @@ package grammar import ( . "github.com/mandelsoft/goutils/regexutils" - "github.com/open-component-model/ocm/pkg/contexts/oci/grammar" + "ocm.software/ocm/api/oci/grammar" ) const ( diff --git a/pkg/contexts/ocm/grammar/grammar_test.go b/api/ocm/grammar/grammar_test.go similarity index 100% rename from pkg/contexts/ocm/grammar/grammar_test.go rename to api/ocm/grammar/grammar_test.go diff --git a/pkg/contexts/ocm/grammar/suite_test.go b/api/ocm/grammar/suite_test.go similarity index 100% rename from pkg/contexts/ocm/grammar/suite_test.go rename to api/ocm/grammar/suite_test.go diff --git a/api/ocm/init.go b/api/ocm/init.go new file mode 100644 index 000000000..e92ae80f2 --- /dev/null +++ b/api/ocm/init.go @@ -0,0 +1,19 @@ +package ocm + +import ( + _ "ocm.software/ocm/api/datacontext/attrs" + _ "ocm.software/ocm/api/oci" + _ "ocm.software/ocm/api/ocm/compdesc/normalizations" + _ "ocm.software/ocm/api/ocm/compdesc/versions" + _ "ocm.software/ocm/api/ocm/config" + _ "ocm.software/ocm/api/ocm/extensions/accessmethods" + _ "ocm.software/ocm/api/ocm/extensions/blobhandler/config" + _ "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers" + _ "ocm.software/ocm/api/ocm/extensions/digester/digesters" + _ "ocm.software/ocm/api/ocm/extensions/download/config" + _ "ocm.software/ocm/api/ocm/extensions/download/handlers" + _ "ocm.software/ocm/api/ocm/extensions/pubsub/providers" + _ "ocm.software/ocm/api/ocm/extensions/pubsub/types" + _ "ocm.software/ocm/api/ocm/extensions/repositories" + _ "ocm.software/ocm/api/ocm/valuemergehandler/handlers" +) diff --git a/api/ocm/interface.go b/api/ocm/interface.go new file mode 100644 index 000000000..b09f9e10a --- /dev/null +++ b/api/ocm/interface.go @@ -0,0 +1,176 @@ +package ocm + +import ( + "context" + + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/cpi/repocpi" + "ocm.software/ocm/api/ocm/internal" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/runtime" +) + +// ErrTempVersion indicates an ignored update in the backend because the +// current version has not yet been added to the repository. +var ErrTempVersion = repocpi.ErrTempVersion + +const ( + KIND_COMPONENT = internal.KIND_COMPONENT + KIND_COMPONENTVERSION = internal.KIND_COMPONENTVERSION + KIND_COMPONENTREFERENCE = "component reference" + KIND_RESOURCE = internal.KIND_RESOURCE + KIND_SOURCE = internal.KIND_SOURCE + KIND_REFERENCE = internal.KIND_REFERENCE + KIND_REPOSITORYSPEC = internal.KIND_REPOSITORYSPEC +) + +const CONTEXT_TYPE = internal.CONTEXT_TYPE + +const CommonTransportFormat = internal.CommonTransportFormat + +type ( + Context = internal.Context + ContextProvider = internal.ContextProvider + LocalContextProvider = internal.LocalContextProvider + ComponentVersionResolver = internal.ComponentVersionResolver + Repository = internal.Repository + RepositorySpecHandlers = internal.RepositorySpecHandlers + RepositorySpecHandler = internal.RepositorySpecHandler + UniformRepositorySpec = internal.UniformRepositorySpec + ComponentLister = internal.ComponentLister + ComponentAccess = internal.ComponentAccess + ComponentVersionAccess = internal.ComponentVersionAccess + AccessSpec = internal.AccessSpec + GenericAccessSpec = internal.GenericAccessSpec + HintProvider = internal.HintProvider + AccessMethod = internal.AccessMethod + AccessType = internal.AccessType + DataAccess = internal.DataAccess + BlobAccess = internal.BlobAccess + AccessProvider = internal.AccessProvider + SourceAccess = internal.SourceAccess + SourceMeta = internal.SourceMeta + ResourceAccess = internal.ResourceAccess + ResourceMeta = internal.ResourceMeta + RepositorySpec = internal.RepositorySpec + GenericRepositorySpec = internal.GenericRepositorySpec + IntermediateRepositorySpecAspect = internal.IntermediateRepositorySpecAspect + RepositoryType = internal.RepositoryType + RepositoryTypeScheme = internal.RepositoryTypeScheme + RepositoryDelegationRegistry = internal.RepositoryDelegationRegistry + AccessTypeScheme = internal.AccessTypeScheme + ComponentReference = internal.ComponentReference +) + +type ( + DigesterType = internal.DigesterType + BlobDigester = internal.BlobDigester + BlobDigesterRegistry = internal.BlobDigesterRegistry + DigestDescriptor = internal.DigestDescriptor + HasherProvider = internal.HasherProvider + Hasher = internal.Hasher +) + +type ( + BlobHandlerRegistry = internal.BlobHandlerRegistry + BlobHandler = internal.BlobHandler + BlobHandlerProvider = internal.BlobHandlerProvider +) + +func NewDigestDescriptor(digest, hashAlgo, normAlgo string) *DigestDescriptor { + return internal.NewDigestDescriptor(digest, hashAlgo, normAlgo) +} + +// DefaultContext is the default context initialized by init functions. +func DefaultContext() internal.Context { + return internal.DefaultContext +} + +// NoComponentVersion provides a dummy component version +// providing access to the context. +// It can be used to instantiate external access methods +// (not based on any component version). +func NoComponentVersion(ctx ContextProvider) ComponentVersionAccess { + return &cpi.DummyComponentVersionAccess{ctx.OCMContext()} +} + +func DefaultBlobHandlers() BlobHandlerRegistry { + return internal.DefaultBlobHandlerRegistry +} + +func DefaultBlobHandlerProvider(ctx Context) BlobHandlerProvider { + return internal.DefaultBlobHandlerProvider(ctx) +} + +func DefaultRepositoryDelegationRegistry() RepositoryDelegationRegistry { + return internal.DefaultRepositoryDelegationRegistry +} + +func NewRepositoryDelegationRegistry(base ...RepositoryDelegationRegistry) RepositoryDelegationRegistry { + return internal.NewDelegationRegistry[Context, RepositorySpec](base...) +} + +// FromContext returns the Context to use for context.Context. +// This is either an explicit context or the default context. +func FromContext(ctx context.Context) Context { + return internal.FromContext(ctx) +} + +func FromProvider(p ContextProvider) Context { + return internal.FromProvider(p) +} + +func DefinedForContext(ctx context.Context) (Context, bool) { + return internal.DefinedForContext(ctx) +} + +func NewGenericAccessSpec(spec string) (AccessSpec, error) { + return internal.NewGenericAccessSpec([]byte(spec)) +} + +func ToGenericAccessSpec(spec AccessSpec) (*GenericAccessSpec, error) { + return internal.ToGenericAccessSpec(spec) +} + +func ToGenericRepositorySpec(spec RepositorySpec) (*GenericRepositorySpec, error) { + return internal.ToGenericRepositorySpec(spec) +} + +func IsNoneAccess(a compdesc.AccessSpec) bool { + return compdesc.IsNoneAccess(a) +} + +func IsNoneAccessKind(k string) bool { + return compdesc.IsNoneAccessKind(k) +} + +type AccessSpecRef = internal.AccessSpecRef + +func NewAccessSpecRef(spec cpi.AccessSpec) *AccessSpecRef { + return internal.NewAccessSpecRef(spec) +} + +func NewRawAccessSpecRef(data []byte, unmarshaler runtime.Unmarshaler) (*AccessSpecRef, error) { + return internal.NewRawAccessSpecRef(data, unmarshaler) +} + +func NewResourceMeta(name string, typ string, relation metav1.ResourceRelation) *ResourceMeta { + return compdesc.NewResourceMeta(name, typ, relation) +} + +func NewSourceMeta(name string, typ string) *SourceMeta { + return compdesc.NewSourceMeta(name, typ) +} + +func NewComponentReference(name, componentName, version string) *ComponentReference { + return compdesc.NewComponentReference(name, componentName, version, nil) +} + +/////////////////////////////////////////////////////// + +func BlobAccessForAccessMethod(m AccessMethod) (blobaccess.AnnotatedBlobAccess[accspeccpi.AccessMethodView], error) { + return accspeccpi.BlobAccessForAccessMethod(m) +} diff --git a/pkg/contexts/ocm/internal/accessspecref.go b/api/ocm/internal/accessspecref.go similarity index 98% rename from pkg/contexts/ocm/internal/accessspecref.go rename to api/ocm/internal/accessspecref.go index 97a7496ec..0a96071b1 100644 --- a/pkg/contexts/ocm/internal/accessspecref.go +++ b/api/ocm/internal/accessspecref.go @@ -6,7 +6,7 @@ import ( "github.com/modern-go/reflect2" - "github.com/open-component-model/ocm/pkg/runtime" + "ocm.software/ocm/api/utils/runtime" ) type AccessSpecRef struct { diff --git a/api/ocm/internal/accesstypes.go b/api/ocm/internal/accesstypes.go new file mode 100644 index 000000000..03613d9ab --- /dev/null +++ b/api/ocm/internal/accesstypes.go @@ -0,0 +1,244 @@ +package internal + +import ( + "encoding/json" + "fmt" + "io" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/general" + "github.com/mandelsoft/goutils/generics" + "github.com/modern-go/reflect2" + + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/utils/cobrautils/flagsets/flagsetscheme" + "ocm.software/ocm/api/utils/errkind" + "ocm.software/ocm/api/utils/refmgmt" + "ocm.software/ocm/api/utils/runtime" +) + +type AccessType flagsetscheme.VersionTypedObjectType[AccessSpec] + +// AccessSpec is the interface access method specifications +// must fulfill. The main task is to map the specification +// to a concrete implementation of the access method for a dedicated +// component version. +type AccessSpec interface { + compdesc.AccessSpec + Describe(Context) string + IsLocal(Context) bool + GlobalAccessSpec(Context) AccessSpec + // AccessMethod provides an access method implementation for + // an access spec. This might be a repository local implementation + // or a global one. It might be implemented directly by the AccessSpec + // for global AccessMethods or forwarded to the ComponentVersion for + // local access methods. It may only be forwarded for AccessSpecs stating + // to be local (IsLocal()==true). + // This forwarding is necessary because the concrete implementation of + // the currently used OCM Repository is not known to the AccessSpec. + AccessMethod(access ComponentVersionAccess) (AccessMethod, error) +} + +type ( + AccessSpecDecoder = runtime.TypedObjectDecoder[AccessSpec] + AccessTypeProvider = runtime.KnownTypesProvider[AccessSpec, AccessType] +) + +// HintProvider is used to provide a reference hint for local access method specs. +// It may optionally be provided by an access spec. +// When adding blobs to a repository the hint is used by blobhandlers for +// expanding a blob to a repository specific representation to determine a +// useful name. +type HintProvider interface { + GetReferenceHint(cv ComponentVersionAccess) string +} + +// GlobalAccessProvider is used to provide a non-local access specification. +// It may optionally be provided by an access spec. +type GlobalAccessProvider interface { + GlobalAccessSpec(ctx Context) AccessSpec +} + +// AccessMethodImpl is the implementation interface +// for access methods provided by access types. It describes +// the access to a dedicated resource +// It can allocate external resources, which should be released +// with the Close() call. +// Resources SHOULD only be allocated, if the content is accessed +// via the DataAccess interface to avoid unnecessary effort +// if the method object is just used to access meta data. +// It is always wrapped by a view model enabling Dup +// operations to pass and keep instances on demand. +type AccessMethodImpl interface { + io.Closer + DataAccess + MimeType + + IsLocal() bool + GetKind() string + AccessSpec() AccessSpec +} + +// AccessMethod is used to support independently closable +// views on an access method implementation, which can +// be passed around and stored. The original method implementation +// object is closed once the last view is closed. +type AccessMethod interface { + refmgmt.Dup[AccessMethod] + AccessMethodImpl + + // AsBlobAccess maps a method object into a + // basic blob access interface. + // It does not provide a separate reference, + // closing the blob access with close the + // access method. + AsBlobAccess() BlobAccess +} + +type AccessTypeScheme flagsetscheme.TypeScheme[AccessSpec, AccessType] + +func NewAccessTypeScheme(base ...AccessTypeScheme) AccessTypeScheme { + return flagsetscheme.NewTypeScheme[AccessSpec, AccessType, AccessTypeScheme]("Access type", "access", "accessType", "blob access specification", "Access Specification Options", &UnknownAccessSpec{}, true, base...) +} + +func NewStrictAccessTypeScheme(base ...AccessTypeScheme) runtime.VersionedTypeRegistry[AccessSpec, AccessType] { + return flagsetscheme.NewTypeScheme[AccessSpec, AccessType, AccessTypeScheme]("Access type", "access", "accessType", "blob access specification", "Access Specification Options", nil, false, base...) +} + +// DefaultAccessTypeScheme contains all globally known access serializer. +var DefaultAccessTypeScheme = NewAccessTypeScheme() + +func RegisterAccessType(atype AccessType) { + DefaultAccessTypeScheme.Register(atype) +} + +func CreateAccessSpec(t runtime.TypedObject) (AccessSpec, error) { + return DefaultAccessTypeScheme.Convert(t) +} + +//////////////////////////////////////////////////////////////////////////////// + +type UnknownAccessSpec struct { + runtime.UnstructuredVersionedTypedObject `json:",inline"` +} + +var ( + _ runtime.TypedObject = &UnknownAccessSpec{} + _ runtime.Unknown = &UnknownAccessSpec{} +) + +func (_ *UnknownAccessSpec) IsUnknown() bool { + return true +} + +func (s *UnknownAccessSpec) AccessMethod(ComponentVersionAccess) (AccessMethod, error) { + return nil, errors.ErrUnknown(errkind.KIND_ACCESSMETHOD, s.GetType()) +} + +func (s *UnknownAccessSpec) Describe(ctx Context) string { + return fmt.Sprintf("unknown access method type %q", s.GetType()) +} + +func (_ *UnknownAccessSpec) IsLocal(Context) bool { + return false +} + +func (_ *UnknownAccessSpec) GlobalAccessSpec(Context) AccessSpec { + return nil +} + +var _ AccessSpec = &UnknownAccessSpec{} + +//////////////////////////////////////////////////////////////////////////////// + +type EvaluatableAccessSpec interface { + AccessSpec + Evaluate(ctx Context) (AccessSpec, error) +} + +type GenericAccessSpec struct { + runtime.UnstructuredVersionedTypedObject `json:",inline"` +} + +var _ AccessSpec = &GenericAccessSpec{} + +func ToGenericAccessSpec(spec AccessSpec) (*GenericAccessSpec, error) { + if reflect2.IsNil(spec) { + return nil, nil + } + if g, ok := spec.(*GenericAccessSpec); ok { + return g, nil + } + data, err := json.Marshal(spec) + if err != nil { + return nil, err + } + return newGenericAccessSpec(data, runtime.DefaultJSONEncoding) +} + +func NewGenericAccessSpec(data []byte, unmarshaler ...runtime.Unmarshaler) (AccessSpec, error) { + return generics.CastPointerR[AccessSpec](newGenericAccessSpec(data, general.Optional(unmarshaler...))) +} + +func newGenericAccessSpec(data []byte, unmarshaler runtime.Unmarshaler) (*GenericAccessSpec, error) { + unstr := &runtime.UnstructuredVersionedTypedObject{} + if unmarshaler == nil { + unmarshaler = runtime.DefaultYAMLEncoding + } + err := unmarshaler.Unmarshal(data, unstr) + if err != nil { + return nil, err + } + return &GenericAccessSpec{*unstr}, nil +} + +func (s *GenericAccessSpec) Describe(ctx Context) string { + eff, err := s.Evaluate(ctx) + if err != nil { + return fmt.Sprintf("invalid access specification: %s", err.Error()) + } + return eff.Describe(ctx) +} + +func (s *GenericAccessSpec) Evaluate(ctx Context) (AccessSpec, error) { + raw, err := s.GetRaw() + if err != nil { + return nil, err + } + return ctx.AccessMethods().Decode(raw, runtime.DefaultJSONEncoding) +} + +func (s *GenericAccessSpec) AccessMethod(acc ComponentVersionAccess) (AccessMethod, error) { + spec, err := s.Evaluate(acc.GetContext()) + if err != nil { + return nil, err + } + if _, ok := spec.(*GenericAccessSpec); ok { + return nil, errors.ErrUnknown(errkind.KIND_ACCESSMETHOD, s.GetType()) + } + return spec.AccessMethod(acc) +} + +func (s *GenericAccessSpec) IsLocal(ctx Context) bool { + spec, err := s.Evaluate(ctx) + if err != nil { + return false + } + if _, ok := spec.(*GenericAccessSpec); ok { + return false + } + return spec.IsLocal(ctx) +} + +func (s *GenericAccessSpec) GlobalAccessSpec(ctx Context) AccessSpec { + spec, err := s.Evaluate(ctx) + if err != nil { + return nil + } + if _, ok := spec.(*GenericAccessSpec); ok { + return nil + } + return spec.GlobalAccessSpec(ctx) +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/api/ocm/internal/blobhandler.go b/api/ocm/internal/blobhandler.go new file mode 100644 index 000000000..5817e1476 --- /dev/null +++ b/api/ocm/internal/blobhandler.go @@ -0,0 +1,472 @@ +package internal + +import ( + "fmt" + "sort" + "strings" + "sync" + + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/registrations" +) + +type ImplementationRepositoryType struct { + ContextType string `json:"contextType,omitempty"` + RepositoryType string `json:"repositoryType,omitempty"` +} + +func (t ImplementationRepositoryType) String() string { + return fmt.Sprintf("%s[%s]", t.RepositoryType, t.ContextType) +} + +func (t ImplementationRepositoryType) IsInitial() bool { + return t.RepositoryType == "" && t.ContextType == "" +} + +// StorageContext is an object describing the storage context used for the +// mapping of a component repository to a base repository (e.g. oci api) +// It depends on the Context type of the used base repository. +type StorageContext interface { + GetContext() Context + TargetComponentName() string + TargetComponentRepository() Repository + GetImplementationRepositoryType() ImplementationRepositoryType +} + +// BlobHandler is the interface for a dedicated handling of storing blobs +// for the LocalBlob access method in a dedicated kind of repository. +// With the possibility of access by an external distribution spec +// (besides of the blob storage as part of a component version). +// The technical repository to use should be derivable from the chosen +// component directory or passed together with the storage context. +// The task of the handler is to store the local blob on its own +// responsibility and to return an appropriate global access method. +type BlobHandler interface { + // StoreBlob has the chance to decide to store a local blob + // in a repository specific fashion providing external access. + // If this is possible and done an appropriate access spec + // must be returned, if this is not done, nil has to be returned + // without error + StoreBlob(blob BlobAccess, artType, hint string, global AccessSpec, ctx StorageContext) (AccessSpec, error) +} + +// MultiBlobHandler is a BlobHandler consisting of a sequence of handlers. +type MultiBlobHandler []BlobHandler + +var _ sort.Interface = MultiBlobHandler(nil) + +func (m MultiBlobHandler) StoreBlob(blob BlobAccess, artType, hint string, global AccessSpec, ctx StorageContext) (AccessSpec, error) { + for _, h := range m { + a, err := h.StoreBlob(blob, artType, hint, global, ctx) + if err != nil { + return nil, err + } + if a != nil { + return a, nil + } + } + return nil, nil +} + +func (m MultiBlobHandler) Len() int { + return len(m) +} + +func (m MultiBlobHandler) Less(i, j int) bool { + pi := DEFAULT_BLOBHANDLER_PRIO + pj := DEFAULT_BLOBHANDLER_PRIO + + if p, ok := m[i].(*PrioBlobHandler); ok { + pi = p.Prio + } + if p, ok := m[j].(*PrioBlobHandler); ok { + pj = p.Prio + } + return pi > pj +} + +func (m MultiBlobHandler) Swap(i, j int) { + m[i], m[j] = m[j], m[i] +} + +//////////////////////////////////////////////////////////////////////////////// + +type BlobHandlerOptions struct { + BlobHandlerKey `json:",inline"` + Priority int `json:"priority,omitempty"` +} + +func NewBlobHandlerOptions(olist ...BlobHandlerOption) *BlobHandlerOptions { + var opts BlobHandlerOptions + for _, o := range olist { + o.ApplyBlobHandlerOptionTo(&opts) + } + return &opts +} + +func (o *BlobHandlerOptions) ApplyBlobHandlerOptionTo(opts *BlobHandlerOptions) { + if o.Priority > 0 { + opts.Priority = o.Priority + } + o.BlobHandlerKey.ApplyBlobHandlerOptionTo(opts) +} + +type BlobHandlerOption interface { + ApplyBlobHandlerOptionTo(*BlobHandlerOptions) +} + +type prio struct { + prio int +} + +func WithPrio(p int) BlobHandlerOption { + return prio{p} +} + +func (o prio) ApplyBlobHandlerOptionTo(opts *BlobHandlerOptions) { + opts.Priority = o.prio +} + +//////////////////////////////////////////////////////////////////////////////// + +// BlobHandlerKey is the registration key for BlobHandlers. +type BlobHandlerKey struct { + ImplementationRepositoryType `json:",inline"` + ArtifactType string `json:"artifactType,omitempty"` + MimeType string `json:"mimeType,omitempty"` +} + +var _ BlobHandlerOption = BlobHandlerKey{} + +func NewBlobHandlerKey(ctxtype, repotype, artifactType, mimetype string) BlobHandlerKey { + return BlobHandlerKey{ + ImplementationRepositoryType: ImplementationRepositoryType{ + ContextType: ctxtype, + RepositoryType: repotype, + }, + ArtifactType: artifactType, + MimeType: mimetype, + } +} + +func (k BlobHandlerKey) ApplyBlobHandlerOptionTo(opts *BlobHandlerOptions) { + if k.ContextType != "" { + opts.ContextType = k.ContextType + } + if k.RepositoryType != "" { + opts.RepositoryType = k.RepositoryType + } + if k.ArtifactType != "" { + opts.ArtifactType = k.ArtifactType + } + if k.MimeType != "" { + opts.MimeType = k.MimeType + } +} + +func ForRepo(ctxtype, repotype string) BlobHandlerOption { + return BlobHandlerKey{ImplementationRepositoryType: ImplementationRepositoryType{ContextType: ctxtype, RepositoryType: repotype}} +} + +func ForMimeType(mimetype string) BlobHandlerOption { + return BlobHandlerKey{MimeType: mimetype} +} + +func ForArtifactType(artifacttype string) BlobHandlerOption { + return BlobHandlerKey{ArtifactType: artifacttype} +} + +//////////////////////////////////////////////////////////////////////////////// + +type ( + BlobHandlerConfig = registrations.HandlerConfig + BlobHandlerRegistrationHandler = registrations.HandlerRegistrationHandler[Context, BlobHandlerOption] + BlobHandlerRegistrationRegistry = registrations.HandlerRegistrationRegistry[Context, BlobHandlerOption] + + RegistrationHandlerInfo = registrations.RegistrationHandlerInfo[Context, BlobHandlerOption] +) + +func NewBlobHandlerRegistrationRegistry(base ...BlobHandlerRegistrationRegistry) BlobHandlerRegistrationRegistry { + return registrations.NewHandlerRegistrationRegistry[Context, BlobHandlerOption](base...) +} + +func NewRegistrationHandlerInfo(path string, handler BlobHandlerRegistrationHandler) *RegistrationHandlerInfo { + return registrations.NewRegistrationHandlerInfo[Context, BlobHandlerOption](path, handler) +} + +//////////////////////////////////////////////////////////////////////////////// + +// BlobHandlerProvider is used to find a blob handler to use adding a resource +// blob to a component version. +// The task of the BlobHandler is to reject the upload, provide an (external) +// access method for the blob or state not to be responsible. +type BlobHandlerProvider interface { + LookupHandler(sctx StorageContext, artifacttype, mimeType string) BlobHandler +} + +type registryBasedProvider struct { + registry BlobHandlerRegistry +} + +func (p *registryBasedProvider) LookupHandler(sctx StorageContext, artifacttype, mimeType string) BlobHandler { + return p.registry.LookupHandler(sctx.GetImplementationRepositoryType(), artifacttype, mimeType) +} + +func BlobHandlerProviderForRegistry(r BlobHandlerRegistry) BlobHandlerProvider { + return ®istryBasedProvider{r} +} + +func DefaultBlobHandlerProvider(ctx Context) BlobHandlerProvider { + return BlobHandlerProviderForRegistry(ctx.BlobHandlers()) +} + +//////////////////////////////////////////////////////////////////////////////// + +// BlobHandlerRegistry registers blob handlers to use in a dedicated ocm context. +type BlobHandlerRegistry interface { + AsHandlerRegistrationRegistry() registrations.HandlerRegistrationRegistry[Context, BlobHandlerOption] + + // BlobHandlerRegistrationRegistry + registrations.HandlerRegistrationRegistryAccess[Context, BlobHandlerOption] + + IsInitial() bool + + // Copy provides a new independend copy of the registry. + Copy() BlobHandlerRegistry + // RegisterBlobHandler registers a blob handler. It must specify either a sole mime type, + // or a context and repository type, or all three keys. + Register(handler BlobHandler, opts ...BlobHandlerOption) BlobHandlerRegistry + + // GetHandler returns the handler with the given key. + GetHandler(key BlobHandlerKey) BlobHandler + + // LookupHandler returns handler trying all matches in the following order: + // + // - a handler matching all keys + // - handlers matching the repo and mime type (from specific to more general by discarding + components) + // - with artifact type + // - without artifact type + // - handlers matching artifact type + // - handlers matching a sole mimetype handler (from specific to more general by discarding + components) + // - a handler matching the repo + // + LookupHandler(repotype ImplementationRepositoryType, artifacttype, mimeType string) BlobHandler +} + +func AsHandlerRegistrationRegistry(r BlobHandlerRegistry) registrations.HandlerRegistrationRegistry[Context, BlobHandlerOption] { + if r == nil { + return nil + } + return r.AsHandlerRegistrationRegistry() +} + +const DEFAULT_BLOBHANDLER_PRIO = 100 + +type PrioBlobHandler struct { + BlobHandler + Prio int +} + +type handlerCache struct { + cache map[BlobHandlerKey]BlobHandler +} + +func newHandlerCache() *handlerCache { + return &handlerCache{map[BlobHandlerKey]BlobHandler{}} +} + +func (c *handlerCache) len() int { + return len(c.cache) +} + +func (c *handlerCache) get(key BlobHandlerKey) (BlobHandler, bool) { + h, ok := c.cache[key] + return h, ok +} + +func (c *handlerCache) set(key BlobHandlerKey, h BlobHandler) { + c.cache[key] = h +} + +type blobHandlerRegistry struct { + lock sync.RWMutex + base BlobHandlerRegistry + handlers map[BlobHandlerKey]BlobHandler + defhandler MultiBlobHandler + + // (should be) BlobHandlerRegistrationRegistry , but does not work with GoLand up to at least 2022.2.6 + registrations.HandlerRegistrationRegistry[Context, BlobHandlerOption] + + cache *handlerCache +} + +var DefaultBlobHandlerRegistry = NewBlobHandlerRegistry() + +func NewBlobHandlerRegistry(base ...BlobHandlerRegistry) BlobHandlerRegistry { + b := utils.Optional(base...) + r := &blobHandlerRegistry{ + base: b, + handlers: map[BlobHandlerKey]BlobHandler{}, + HandlerRegistrationRegistry: NewBlobHandlerRegistrationRegistry(AsHandlerRegistrationRegistry(b)), + cache: newHandlerCache(), + } + return r +} + +func (r *blobHandlerRegistry) AsHandlerRegistrationRegistry() registrations.HandlerRegistrationRegistry[Context, BlobHandlerOption] { + return r.HandlerRegistrationRegistry +} + +func (r *blobHandlerRegistry) Copy() BlobHandlerRegistry { + r.lock.RLock() + defer r.lock.RUnlock() + n := NewBlobHandlerRegistry(r.base).(*blobHandlerRegistry) + n.defhandler = append(n.defhandler, r.defhandler...) + for k, h := range r.handlers { + n.handlers[k] = h + } + return n +} + +func (r *blobHandlerRegistry) IsInitial() bool { + if r.base != nil && !r.base.IsInitial() { + return false + } + return len(r.handlers) == 0 && len(r.defhandler) == 0 +} + +func (r *blobHandlerRegistry) Register(handler BlobHandler, olist ...BlobHandlerOption) BlobHandlerRegistry { + opts := NewBlobHandlerOptions(olist...) + r.lock.Lock() + defer r.lock.Unlock() + + def := BlobHandlerKey{} + + if opts.Priority != 0 { + handler = &PrioBlobHandler{handler, opts.Priority} + } + if opts.BlobHandlerKey == def { + r.defhandler = append(r.defhandler, handler) + } else { + r.handlers[opts.BlobHandlerKey] = handler + } + if r.cache.len() > 0 { + r.cache = newHandlerCache() + } + return r +} + +func (r *blobHandlerRegistry) forMimeType(ctxtype, repotype, artifacttype, mimetype string) MultiBlobHandler { + var multi MultiBlobHandler + + mime := mimetype + for { + if h := r.getHandler(NewBlobHandlerKey(ctxtype, repotype, artifacttype, mime)); h != nil { + multi = append(multi, h) + } + idx := strings.LastIndex(mime, "+") + if idx < 0 { + break + } + mime = mime[:idx] + } + return multi +} + +func (r *blobHandlerRegistry) GetHandler(key BlobHandlerKey) BlobHandler { + r.lock.RLock() + defer r.lock.RUnlock() + return r.getHandler(key) +} + +func (r *blobHandlerRegistry) getHandler(key BlobHandlerKey) BlobHandler { + def := BlobHandlerKey{} + + if key == def { + if len(r.defhandler) > 0 { + return r.defhandler + } + } + h := r.handlers[key] + if h != nil { + return h + } + if r.base != nil { + return r.base.GetHandler(key) + } + return nil +} + +func (r *blobHandlerRegistry) LookupHandler(repotype ImplementationRepositoryType, artifacttype, mimetype string) BlobHandler { + key := BlobHandlerKey{ + ImplementationRepositoryType: repotype, + ArtifactType: artifacttype, + MimeType: mimetype, + } + h, cache := r.lookupHandler(key) + if cache != nil { + r.lock.Lock() + defer r.lock.Unlock() + // fill cache, if unchanged during pseudo lock upgrade (no support in go sync package for that). + // if cache has been renewed in the meantime, just use the old outdated result, but don't update. + if r.cache == cache { + r.cache.set(key, h) + } + } + return h +} + +func (r *blobHandlerRegistry) lookupHandler(key BlobHandlerKey) (BlobHandler, *handlerCache) { + r.lock.RLock() + defer r.lock.RUnlock() + + if h, ok := r.cache.get(key); ok { + return h, nil + } + var multi MultiBlobHandler + if !key.ImplementationRepositoryType.IsInitial() { + multi = append(multi, r.forMimeType(key.ContextType, key.RepositoryType, key.ArtifactType, key.MimeType)...) + if key.MimeType != "" { + multi = append(multi, r.forMimeType(key.ContextType, key.RepositoryType, key.ArtifactType, "")...) + } + if key.ArtifactType != "" { + multi = append(multi, r.forMimeType(key.ContextType, key.RepositoryType, "", key.MimeType)...) + } + } + multi = append(multi, r.forMimeType("", "", key.ArtifactType, key.MimeType)...) + if key.MimeType != "" { + multi = append(multi, r.forMimeType("", "", key.ArtifactType, "")...) + } + if key.ArtifactType != "" { + multi = append(multi, r.forMimeType("", "", "", key.MimeType)...) + } + if !key.ImplementationRepositoryType.IsInitial() && key.ArtifactType != "" && key.MimeType != "" { + multi = append(multi, r.forMimeType(key.ContextType, key.RepositoryType, "", "")...) + } + + def := r.getHandler(BlobHandlerKey{}) + if def != nil { + if m, ok := def.(MultiBlobHandler); ok { + multi = append(multi, m...) + } else { + multi = append(multi, def) + } + } + if len(multi) == 0 { + return nil, r.cache + } + sort.Stable(multi) + return multi, r.cache +} + +func RegisterBlobHandler(handler BlobHandler, opts ...BlobHandlerOption) { + DefaultBlobHandlerRegistry.Register(handler, opts...) +} + +func MustRegisterBlobHandler(handler BlobHandler, opts ...BlobHandlerOption) { + DefaultBlobHandlerRegistry.Register(handler, opts...) +} + +func RegisterBlobHandlerRegistrationHandler(path string, handler BlobHandlerRegistrationHandler) { + DefaultBlobHandlerRegistry.RegisterRegistrationHandler(path, handler) +} diff --git a/api/ocm/internal/blobhandler_test.go b/api/ocm/internal/blobhandler_test.go new file mode 100644 index 000000000..30dd26df8 --- /dev/null +++ b/api/ocm/internal/blobhandler_test.go @@ -0,0 +1,244 @@ +package internal_test + +import ( + "fmt" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/ocm/internal" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/api/utils/registrations" +) + +const REPO = "repo" + +var ( + IMPL = internal.ImplementationRepositoryType{internal.CONTEXT_TYPE, REPO} + ART = "myType" +) + +type BlobHandler struct { + name string +} + +var _ internal.BlobHandler = (*BlobHandler)(nil) + +func (b BlobHandler) StoreBlob(blob internal.BlobAccess, artType string, hint string, global internal.AccessSpec, ctx internal.StorageContext) (internal.AccessSpec, error) { + return nil, fmt.Errorf(b.name) +} + +type TestRegistrationHandler struct { + name string + registered map[string]interface{} +} + +func NewTestRegistrationHandler(name string) *TestRegistrationHandler { + return &TestRegistrationHandler{ + name: name, + registered: map[string]interface{}{}, + } +} + +func (t *TestRegistrationHandler) RegisterByName(handler string, ctx internal.Context, config internal.BlobHandlerConfig, opts ...internal.BlobHandlerOption) (bool, error) { + path := registrations.NewNamePath(handler) + if len(path) < 1 || path[0] != "match" { + return false, nil + } + t.registered[handler] = nil + return true, nil +} + +func (t *TestRegistrationHandler) GetHandlers(ctx internal.Context) registrations.HandlerInfos { + return nil +} + +var _ = Describe("blob handler registry test", func() { + Context("registration registry", func() { + var reg internal.BlobHandlerRegistrationRegistry + + var ha *TestRegistrationHandler + var hab *TestRegistrationHandler + var habc *TestRegistrationHandler + var habd *TestRegistrationHandler + var habe *TestRegistrationHandler + var hb *TestRegistrationHandler + + BeforeEach(func() { + reg = internal.NewBlobHandlerRegistrationRegistry() + ha = NewTestRegistrationHandler("a") + hab = NewTestRegistrationHandler("a/b") + habc = NewTestRegistrationHandler("a/b/c") + habd = NewTestRegistrationHandler("a/b/d") + habe = NewTestRegistrationHandler("a/b/e") + hb = NewTestRegistrationHandler("b") + }) + + It("registers ordered prefixes", func() { + reg.RegisterRegistrationHandler("a", ha) + reg.RegisterRegistrationHandler("a/b/d", habd) + reg.RegisterRegistrationHandler("a/b/c", habc) + reg.RegisterRegistrationHandler("a/b/e", habe) + reg.RegisterRegistrationHandler("a/b", hab) + reg.RegisterRegistrationHandler("b", hb) + + Expect(reg.GetRegistrationHandlers("a/b/c")).To(Equal([]*internal.RegistrationHandlerInfo{ + internal.NewRegistrationHandlerInfo("a/b/c", habc), + internal.NewRegistrationHandlerInfo("a/b", hab), + internal.NewRegistrationHandlerInfo("a", ha), + })) + }) + + It("registers ordered prefixes", func() { + reg.RegisterRegistrationHandler("a", ha) + reg.RegisterRegistrationHandler("a/b/d", habd) + reg.RegisterRegistrationHandler("a/b/c", habc) + reg.RegisterRegistrationHandler("a/b/e", habe) + reg.RegisterRegistrationHandler("a/b", hab) + reg.RegisterRegistrationHandler("b", hb) + + _, err := reg.RegisterByName("a/b/c/d", nil, nil) + MustFailWithMessage(err, "no registration handler found for a/b/c/d") + Expect(Must(reg.RegisterByName("a/b/c/match/d", nil, nil))).To(BeTrue()) + Expect(Must(reg.RegisterByName("a/b/c/match", nil, nil))).To(BeTrue()) + + Expect(ha.registered).To(Equal(map[string]interface{}{})) + Expect(hb.registered).To(Equal(map[string]interface{}{})) + Expect(hab.registered).To(Equal(map[string]interface{}{})) + Expect(habd.registered).To(Equal(map[string]interface{}{})) + Expect(habe.registered).To(Equal(map[string]interface{}{})) + Expect(habc.registered).To(Equal(map[string]interface{}{"match/d": nil, "match": nil})) + }) + + It("registers ordered prefixes", func() { + reg.RegisterRegistrationHandler("a/b/d", habd) + reg.RegisterRegistrationHandler("a/b/c", habc) + reg.RegisterRegistrationHandler("a/b/e", habe) + reg.RegisterRegistrationHandler("a", ha) + + derived := internal.NewBlobHandlerRegistrationRegistry(reg) + derived.RegisterRegistrationHandler("a/b", hab) + derived.RegisterRegistrationHandler("b", hb) + + list := derived.GetRegistrationHandlers("a/b/e") + Expect(list).To(Equal([]*internal.RegistrationHandlerInfo{ + internal.NewRegistrationHandlerInfo("a/b/e", habe), + internal.NewRegistrationHandlerInfo("a/b", hab), + internal.NewRegistrationHandlerInfo("a", ha), + })) + + _, err := reg.RegisterByName("a/b/e/d", nil, nil) + MustFailWithMessage(err, "no registration handler found for a/b/e/d") + Expect(Must(reg.RegisterByName("a/b/e/match/d", nil, nil))).To(BeTrue()) + Expect(Must(reg.RegisterByName("a/b/e/match", nil, nil))).To(BeTrue()) + + Expect(ha.registered).To(Equal(map[string]interface{}{})) + Expect(hb.registered).To(Equal(map[string]interface{}{})) + Expect(hab.registered).To(Equal(map[string]interface{}{})) + Expect(habd.registered).To(Equal(map[string]interface{}{})) + Expect(habc.registered).To(Equal(map[string]interface{}{})) + Expect(habe.registered).To(Equal(map[string]interface{}{"match/d": nil, "match": nil})) + }) + }) + + //////////////////////////////////////////////////////////////////////////// + + Context("blob handler registry", func() { + var reg internal.BlobHandlerRegistry + var ext internal.BlobHandlerRegistry + + BeforeEach(func() { + reg = internal.NewBlobHandlerRegistry() + ext = internal.NewBlobHandlerRegistry(reg) + }) + + DescribeTable("priortizes complete specs", + func(eff *internal.BlobHandlerRegistry) { + reg.Register(&BlobHandler{"mine"}, internal.ForMimeType(mime.MIME_TEXT)) + reg.Register(&BlobHandler{"repo"}, internal.ForRepo(internal.CONTEXT_TYPE, REPO)) + reg.Register(&BlobHandler{"art"}, internal.ForArtifactType(ART)) + reg.Register(&BlobHandler{"all"}, internal.ForRepo(internal.CONTEXT_TYPE, REPO), internal.ForArtifactType(ART), internal.ForMimeType(mime.MIME_TEXT)) + reg.Register(&BlobHandler{"repomime"}, internal.ForRepo(internal.CONTEXT_TYPE, REPO), internal.ForMimeType(mime.MIME_TEXT)) + + h := (*eff).LookupHandler(IMPL, ART, mime.MIME_TEXT) + Expect(h).NotTo(BeNil()) + _, err := h.StoreBlob(nil, "", "", nil, nil) + Expect(err).To(MatchError(fmt.Errorf("all"))) + }, + Entry("plain", ®), + Entry("extended", &ext), + ) + + DescribeTable("priortizes mime", + func(eff *internal.BlobHandlerRegistry) { + reg.Register(&BlobHandler{"mine"}, internal.ForMimeType(mime.MIME_TEXT)) + reg.Register(&BlobHandler{"repo"}, internal.ForRepo(internal.CONTEXT_TYPE, REPO)) + reg.Register(&BlobHandler{"repomime"}, internal.ForRepo(internal.CONTEXT_TYPE, REPO), internal.ForMimeType(mime.MIME_TEXT)) + + h := (*eff).LookupHandler(IMPL, ART, mime.MIME_TEXT) + Expect(h).NotTo(BeNil()) + _, err := h.StoreBlob(nil, "", "", nil, nil) + Expect(err).To(MatchError(fmt.Errorf("repomime"))) + }, + Entry("plain", ®), + Entry("extended", &ext), + ) + + DescribeTable("priortizes mime", + func(eff *internal.BlobHandlerRegistry) { + reg.Register(&BlobHandler{"mine"}, internal.ForMimeType(mime.MIME_TEXT)) + reg.Register(&BlobHandler{"repo"}, internal.ForRepo(internal.CONTEXT_TYPE, REPO)) + reg.Register(&BlobHandler{"repomine"}, internal.ForRepo(internal.CONTEXT_TYPE, REPO), internal.ForMimeType(mime.MIME_TEXT)) + reg.Register(&BlobHandler{"repoart"}, internal.ForRepo(internal.CONTEXT_TYPE, REPO), internal.ForArtifactType(ART)) + + h := (*eff).LookupHandler(IMPL, ART, mime.MIME_TEXT) + Expect(h).NotTo(BeNil()) + _, err := h.StoreBlob(nil, "", "", nil, nil) + Expect(err).To(MatchError(fmt.Errorf("repoart"))) + }, + Entry("plain", ®), + Entry("extended", &ext), + ) + + DescribeTable("priortizes prio", + func(eff *internal.BlobHandlerRegistry) { + reg.Register(&BlobHandler{"mine"}, internal.ForMimeType(mime.MIME_TEXT)) + reg.Register(&BlobHandler{"repo"}, internal.ForRepo(internal.CONTEXT_TYPE, REPO)) + reg.Register(&BlobHandler{"all"}, internal.ForRepo(internal.CONTEXT_TYPE, REPO), internal.ForMimeType(mime.MIME_TEXT)) + reg.Register(&BlobHandler{"high"}, internal.WithPrio(internal.DEFAULT_BLOBHANDLER_PRIO+1)) + + h := (*eff).LookupHandler(IMPL, ART, mime.MIME_TEXT) + Expect(h).NotTo(BeNil()) + _, err := h.StoreBlob(nil, "", "", nil, nil) + Expect(err).To(MatchError(fmt.Errorf("high"))) + }, + Entry("plain", ®), + Entry("extended", &ext), + ) + + DescribeTable("copies registries", + func(eff *internal.BlobHandlerRegistry) { + mine := &BlobHandler{"mine"} + repo := &BlobHandler{"repo"} + reg.Register(mine, internal.ForMimeType(mime.MIME_TEXT)) + reg.Register(repo, internal.ForRepo(internal.CONTEXT_TYPE, REPO)) + + h := (*eff).LookupHandler(internal.ImplementationRepositoryType{internal.CONTEXT_TYPE, REPO}, ART, mime.MIME_OCTET) + Expect(h).To(Equal(internal.MultiBlobHandler{repo})) + + copy := (*eff).Copy() + new := &BlobHandler{"repo2"} + copy.Register(new, internal.ForRepo(internal.CONTEXT_TYPE, REPO)) + + h = (*eff).LookupHandler(internal.ImplementationRepositoryType{internal.CONTEXT_TYPE, REPO}, ART, mime.MIME_OCTET) + Expect(h).To(Equal(internal.MultiBlobHandler{repo})) + + h = copy.LookupHandler(internal.ImplementationRepositoryType{internal.CONTEXT_TYPE, REPO}, ART, mime.MIME_OCTET) + Expect(h).To(Equal(internal.MultiBlobHandler{new})) + }, + Entry("plain", ®), + Entry("extended", &ext), + ) + }) +}) diff --git a/api/ocm/internal/builder.go b/api/ocm/internal/builder.go new file mode 100644 index 000000000..2e0a3b465 --- /dev/null +++ b/api/ocm/internal/builder.go @@ -0,0 +1,201 @@ +package internal + +import ( + "context" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/utils/runtime" +) + +type Builder struct { + ctx context.Context + credentials credentials.Context + oci oci.Context + reposcheme RepositoryTypeScheme + repodel RepositoryDelegationRegistry + accessscheme AccessTypeScheme + spechandlers RepositorySpecHandlers + blobhandlers BlobHandlerRegistry + blobdigesters BlobDigesterRegistry +} + +func (b *Builder) getContext() context.Context { + if b.ctx == nil { + return context.Background() + } + return b.ctx +} + +func (b Builder) WithContext(ctx context.Context) Builder { + b.ctx = ctx + return b +} + +func (b Builder) WithCredentials(ctx credentials.Context) Builder { + b.credentials = ctx + return b +} + +func (b Builder) WithOCIRepositories(ctx oci.Context) Builder { + b.oci = ctx + return b +} + +func (b Builder) WithRepositoyTypeScheme(scheme RepositoryTypeScheme) Builder { + b.reposcheme = scheme + return b +} + +func (b Builder) WithRepositoryDelegation(reg RepositoryDelegationRegistry) Builder { + b.repodel = reg + return b +} + +func (b Builder) WithAccessTypeScheme(scheme AccessTypeScheme) Builder { + b.accessscheme = scheme + return b +} + +func (b Builder) WithRepositorySpecHandlers(reg RepositorySpecHandlers) Builder { + b.spechandlers = reg + return b +} + +func (b Builder) WithBlobHandlers(reg BlobHandlerRegistry) Builder { + b.blobhandlers = reg + return b +} + +func (b Builder) WithBlobDigesters(reg BlobDigesterRegistry) Builder { + b.blobdigesters = reg + return b +} + +func (b Builder) Bound() (Context, context.Context) { + c := b.New() + return c, context.WithValue(b.getContext(), key, c) +} + +func (b Builder) New(m ...datacontext.BuilderMode) Context { + mode := datacontext.Mode(m...) + ctx := b.getContext() + + if b.oci == nil { + if b.credentials != nil { + b.oci = oci.WithCredentials(b.credentials).New(mode) + } else { + var ok bool + b.oci, ok = oci.DefinedForContext(ctx) + if !ok && mode != datacontext.MODE_SHARED { + b.oci = oci.New(mode) + } + } + } + if b.credentials == nil { + b.credentials = b.oci.CredentialsContext() + } + if b.reposcheme == nil { + switch mode { + case datacontext.MODE_INITIAL: + b.reposcheme = NewRepositoryTypeScheme(nil) + case datacontext.MODE_CONFIGURED: + b.reposcheme = NewRepositoryTypeScheme(nil) + b.reposcheme.AddKnownTypes(DefaultRepositoryTypeScheme) + case datacontext.MODE_EXTENDED: + b.reposcheme = NewRepositoryTypeScheme(nil, DefaultRepositoryTypeScheme) + case datacontext.MODE_DEFAULTED: + fallthrough + case datacontext.MODE_SHARED: + b.reposcheme = DefaultRepositoryTypeScheme + } + } + if b.accessscheme == nil { + switch mode { + case datacontext.MODE_INITIAL: + b.accessscheme = NewAccessTypeScheme() + case datacontext.MODE_CONFIGURED: + b.accessscheme = NewAccessTypeScheme() + b.accessscheme.AddKnownTypes(DefaultAccessTypeScheme) + case datacontext.MODE_EXTENDED: + b.accessscheme = NewAccessTypeScheme(DefaultAccessTypeScheme) + case datacontext.MODE_DEFAULTED: + fallthrough + case datacontext.MODE_SHARED: + b.accessscheme = DefaultAccessTypeScheme + } + } + if b.spechandlers == nil { + switch mode { + case datacontext.MODE_INITIAL: + b.spechandlers = NewRepositorySpecHandlers() + case datacontext.MODE_CONFIGURED: + b.spechandlers = DefaultRepositorySpecHandlers.Copy() + case datacontext.MODE_EXTENDED: + fallthrough + case datacontext.MODE_DEFAULTED: + fallthrough + case datacontext.MODE_SHARED: + b.spechandlers = DefaultRepositorySpecHandlers + } + } + if b.repodel == nil { + switch mode { + case datacontext.MODE_INITIAL: + b.repodel = nil + case datacontext.MODE_CONFIGURED: + b.repodel = DefaultRepositoryDelegationRegistry.Copy() + case datacontext.MODE_EXTENDED: + b.repodel = NewDelegationRegistry[Context, RepositorySpec](DefaultRepositoryDelegationRegistry) + case datacontext.MODE_DEFAULTED: + fallthrough + case datacontext.MODE_SHARED: + b.repodel = DefaultRepositoryDelegationRegistry + } + } + if b.blobhandlers == nil { + switch mode { + case datacontext.MODE_INITIAL: + b.blobhandlers = NewBlobHandlerRegistry() + case datacontext.MODE_CONFIGURED: + b.blobhandlers = DefaultBlobHandlerRegistry.Copy() + case datacontext.MODE_EXTENDED: + b.blobhandlers = NewBlobHandlerRegistry(DefaultBlobHandlerRegistry) + case datacontext.MODE_DEFAULTED: + fallthrough + case datacontext.MODE_SHARED: + b.blobhandlers = DefaultBlobHandlerRegistry + } + } + if b.blobdigesters == nil { + switch mode { + case datacontext.MODE_INITIAL: + b.blobdigesters = NewBlobDigesterRegistry() + case datacontext.MODE_CONFIGURED: + b.blobdigesters = DefaultBlobDigesterRegistry.Copy() + case datacontext.MODE_EXTENDED: + b.blobdigesters = NewBlobDigesterRegistry(DefaultBlobDigesterRegistry) + case datacontext.MODE_DEFAULTED: + fallthrough + case datacontext.MODE_SHARED: + b.blobdigesters = DefaultBlobDigesterRegistry + } + } + + return datacontext.SetupContext(mode, newContext(b.credentials, b.oci, b.reposcheme, b.accessscheme, b.spechandlers, b.blobhandlers, b.blobdigesters, b.repodel, b.credentials.ConfigContext())) +} + +type delegatingDecoder struct { + ctx Context + delegate RepositoryDelegationRegistry +} + +var _ RepositorySpecDecoder = (*delegatingDecoder)(nil) + +func (d *delegatingDecoder) Decode(data []byte, unmarshaler runtime.Unmarshaler) (RepositorySpec, error) { + if d.delegate != nil { + return d.delegate.Decode(d.ctx, data, unmarshaler) + } + return nil, nil +} diff --git a/api/ocm/internal/builder_test.go b/api/ocm/internal/builder_test.go new file mode 100644 index 000000000..7fc59da91 --- /dev/null +++ b/api/ocm/internal/builder_test.go @@ -0,0 +1,101 @@ +package internal_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/ocm/cpi" + local "ocm.software/ocm/api/ocm/internal" + "ocm.software/ocm/api/utils/runtime" +) + +var _ = Describe("builder test", func() { + It("creates local", func() { + ctx := local.Builder{}.New(datacontext.MODE_SHARED) + + Expect(ctx.AttributesContext()).To(BeIdenticalTo(datacontext.DefaultContext)) + Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) + Expect(BaseRepoTypes(ctx.RepositoryTypes())).To(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) + Expect(ctx.AccessMethods()).To(BeIdenticalTo(local.DefaultAccessTypeScheme)) + Expect(ctx.RepositorySpecHandlers()).To(BeIdenticalTo(local.DefaultRepositorySpecHandlers)) + Expect(ctx.BlobHandlers()).To(BeIdenticalTo(local.DefaultBlobHandlerRegistry)) + Expect(ctx.BlobDigesters()).To(BeIdenticalTo(local.DefaultBlobDigesterRegistry)) + + Expect(ctx.ConfigContext().GetId()).To(BeIdenticalTo(config.DefaultContext().GetId())) + Expect(ctx.CredentialsContext().GetId()).To(BeIdenticalTo(credentials.DefaultContext().GetId())) + Expect(ctx.OCIContext().GetId()).To(BeIdenticalTo(oci.DefaultContext().GetId())) + }) + + It("creates defaulted", func() { + ctx := local.Builder{}.New(datacontext.MODE_DEFAULTED) + + Expect(ctx.AttributesContext()).NotTo(BeIdenticalTo(datacontext.DefaultContext)) + Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) + Expect(BaseRepoTypes(ctx.RepositoryTypes())).To(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) + Expect(ctx.AccessMethods()).To(BeIdenticalTo(local.DefaultAccessTypeScheme)) + Expect(ctx.RepositorySpecHandlers()).To(BeIdenticalTo(local.DefaultRepositorySpecHandlers)) + Expect(ctx.BlobHandlers()).To(BeIdenticalTo(local.DefaultBlobHandlerRegistry)) + Expect(ctx.BlobDigesters()).To(BeIdenticalTo(local.DefaultBlobDigesterRegistry)) + + Expect(ctx.ConfigContext()).NotTo(BeIdenticalTo(config.DefaultContext())) + Expect(ctx.ConfigContext().ConfigTypes()).To(BeIdenticalTo(config.DefaultContext().ConfigTypes())) + + Expect(ctx.CredentialsContext()).NotTo(BeIdenticalTo(credentials.DefaultContext())) + Expect(ctx.CredentialsContext().RepositoryTypes()).To(BeIdenticalTo(credentials.DefaultContext().RepositoryTypes())) + + Expect(ctx.OCIContext()).NotTo(BeIdenticalTo(oci.DefaultContext())) + Expect(ctx.OCIContext().RepositoryTypes()).To(BeIdenticalTo(oci.DefaultContext().RepositoryTypes())) + }) + + It("creates configured", func() { + ctx := local.Builder{}.New(datacontext.MODE_CONFIGURED) + + Expect(ctx.AttributesContext()).NotTo(BeIdenticalTo(datacontext.DefaultContext)) + Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) + Expect(ctx.RepositoryTypes()).NotTo(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) + Expect(ctx.RepositoryTypes().KnownTypeNames()).To(Equal(local.DefaultRepositoryTypeScheme.KnownTypeNames())) + Expect(ctx.AccessMethods()).NotTo(BeIdenticalTo(local.DefaultAccessTypeScheme)) + Expect(ctx.RepositorySpecHandlers()).NotTo(BeIdenticalTo(local.DefaultRepositorySpecHandlers)) + Expect(ctx.BlobHandlers()).NotTo(BeIdenticalTo(local.DefaultBlobHandlerRegistry)) + Expect(ctx.BlobDigesters()).NotTo(BeIdenticalTo(local.DefaultBlobDigesterRegistry)) + + Expect(ctx.ConfigContext()).NotTo(BeIdenticalTo(config.DefaultContext())) + Expect(ctx.ConfigContext().ConfigTypes()).NotTo(BeIdenticalTo(config.DefaultContext().ConfigTypes())) + Expect(ctx.ConfigContext().ConfigTypes().KnownTypeNames()).To(Equal(config.DefaultContext().ConfigTypes().KnownTypeNames())) + + Expect(ctx.CredentialsContext()).NotTo(BeIdenticalTo(config.DefaultContext())) + Expect(ctx.CredentialsContext().RepositoryTypes()).NotTo(BeIdenticalTo(credentials.DefaultContext().RepositoryTypes())) + Expect(ctx.CredentialsContext().RepositoryTypes().KnownTypeNames()).To(Equal(credentials.DefaultContext().RepositoryTypes().KnownTypeNames())) + + Expect(ctx.OCIContext()).NotTo(BeIdenticalTo(config.DefaultContext())) + Expect(ctx.OCIContext().RepositoryTypes()).NotTo(BeIdenticalTo(oci.DefaultContext().RepositoryTypes())) + Expect(ctx.OCIContext().RepositoryTypes().KnownTypeNames()).To(Equal(oci.DefaultContext().RepositoryTypes().KnownTypeNames())) + }) + + It("creates iniial", func() { + ctx := local.Builder{}.New(datacontext.MODE_INITIAL) + + Expect(ctx.AttributesContext()).NotTo(BeIdenticalTo(datacontext.DefaultContext)) + Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) + Expect(ctx.RepositoryTypes()).NotTo(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) + Expect(len(ctx.RepositoryTypes().KnownTypeNames())).To(Equal(0)) + Expect(len(ctx.AccessMethods().KnownTypeNames())).To(Equal(0)) + Expect(len(ctx.RepositorySpecHandlers().KnownTypeNames())).To(Equal(0)) + Expect(ctx.BlobHandlers().IsInitial()).To(Equal(true)) + Expect(ctx.BlobDigesters().IsInitial()).To(Equal(true)) + + Expect(ctx.ConfigContext()).NotTo(BeIdenticalTo(config.DefaultContext())) + Expect(len(ctx.ConfigContext().ConfigTypes().KnownTypeNames())).To(Equal(0)) + + Expect(ctx.CredentialsContext()).NotTo(BeIdenticalTo(credentials.DefaultContext())) + Expect(len(ctx.CredentialsContext().RepositoryTypes().KnownTypeNames())).To(Equal(0)) + }) +}) + +func BaseRepoTypes(r cpi.RepositoryTypeScheme) runtime.Scheme[local.RepositorySpec, local.RepositoryType] { + return r.BaseScheme() +} diff --git a/api/ocm/internal/context.go b/api/ocm/internal/context.go new file mode 100644 index 000000000..cabebeff3 --- /dev/null +++ b/api/ocm/internal/context.go @@ -0,0 +1,340 @@ +package internal + +import ( + "context" + "reflect" + "strings" + + . "github.com/mandelsoft/goutils/finalizer" + + "github.com/modern-go/reflect2" + + "ocm.software/ocm/api/config" + cfgcpi "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" +) + +const CONTEXT_TYPE = "ocm" + datacontext.OCM_CONTEXT_SUFFIX + +const CommonTransportFormat = ctf.Type + +type ContextProvider interface { + OCMContext() Context +} + +type LocalContextProvider interface { + GetContext() Context +} + +type localContextProvider struct { + LocalContextProvider +} + +func (l *localContextProvider) OCMContext() Context { + return l.GetContext() +} + +func WrapContextProvider(ctx LocalContextProvider) ContextProvider { + return &localContextProvider{ctx} +} + +type Context interface { + datacontext.Context + config.ContextProvider + credentials.ContextProvider + oci.ContextProvider + ContextProvider + + RepositoryTypes() RepositoryTypeScheme + AccessMethods() AccessTypeScheme + + RepositorySpecHandlers() RepositorySpecHandlers + MapUniformRepositorySpec(u *UniformRepositorySpec) (RepositorySpec, error) + + DisableBlobHandlers() + BlobHandlers() BlobHandlerRegistry + BlobDigesters() BlobDigesterRegistry + + RepositoryForSpec(spec RepositorySpec, creds ...credentials.CredentialsSource) (Repository, error) + RepositoryForConfig(data []byte, unmarshaler runtime.Unmarshaler, creds ...credentials.CredentialsSource) (Repository, error) + RepositorySpecForConfig(data []byte, unmarshaler runtime.Unmarshaler) (RepositorySpec, error) + + AccessSpecForSpec(spec compdesc.AccessSpec) (AccessSpec, error) + AccessSpecForConfig(data []byte, unmarshaler runtime.Unmarshaler) (AccessSpec, error) + + Encode(AccessSpec, runtime.Marshaler) ([]byte, error) + + GetAlias(name string) RepositorySpec + SetAlias(name string, spec RepositorySpec) + + GetResolver() ComponentVersionResolver + AddResolverRule(prefix string, spec RepositorySpec, prio ...int) + + // Finalize finalizes elements implicitly opened during resource operations. + // For example, registered blob handler may open objects, which are kept open + // for performance reasons. At the end of a usage finalize should be called + // to finalize those elements. This method can be called any time by a context + // user to cleanup temporarily allocated resources. Therefore, only + // elements should be added to the finalzer, which can be reopened/created + // on-the fly whenever required. + Finalize() error + Finalizer() *Finalizer +} + +// ////////////////////////////////////////////////////////////////////////////// + +var key = reflect.TypeOf(_context{}) + +// DefaultContext is the default context initialized by init functions. +var DefaultContext = Builder{}.New(datacontext.MODE_SHARED) + +// FromContext returns the Context to use for context.Context. +// This is either an explicit context or the default context. +func FromContext(ctx context.Context) Context { + c, _ := datacontext.ForContextByKey(ctx, key, DefaultContext) + return c.(Context) +} + +func FromProvider(p ContextProvider) Context { + if p == nil { + return nil + } + return p.OCMContext() +} + +func DefinedForContext(ctx context.Context) (Context, bool) { + c, ok := datacontext.ForContextByKey(ctx, key, DefaultContext) + if c != nil { + return c.(Context), ok + } + return nil, ok +} + +// ////////////////////////////////////////////////////////////////////////////// + +type _InternalContext = datacontext.InternalContext + +type _context struct { + _InternalContext + updater cfgcpi.Updater + + credctx credentials.Context + ocictx oci.Context + + knownRepositoryTypes RepositoryTypeScheme + knownAccessTypes AccessTypeScheme + + specHandlers RepositorySpecHandlers + blobHandlers BlobHandlerRegistry + blobDigesters BlobDigesterRegistry + aliases map[string]RepositorySpec + resolver *resolver +} + +var ( + _ Context = (*_context)(nil) + _ datacontext.ViewCreator[Context] = (*_context)(nil) +) + +// gcWrapper is used as garbage collectable +// wrapper for a context implementation +// to establish a runtime finalizer. +type gcWrapper struct { + datacontext.GCWrapper + *_context +} + +func newView(c *_context, ref ...bool) Context { + if utils.Optional(ref...) { + return datacontext.FinalizedContext[gcWrapper](c) + } + return c +} + +func (w *gcWrapper) SetContext(c *_context) { + w._context = c +} + +func newContext(credctx credentials.Context, ocictx oci.Context, reposcheme RepositoryTypeScheme, accessscheme AccessTypeScheme, specHandlers RepositorySpecHandlers, blobHandlers BlobHandlerRegistry, blobDigesters BlobDigesterRegistry, repodel RepositoryDelegationRegistry, delegates datacontext.Delegates) Context { + c := &_context{ + credctx: datacontext.PersistentContextRef(credctx), + ocictx: datacontext.PersistentContextRef(ocictx), + specHandlers: specHandlers, + blobHandlers: blobHandlers, + blobDigesters: blobDigesters, + knownAccessTypes: accessscheme, + knownRepositoryTypes: reposcheme, + aliases: map[string]RepositorySpec{}, + } + + if repodel != nil { + c.knownRepositoryTypes = NewRepositoryTypeScheme(&delegatingDecoder{ctx: c, delegate: repodel}, reposcheme) + } + c._InternalContext = datacontext.NewContextBase(c, CONTEXT_TYPE, key, credctx.GetAttributes(), delegates) + c.updater = cfgcpi.NewUpdaterForFactory(credctx.ConfigContext(), c.OCMContext) + c.resolver = &resolver{ + ctx: c, + MatchingResolver: NewMatchingResolver(c), + } + c.Finalizer().With(c.resolver.Finalize) + return newView(c, true) +} + +func (c *_context) CreateView() Context { + return newView(c, true) +} + +func (c *_context) OCMContext() Context { + return newView(c) +} + +func (c *_context) Update() error { + return c.updater.Update() +} + +func (c *_context) AttributesContext() datacontext.AttributesContext { + return c.credctx.AttributesContext() +} + +func (c *_context) ConfigContext() config.Context { + return c.updater.GetContext() +} + +func (c *_context) CredentialsContext() credentials.Context { + return c.credctx +} + +func (c *_context) OCIContext() oci.Context { + return c.ocictx +} + +func (c *_context) RepositoryTypes() RepositoryTypeScheme { + return c.knownRepositoryTypes +} + +func (c *_context) RepositorySpecHandlers() RepositorySpecHandlers { + return c.specHandlers +} + +func (c *_context) MapUniformRepositorySpec(u *UniformRepositorySpec) (RepositorySpec, error) { + return c.specHandlers.MapUniformRepositorySpec(c, u) +} + +func (c *_context) DisableBlobHandlers() { + c.blobHandlers = NewBlobHandlerRegistry(nil) +} + +func (c *_context) BlobHandlers() BlobHandlerRegistry { + c.Update() + return c.blobHandlers +} + +func (c *_context) BlobDigesters() BlobDigesterRegistry { + c.Update() + return c.blobDigesters +} + +func (c *_context) RepositoryForSpec(spec RepositorySpec, creds ...credentials.CredentialsSource) (Repository, error) { + cred, err := credentials.CredentialsChain(creds).Credentials(c.CredentialsContext()) + if err != nil { + return nil, err + } + return spec.Repository(c, cred) +} + +func (c *_context) RepositoryForConfig(data []byte, unmarshaler runtime.Unmarshaler, creds ...credentials.CredentialsSource) (Repository, error) { + spec, err := c.knownRepositoryTypes.Decode(data, unmarshaler) + if err != nil { + return nil, err + } + return c.RepositoryForSpec(spec, creds...) +} + +func (c *_context) RepositorySpecForConfig(data []byte, unmarshaler runtime.Unmarshaler) (RepositorySpec, error) { + return c.knownRepositoryTypes.Decode(data, unmarshaler) +} + +func (c *_context) AccessMethods() AccessTypeScheme { + return c.knownAccessTypes +} + +func (c *_context) AccessSpecForConfig(data []byte, unmarshaler runtime.Unmarshaler) (AccessSpec, error) { + return c.knownAccessTypes.Decode(data, unmarshaler) +} + +// AccessSpecForSpec takes an anonymous access specification and tries to map +// it to an effective implementation. +func (c *_context) AccessSpecForSpec(spec compdesc.AccessSpec) (AccessSpec, error) { + if reflect2.IsNil(spec) { + return nil, nil + } + if n, ok := spec.(AccessSpec); ok { + if g, ok := spec.(EvaluatableAccessSpec); ok { + return g.Evaluate(c) + } + return n, nil + } + un, err := runtime.ToUnstructuredTypedObject(spec) + if err != nil { + return nil, err + } + + raw, err := un.GetRaw() + if err != nil { + return nil, err + } + + return c.knownAccessTypes.Decode(raw, runtime.DefaultJSONEncoding) +} + +func (c *_context) Encode(spec AccessSpec, marshaler runtime.Marshaler) ([]byte, error) { + return c.knownAccessTypes.Encode(spec, marshaler) +} + +func (c *_context) GetAlias(name string) RepositorySpec { + err := c.updater.Update() + if err != nil { + return nil + } + c.updater.RLock() + defer c.updater.RUnlock() + spec := c.aliases[name] + if spec == nil && strings.HasSuffix(name, ".alias") { + spec = c.aliases[name[:len(name)-6]] + } + return spec +} + +func (c *_context) SetAlias(name string, spec RepositorySpec) { + c.updater.Lock() + defer c.updater.Unlock() + c.aliases[name] = spec +} + +func (c *_context) GetResolver() ComponentVersionResolver { + c.Update() + if len(c.resolver.rules) == 0 { + return nil + } + return c.resolver +} + +func (c *_context) AddResolverRule(prefix string, spec RepositorySpec, prio ...int) { + c.resolver.AddRule(prefix, spec, prio...) +} + +type resolver struct { + ctx *_context + *MatchingResolver +} + +func (r *resolver) LookupComponentVersion(name, version string) (ComponentVersionAccess, error) { + r.ctx.Update() + return r.MatchingResolver.LookupComponentVersion(name, version) +} diff --git a/pkg/contexts/ocm/internal/delegation.go b/api/ocm/internal/delegation.go similarity index 97% rename from pkg/contexts/ocm/internal/delegation.go rename to api/ocm/internal/delegation.go index f95e08c13..22f1211e1 100644 --- a/pkg/contexts/ocm/internal/delegation.go +++ b/api/ocm/internal/delegation.go @@ -4,8 +4,8 @@ import ( "sort" "sync" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" ) // RepositoryDelegationRegistry is used to register handlers able to dynamically diff --git a/pkg/contexts/ocm/internal/digesthandler.go b/api/ocm/internal/digesthandler.go similarity index 97% rename from pkg/contexts/ocm/internal/digesthandler.go rename to api/ocm/internal/digesthandler.go index c13f6943d..8cfd81b0c 100644 --- a/pkg/contexts/ocm/internal/digesthandler.go +++ b/api/ocm/internal/digesthandler.go @@ -6,9 +6,9 @@ import ( "golang.org/x/exp/slices" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/utils" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/utils" ) type DigesterType struct { diff --git a/pkg/contexts/ocm/internal/digesthandler_test.go b/api/ocm/internal/digesthandler_test.go similarity index 90% rename from pkg/contexts/ocm/internal/digesthandler_test.go rename to api/ocm/internal/digesthandler_test.go index c89af110e..5409945ef 100644 --- a/pkg/contexts/ocm/internal/digesthandler_test.go +++ b/api/ocm/internal/digesthandler_test.go @@ -7,11 +7,11 @@ import ( . "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/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/hasher/sha256" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/internal" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/hasher/sha256" ) type DigestHandler struct { diff --git a/api/ocm/internal/errors.go b/api/ocm/internal/errors.go new file mode 100644 index 000000000..aa4888505 --- /dev/null +++ b/api/ocm/internal/errors.go @@ -0,0 +1,28 @@ +package internal + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/utils/errkind" +) + +const ( + KIND_REPOSITORY = "ocm repository" + KIND_COMPONENT = errkind.KIND_COMPONENT + KIND_COMPONENTVERSION = "component version" + KIND_RESOURCE = "component resource" + KIND_SOURCE = "component source" + KIND_REFERENCE = compdesc.KIND_REFERENCE + KIND_REPOSITORYSPEC = "repository specification" +) + +func ErrComponentVersionNotFound(name, version string) error { + return errors.ErrNotFound(KIND_COMPONENTVERSION, fmt.Sprintf("%s:%s", name, version)) +} + +func ErrComponentVersionNotFoundWrap(err error, name, version string) error { + return errors.ErrNotFoundWrap(err, KIND_COMPONENTVERSION, fmt.Sprintf("%s:%s", name, version)) +} diff --git a/api/ocm/internal/hasher.go b/api/ocm/internal/hasher.go new file mode 100644 index 000000000..8835d3c47 --- /dev/null +++ b/api/ocm/internal/hasher.go @@ -0,0 +1,11 @@ +package internal + +import ( + "ocm.software/ocm/api/tech/signing" +) + +// Hasher creates a new hash.Hash interface. +type Hasher = signing.Hasher + +// HasherProvider provides access to supported hash methods. +type HasherProvider = signing.HasherProvider diff --git a/api/ocm/internal/modopts.go b/api/ocm/internal/modopts.go new file mode 100644 index 000000000..98f902d72 --- /dev/null +++ b/api/ocm/internal/modopts.go @@ -0,0 +1,505 @@ +package internal + +import ( + "fmt" + + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/ocm/compdesc" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/utils" +) + +type BlobUploadOption interface { + ApplyBlobUploadOption(opts *BlobUploadOptions) +} + +type BlobOptionImpl interface { + BlobUploadOption + BlobModificationOption +} + +type BlobUploadOptions struct { + UseNoDefaultIfNotSet *bool `json:"noDefaultUpload,omitempty"` + BlobHandlerProvider BlobHandlerProvider `json:"-"` +} + +var _ BlobUploadOption = (*BlobUploadOptions)(nil) + +func NewBlobUploadOptions(list ...BlobUploadOption) *BlobUploadOptions { + var m BlobUploadOptions + m.ApplyBlobUploadOptions(list...) + return &m +} + +func (m *BlobUploadOptions) ApplyBlobUploadOptions(list ...BlobUploadOption) { + for _, o := range list { + if o != nil { + o.ApplyBlobUploadOption(m) + } + } +} + +func (o *BlobUploadOptions) ApplyBlobModificationOption(opts *BlobModificationOptions) { + o.ApplyBlobUploadOption(&opts.BlobUploadOptions) +} + +func (o *BlobUploadOptions) ApplyBlobUploadOption(opts *BlobUploadOptions) { + optionutils.ApplyOption(o.UseNoDefaultIfNotSet, &opts.UseNoDefaultIfNotSet) + if o.BlobHandlerProvider != nil { + opts.BlobHandlerProvider = o.BlobHandlerProvider + opts.UseNoDefaultIfNotSet = utils.BoolP(true) + } +} + +//////////////////////////////////////////////////////////////////////////////// + +type nodefaulthandler bool + +func (o nodefaulthandler) ApplyBlobModificationOption(opts *BlobModificationOptions) { + o.ApplyBlobUploadOption(&opts.BlobUploadOptions) +} + +func (o nodefaulthandler) ApplyBlobUploadOption(opts *BlobUploadOptions) { + opts.UseNoDefaultIfNotSet = optionutils.PointerTo(bool(o)) +} + +func UseNoDefaultBlobHandlers(b ...bool) BlobOptionImpl { + return nodefaulthandler(utils.OptionalDefaultedBool(true, b...)) +} + +//////////////////////////////////////////////////////////////////////////////// + +type handler struct { + blobHandlerProvider BlobHandlerProvider +} + +func (o *handler) ApplyBlobModificationOption(opts *BlobModificationOptions) { + o.ApplyBlobUploadOption(&opts.BlobUploadOptions) +} + +func (o *handler) ApplyBlobUploadOption(opts *BlobUploadOptions) { + if o.blobHandlerProvider != nil { + opts.BlobHandlerProvider = o.blobHandlerProvider + } +} + +func UseBlobHandlers(h BlobHandlerProvider) BlobOptionImpl { + return &handler{h} +} + +//////////////////////////////////////////////////////////////////////////////// + +// TargetElement described the index used to set the +// resource or source for the SetXXX calls. +// If -1 is returned an append is enforced. +type TargetElement interface { + GetTargetIndex(resources compdesc.ElementAccessor, meta *compdesc.ElementMeta) (int, error) +} + +type TargetOptionImpl interface { + TargetOption + ModificationOption + BlobModificationOption +} + +type TargetOptions struct { + TargetElement TargetElement +} + +type TargetOption interface { + ApplyTargetOption(options *TargetOptions) +} + +func (m *TargetOptions) ApplyBlobModificationOption(opts *BlobModificationOptions) { + m.ApplyTargetOption(&opts.TargetOptions) +} + +func (m *TargetOptions) ApplyModificationOption(opts *ModificationOptions) { + m.ApplyTargetOption(&opts.TargetOptions) +} + +func (m *TargetOptions) ApplyTargetOption(opts *TargetOptions) { + optionutils.Transfer(&opts.TargetElement, m.TargetElement) +} + +func (m *TargetOptions) ApplyTargetOptions(list ...TargetOption) *TargetOptions { + for _, o := range list { + if o != nil { + o.ApplyTargetOption(m) + } + } + return m +} + +func NewTargetOptions(list ...TargetOption) *TargetOptions { + var m TargetOptions + m.ApplyTargetOptions(list...) + return &m +} + +type ModificationOption interface { + ApplyModificationOption(opts *ModificationOptions) +} + +type ModOptionImpl interface { + ModificationOption + BlobModificationOption +} + +type ModificationOptions struct { + TargetOptions + + // ModifyResource disables the modification of signature releveant + // resource parts. + ModifyResource *bool + + // AcceptExistentDigests don't validate/recalculate the content digest + // of resources. + AcceptExistentDigests *bool + + // DefaultHashAlgorithm is the hash algorithm to use if no specific setting os found + DefaultHashAlgorithm string + + // HasherProvider is the factory for hash algorithms to use. + HasherProvider HasherProvider + + // SkipVerify disabled the verification of given digests + SkipVerify *bool + + // SkipDigest disabled digest creation (for legacy code, only!) + SkipDigest *bool +} + +func (m *ModificationOptions) IsModifyResource() bool { + return utils.AsBool(m.ModifyResource) +} + +func (m *ModificationOptions) IsAcceptExistentDigests() bool { + return utils.AsBool(m.AcceptExistentDigests) +} + +func (m *ModificationOptions) IsSkipDigest() bool { + return utils.AsBool(m.SkipDigest) +} + +func (m *ModificationOptions) IsSkipVerify() bool { + return utils.AsBool(m.SkipVerify) +} + +func (m *ModificationOptions) ApplyModificationOptions(list ...ModificationOption) *ModificationOptions { + for _, o := range list { + if o != nil { + o.ApplyModificationOption(m) + } + } + return m +} + +func (m *ModificationOptions) ApplyBlobModificationOption(opts *BlobModificationOptions) { + m.ApplyModificationOption(&opts.ModificationOptions) +} + +func (m *ModificationOptions) ApplyModificationOption(opts *ModificationOptions) { + m.TargetOptions.ApplyTargetOption(&opts.TargetOptions) + optionutils.Transfer(&opts.ModifyResource, m.ModifyResource) + optionutils.Transfer(&opts.AcceptExistentDigests, m.AcceptExistentDigests) + optionutils.Transfer(&opts.SkipDigest, m.SkipDigest) + optionutils.Transfer(&opts.SkipVerify, m.SkipVerify) + optionutils.Transfer(&opts.HasherProvider, m.HasherProvider) + optionutils.Transfer(&opts.DefaultHashAlgorithm, m.DefaultHashAlgorithm) +} + +func (m *ModificationOptions) GetHasher(algo ...string) Hasher { + return m.HasherProvider.GetHasher(utils.OptionalDefaulted(m.DefaultHashAlgorithm, algo...)) +} + +func NewModificationOptions(list ...ModificationOption) *ModificationOptions { + var m ModificationOptions + m.ApplyModificationOptions(list...) + return &m +} + +//////////////////////////////////////////////////////////////////////////////// + +type TargetIndex int + +func (m TargetIndex) GetTargetIndex(elems compdesc.ElementAccessor, meta *compdesc.ElementMeta) (int, error) { + if int(m) >= elems.Len() { + return -1, nil + } + return int(m), nil +} + +func (m TargetIndex) ApplyBlobModificationOption(opts *BlobModificationOptions) { + m.ApplyModificationOption(&opts.ModificationOptions) +} + +func (m TargetIndex) ApplyModificationOption(opts *ModificationOptions) { + m.ApplyTargetOption(&opts.TargetOptions) +} + +func (m TargetIndex) ApplyTargetOption(opts *TargetOptions) { + opts.TargetElement = m +} + +//////////////////////////////////////////////////////////////////////////////// + +type TargetIdentityOrAppend v1.Identity + +func (m TargetIdentityOrAppend) GetTargetIndex(elems compdesc.ElementAccessor, meta *compdesc.ElementMeta) (int, error) { + idx, _ := TargetIdentity(m).GetTargetIndex(elems, meta) + return idx, nil +} + +func (m TargetIdentityOrAppend) ApplyBlobModificationOption(opts *BlobModificationOptions) { + m.ApplyModificationOption(&opts.ModificationOptions) +} + +func (m TargetIdentityOrAppend) ApplyModificationOption(opts *ModificationOptions) { + m.ApplyTargetOption(&opts.TargetOptions) +} + +func (m TargetIdentityOrAppend) ApplyTargetOption(opts *TargetOptions) { + opts.TargetElement = m +} + +//////////////////////////////////////////////////////////////////////////////// + +type TargetIdentity v1.Identity + +func (m TargetIdentity) GetTargetIndex(elems compdesc.ElementAccessor, meta *compdesc.ElementMeta) (int, error) { + for i := 0; i < elems.Len(); i++ { + r := elems.Get(i) + if r.GetMeta().GetIdentity(elems).Equals(v1.Identity(m)) { + return i, nil + } + } + return -1, fmt.Errorf("element %s not found", v1.Identity(m)) +} + +func (m TargetIdentity) ApplyBlobModificationOption(opts *BlobModificationOptions) { + m.ApplyModificationOption(&opts.ModificationOptions) +} + +func (m TargetIdentity) ApplyModificationOption(opts *ModificationOptions) { + m.ApplyTargetOption(&opts.TargetOptions) +} + +func (m TargetIdentity) ApplyTargetOption(opts *TargetOptions) { + opts.TargetElement = m +} + +//////////////////////////////////////////////////////////////////////////////// + +type replaceElement struct{} + +var UpdateElement = replaceElement{} + +func (m replaceElement) GetTargetIndex(elems compdesc.ElementAccessor, meta *compdesc.ElementMeta) (int, error) { + id := meta.GetIdentity(elems) + for i := 0; i < elems.Len(); i++ { + if elems.Get(i).GetMeta().GetIdentity(elems).Equals(id) { + return i, nil + } + } + return -1, fmt.Errorf("element %s not found", id) +} + +func (m replaceElement) ApplyBlobModificationOption(opts *BlobModificationOptions) { + m.ApplyModificationOption(&opts.ModificationOptions) +} + +func (m replaceElement) ApplyModificationOption(opts *ModificationOptions) { + m.ApplyTargetOption(&opts.TargetOptions) +} + +func (m replaceElement) ApplyTargetOption(opts *TargetOptions) { + opts.TargetElement = m +} + +//////////////////////////////////////////////////////////////////////////////// + +type modifyresource bool + +func (m modifyresource) ApplyBlobModificationOption(opts *BlobModificationOptions) { + m.ApplyModificationOption(&opts.ModificationOptions) +} + +func (m modifyresource) ApplyModificationOption(opts *ModificationOptions) { + opts.ModifyResource = utils.BoolP(m) +} + +func ModifyResource(flag ...bool) ModOptionImpl { + return modifyresource(utils.OptionalDefaultedBool(true, flag...)) +} + +//////////////////////////////////////////////////////////////////////////////// + +type acceptdigests bool + +func (m acceptdigests) ApplyBlobModificationOption(opts *BlobModificationOptions) { + m.ApplyModificationOption(&opts.ModificationOptions) +} + +func (m acceptdigests) ApplyModificationOption(opts *ModificationOptions) { + opts.AcceptExistentDigests = utils.BoolP(m) +} + +func AcceptExistentDigests(flag ...bool) ModOptionImpl { + return acceptdigests(utils.OptionalDefaultedBool(true, flag...)) +} + +//////////////////////////////////////////////////////////////////////////////// + +type hashalgo string + +func (m hashalgo) ApplyBlobModificationOption(opts *BlobModificationOptions) { + m.ApplyModificationOption(&opts.ModificationOptions) +} + +func (m hashalgo) ApplyModificationOption(opts *ModificationOptions) { + opts.DefaultHashAlgorithm = string(m) +} + +func WithDefaultHashAlgorithm(algo ...string) ModOptionImpl { + return hashalgo(utils.Optional(algo...)) +} + +//////////////////////////////////////////////////////////////////////////////// + +type hashprovider struct { + prov HasherProvider +} + +func (m hashprovider) ApplyBlobModificationOption(opts *BlobModificationOptions) { + m.ApplyModificationOption(&opts.ModificationOptions) +} + +func (m *hashprovider) ApplyModificationOption(opts *ModificationOptions) { + opts.HasherProvider = m.prov +} + +func WithHasherProvider(prov HasherProvider) ModOptionImpl { + return &hashprovider{prov} +} + +//////////////////////////////////////////////////////////////////////////////// + +type skipverify bool + +func (m skipverify) ApplyBlobModificationOption(opts *BlobModificationOptions) { + m.ApplyModificationOption(&opts.ModificationOptions) +} + +func (m skipverify) ApplyModificationOption(opts *ModificationOptions) { + opts.SkipVerify = utils.BoolP(m) +} + +func SkipVerify(flag ...bool) ModOptionImpl { + return skipverify(utils.OptionalDefaultedBool(true, flag...)) +} + +//////////////////////////////////////////////////////////////////////////////// + +type skipdigest bool + +func (m skipdigest) ApplyBlobModificationOption(opts *BlobModificationOptions) { + m.ApplyModificationOption(&opts.ModificationOptions) +} + +func (m skipdigest) ApplyModificationOption(opts *ModificationOptions) { + opts.SkipDigest = utils.BoolP(m) +} + +// SkipDigest disables digest creation if enabled. +// +// Deprecated: for legacy code, only. +func SkipDigest(flag ...bool) ModOptionImpl { + return skipdigest(utils.OptionalDefaultedBool(true, flag...)) +} + +//////////////////////////////////////////////////////////////////////////////// + +// BlobModificationOption is used for option list allowing both, +// blob upload and modification options. +type BlobModificationOption interface { + ApplyBlobModificationOption(*BlobModificationOptions) +} + +type BlobModificationOptions struct { + BlobUploadOptions + ModificationOptions +} + +func NewBlobModificationOptions(list ...BlobModificationOption) *BlobModificationOptions { + var m BlobModificationOptions + m.ApplyBlobModificationOptions(list...) + return &m +} + +func (m *BlobModificationOptions) ApplyBlobModificationOptions(list ...BlobModificationOption) { + for _, o := range list { + if o != nil { + o.ApplyBlobModificationOption(m) + } + } +} + +func (o *BlobModificationOptions) ApplyBlobModificationOption(opts *BlobModificationOptions) { + o.BlobUploadOptions.ApplyBlobUploadOption(&opts.BlobUploadOptions) + o.ModificationOptions.ApplyModificationOption(&opts.ModificationOptions) +} + +func (o *BlobModificationOptions) ApplyBlobUploadOption(opts *BlobUploadOptions) { + o.BlobUploadOptions.ApplyBlobUploadOption(opts) +} + +func (o *BlobModificationOptions) ApplyModificationOption(opts *ModificationOptions) { + o.ModificationOptions.ApplyModificationOption(opts) +} + +/////////////////////////////////////////////////////////////////////////////// + +// BlobModificationOption is used for option list allowing both, +// blob upload and modification options. +type AddVersionOption interface { + ApplyAddVersionOption(*AddVersionOptions) +} + +type AddVersionOptions struct { + Overwrite *bool + BlobUploadOptions +} + +func NewAddVersionOptions(list ...AddVersionOption) *AddVersionOptions { + var m AddVersionOptions + m.ApplyAddVersionOptions(list...) + return &m +} + +func (m *AddVersionOptions) ApplyAddVersionOptions(list ...AddVersionOption) { + for _, o := range list { + if o != nil { + o.ApplyAddVersionOption(m) + } + } +} + +func (o *AddVersionOptions) ApplyAddVersionOption(opts *AddVersionOptions) { + optionutils.ApplyOption(o.Overwrite, &opts.Overwrite) + o.BlobUploadOptions.ApplyBlobUploadOption(&opts.BlobUploadOptions) +} + +//////////////////////////////////////////////////////////////////////////////// + +type overwrite bool + +func (m overwrite) ApplyAddVersionOption(opts *AddVersionOptions) { + opts.Overwrite = utils.BoolP(m) +} + +// Overwrite enabled the overwrite mode for adding a component version. +func Overwrite(flag ...bool) AddVersionOption { + return overwrite(utils.OptionalDefaultedBool(true, flag...)) +} diff --git a/api/ocm/internal/repository.go b/api/ocm/internal/repository.go new file mode 100644 index 000000000..21a5a9f6b --- /dev/null +++ b/api/ocm/internal/repository.go @@ -0,0 +1,234 @@ +package internal + +import ( + "io" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/selectors/refsel" + "ocm.software/ocm/api/ocm/selectors/rscsel" + "ocm.software/ocm/api/ocm/selectors/srcsel" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/refmgmt/resource" +) + +type ReadOnlyFeature interface { + IsReadOnly() bool + // SetReadOnly is used to set the element into readonly mode. + // Once enabled it cannot be reverted. An underlying object, for + // example a CTF might be in readonly mode, forced by filesystem + // permissions. Such elements cannot be set into write mode again. + // Therefore, generally only one direction is possible. + SetReadOnly() +} + +type ComponentVersionResolver interface { + LookupComponentVersion(name string, version string) (ComponentVersionAccess, error) +} + +type RepositoryImpl interface { + GetContext() Context + + GetSpecification() RepositorySpec + ComponentLister() ComponentLister + + ExistsComponentVersion(name string, version string) (bool, error) + LookupComponentVersion(name string, version string) (ComponentVersionAccess, error) + LookupComponent(name string) (ComponentAccess, error) + + io.Closer + ReadOnlyFeature +} + +type Repository interface { + resource.ResourceView[Repository] + RepositoryImpl + + NewComponentVersion(comp, version string, overrides ...bool) (ComponentVersionAccess, error) + AddComponentVersion(cv ComponentVersionAccess, overrides ...bool) error +} + +// ConsumerIdentityProvider is an interface for object requiring +// credentials, which want to expose the ConsumerId they are +// usingto request implicit credentials. +type ConsumerIdentityProvider = credentials.ConsumerIdentityProvider + +type ( + DataAccess = blobaccess.DataAccess + BlobAccess = blobaccess.BlobAccess + MimeType = blobaccess.MimeType +) + +type ComponentAccess interface { + resource.ResourceView[ComponentAccess] + + GetContext() Context + GetName() string + + ListVersions() ([]string, error) + LookupVersion(version string) (ComponentVersionAccess, error) + HasVersion(vers string) (bool, error) + NewVersion(version string, overrides ...bool) (ComponentVersionAccess, error) + AddVersion(cv ComponentVersionAccess, overrides ...bool) error + AddVersionOpt(cv ComponentVersionAccess, opts ...AddVersionOption) error + + io.Closer +} + +// AccessProvider assembled methods provided +// and used for access methods. +// It is provided for resources in a component version +// with the base support implementation in package cpi. +// But it can also be provided by resource provisioners +// used to feed the ComponentVersionAccess.SetResourceByAccess +// or the ComponentVersionAccessSetSourceByAccess +// method. +type AccessProvider interface { + GetOCMContext() Context + ReferenceHint() string + + Access() (AccessSpec, error) + AccessMethod() (AccessMethod, error) + + GlobalAccess() AccessSpec + + blobaccess.BlobAccessProvider +} + +type ArtifactAccess[M any] interface { + Meta() *M + GetComponentVersion() (ComponentVersionAccess, error) + AccessProvider +} + +type ( + ResourceMeta = compdesc.ResourceMeta + ResourceAccess = ArtifactAccess[ResourceMeta] +) + +type ( + SourceMeta = compdesc.SourceMeta + SourceAccess = ArtifactAccess[SourceMeta] +) + +type ComponentReference = compdesc.ComponentReference + +type ComponentVersionAccess interface { + resource.ResourceView[ComponentVersionAccess] + common.VersionedElement + io.Closer + ReadOnlyFeature + + GetContext() Context + Repository() Repository + GetDescriptor() *compdesc.ComponentDescriptor + + DiscardChanges() + IsPersistent() bool + + GetProvider() *compdesc.Provider + SetProvider(provider *compdesc.Provider) error + + GetResource(meta metav1.Identity) (ResourceAccess, error) + GetResourceIndex(meta metav1.Identity) int + GetResourceByIndex(i int) (ResourceAccess, error) + GetResources() []ResourceAccess + SelectResources(sel ...rscsel.Selector) ([]ResourceAccess, error) + + // + // Deprecated: use GetResources with selector arguments. + //nolint: staticcheck // deprecated + GetResourcesByName(name string, selectors ...compdesc.IdentitySelector) ([]ResourceAccess, error) + + // + // Deprecated: use GetResources with selector arguments. + //nolint: staticcheck // deprecated + GetResourcesByIdentitySelectors(selectors ...compdesc.IdentitySelector) ([]ResourceAccess, error) + + // + // Deprecated: use GetResources with selector arguments. + //nolint: staticcheck // deprecated + GetResourcesByResourceSelectors(selectors ...compdesc.ResourceSelector) ([]ResourceAccess, error) + SetResource(*ResourceMeta, compdesc.AccessSpec, ...ModificationOption) error + SetResourceByAccess(art ResourceAccess, modopts ...BlobModificationOption) error + + GetSource(meta metav1.Identity) (SourceAccess, error) + GetSourceIndex(meta metav1.Identity) int + GetSourceByIndex(i int) (SourceAccess, error) + GetSources() []SourceAccess + SelectSources(sel ...srcsel.Selector) ([]SourceAccess, error) + + // Deprecated: use GetResources with appropriate selectors. + //nolint: staticcheck // deprecated + GetSourcesByName(name string, selectors ...compdesc.IdentitySelector) ([]SourceAccess, error) + // SetSource updates or sets anew source. The options only use the + // target options. All other options are ignored. + SetSource(*SourceMeta, compdesc.AccessSpec, ...TargetOption) error + // SetSourceByAccess updates or sets anew source. The options only use the + // target options. All other options are ignored. + SetSourceByAccess(art SourceAccess, opts ...TargetOption) error + + GetReference(meta metav1.Identity) (ComponentReference, error) + GetReferenceIndex(meta metav1.Identity) int + GetReferenceByIndex(i int) (ComponentReference, error) + GetReferences() []ComponentReference + SelectReferences(sel ...refsel.Selector) ([]ComponentReference, error) + + // Deprecated: use GetReferences with appropriate selectors. + //nolint: staticcheck // deprecated + GetReferencesByName(name string, selectors ...compdesc.IdentitySelector) (compdesc.References, error) + + // Deprecated: use GetReferences with appropriate selectors. + //nolint: staticcheck // deprecated + GetReferencesByIdentitySelectors(selectors ...compdesc.IdentitySelector) (compdesc.References, error) + + // Deprecated: use GetReferences with appropriate selectors. + //nolint: staticcheck // deprecated + GetReferencesByReferenceSelectors(selectors ...compdesc.ReferenceSelector) (compdesc.References, error) + SetReference(ref *ComponentReference, opts ...TargetOption) error + + // AddBlob adds a local blob and returns an appropriate local access spec. + AddBlob(blob BlobAccess, artType, refName string, global AccessSpec, opts ...BlobUploadOption) (AccessSpec, error) + + // AdjustResourceAccess is used to modify the access spec. The old and new one MUST refer to the same content. + AdjustResourceAccess(meta *ResourceMeta, acc compdesc.AccessSpec, opts ...ModificationOption) error + SetResourceBlob(meta *ResourceMeta, blob BlobAccess, refname string, global AccessSpec, opts ...BlobModificationOption) error + AdjustSourceAccess(meta *SourceMeta, acc compdesc.AccessSpec) error + // SetSourceBlob updates or sets anew source. The options only use the + // target options. All other options are ignored. + SetSourceBlob(meta *SourceMeta, blob BlobAccess, refname string, global AccessSpec, opts ...TargetOption) error + + // AccessMethod provides an access method implementation for + // an access spec. This might be a repository local implementation + // or a global one. It might be called by the AccessSpec method + // to map itself to a local implementation or called directly. + // If called it should forward the call to the AccessSpec + // if and only if this specs NOT states to be local IsLocal()==false + // If the spec states to be local, the repository is responsible for + // providing a local implementation or return nil if this is + // not supported by the actual repository type. + AccessMethod(AccessSpec) (AccessMethod, error) + + // Update adds the version with all changes to the component instance it has been created for. + Update() error + + // Execute executes a function on a valid and locked component version reference. + // If it returns an error this error is forwarded. + Execute(func() error) error +} + +// ComponentLister provides the optional repository list functionality of +// a repository. +type ComponentLister interface { + // NumComponents returns the number of components found for a prefix + // If the given prefix does not end with a /, a repository with the + // prefix name is included + NumComponents(prefix string) (int, error) + + // GetNamespaces returns the name of namespaces found for a prefix + // If the given prefix does not end with a /, a repository with the + // prefix name is included + GetComponents(prefix string, closure bool) ([]string, error) +} diff --git a/api/ocm/internal/repotypes.go b/api/ocm/internal/repotypes.go new file mode 100644 index 000000000..5ec9e63fd --- /dev/null +++ b/api/ocm/internal/repotypes.go @@ -0,0 +1,158 @@ +package internal + +import ( + "encoding/json" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/generics" + "github.com/modern-go/reflect2" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" +) + +type RepositoryType interface { + runtime.VersionedTypedObjectType[RepositorySpec] + + // LocalSupportForAccessSpec checks whether a repository + // provides a local version for the access spec. + // LocalSupportForAccessSpec(ctx Context, a compdesc.AccessSpec) bool +} + +type IntermediateRepositorySpecAspect = oci.IntermediateRepositorySpecAspect + +type RepositorySpec interface { + runtime.VersionedTypedObject + + AsUniformSpec(Context) *UniformRepositorySpec + Repository(Context, credentials.Credentials) (Repository, error) +} + +type ( + RepositorySpecDecoder = runtime.TypedObjectDecoder[RepositorySpec] + RepositoryTypeProvider = runtime.KnownTypesProvider[RepositorySpec, RepositoryType] +) + +type RepositoryTypeScheme interface { + runtime.TypeScheme[RepositorySpec, RepositoryType] +} + +type _Scheme = runtime.TypeScheme[RepositorySpec, RepositoryType] + +type repositoryTypeScheme struct { + _Scheme +} + +func NewRepositoryTypeScheme(defaultDecoder RepositorySpecDecoder, base ...RepositoryTypeScheme) RepositoryTypeScheme { + scheme := runtime.MustNewDefaultTypeScheme[RepositorySpec, RepositoryType](&UnknownRepositorySpec{}, true, defaultDecoder, utils.Optional(base...)) + return &repositoryTypeScheme{scheme} +} + +func NewStrictRepositoryTypeScheme(base ...RepositoryTypeScheme) RepositoryTypeScheme { + scheme := runtime.MustNewDefaultTypeScheme[RepositorySpec, RepositoryType](nil, false, nil, utils.Optional(base...)) + return &repositoryTypeScheme{scheme} +} + +func (t *repositoryTypeScheme) KnownTypes() runtime.KnownTypes[RepositorySpec, RepositoryType] { + return t._Scheme.KnownTypes() // Goland +} + +// DefaultRepositoryTypeScheme contains all globally known access serializer. +var DefaultRepositoryTypeScheme = NewRepositoryTypeScheme(nil) + +func RegisterRepositoryType(atype RepositoryType) { + DefaultRepositoryTypeScheme.Register(atype) +} + +func CreateRepositorySpec(t runtime.TypedObject) (RepositorySpec, error) { + return DefaultRepositoryTypeScheme.Convert(t) +} + +//////////////////////////////////////////////////////////////////////////////// + +type UnknownRepositorySpec struct { + runtime.UnstructuredVersionedTypedObject `json:",inline"` +} + +var ( + _ RepositorySpec = &UnknownRepositorySpec{} + _ runtime.Unknown = &UnknownRepositorySpec{} +) + +func (_ *UnknownRepositorySpec) IsUnknown() bool { + return true +} + +func (r *UnknownRepositorySpec) AsUniformSpec(Context) *UniformRepositorySpec { + return UniformRepositorySpecForUnstructured(&r.UnstructuredVersionedTypedObject) +} + +func (r *UnknownRepositorySpec) Repository(Context, credentials.Credentials) (Repository, error) { + return nil, errors.ErrUnknown("repository type", r.GetType()) +} + +//////////////////////////////////////////////////////////////////////////////// + +type GenericRepositorySpec struct { + runtime.UnstructuredVersionedTypedObject `json:",inline"` +} + +var _ RepositorySpec = &GenericRepositorySpec{} + +func ToGenericRepositorySpec(spec RepositorySpec) (*GenericRepositorySpec, error) { + if reflect2.IsNil(spec) { + return nil, nil + } + if g, ok := spec.(*GenericRepositorySpec); ok { + return g, nil + } + data, err := json.Marshal(spec) + if err != nil { + return nil, err + } + return newGenericRepositorySpec(data, runtime.DefaultJSONEncoding) +} + +func NewGenericRepositorySpec(data []byte, unmarshaler runtime.Unmarshaler) (RepositorySpec, error) { + return generics.CastPointerR[RepositorySpec](newGenericRepositorySpec(data, unmarshaler)) +} + +func newGenericRepositorySpec(data []byte, unmarshaler runtime.Unmarshaler) (*GenericRepositorySpec, error) { + unstr := &runtime.UnstructuredVersionedTypedObject{} + if unmarshaler == nil { + unmarshaler = runtime.DefaultYAMLEncoding + } + err := unmarshaler.Unmarshal(data, unstr) + if err != nil { + return nil, err + } + return &GenericRepositorySpec{*unstr}, nil +} + +func (s *GenericRepositorySpec) AsUniformSpec(ctx Context) *UniformRepositorySpec { + eff, err := s.Evaluate(ctx) + if err != nil { + return &UniformRepositorySpec{Type: s.GetKind()} + } + return eff.AsUniformSpec(ctx) +} + +func (s *GenericRepositorySpec) Evaluate(ctx Context) (RepositorySpec, error) { + raw, err := s.GetRaw() + if err != nil { + return nil, err + } + return ctx.RepositoryTypes().Decode(raw, runtime.DefaultJSONEncoding) +} + +func (s *GenericRepositorySpec) Repository(ctx Context, creds credentials.Credentials) (Repository, error) { + spec, err := s.Evaluate(ctx) + if err != nil { + return nil, err + } + return spec.Repository(ctx, creds) +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/api/ocm/internal/resolver.go b/api/ocm/internal/resolver.go new file mode 100644 index 000000000..400ea0f19 --- /dev/null +++ b/api/ocm/internal/resolver.go @@ -0,0 +1,195 @@ +package internal + +import ( + "strings" + "sync" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/finalizer" + "github.com/mandelsoft/goutils/general" + "golang.org/x/exp/slices" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/utils" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/refmgmt" + "ocm.software/ocm/api/utils/registrations" +) + +//////////////////////////////////////////////////////////////////////////////// + +type ResolverRule struct { + prefix string + path registrations.NamePath + spec RepositorySpec + prio int +} + +func (r *ResolverRule) GetPrefix() string { + return r.prefix +} + +func (r *ResolverRule) GetSpecification() RepositorySpec { + return r.spec +} + +func (r *ResolverRule) GetPriority() int { + return r.prio +} + +// RepositoryCache is a utility object intended to be used by higher level objects such as session or resolver. Since +// the closing of the repository objects depends on the usage context (e.g. if components have been looked up in this +// repository, these components have to be closed before the repository can be closed), it is the responsibility of the +// higher level objects to close the repositories correctly. +type RepositoryCache struct { + lock sync.Mutex + repositories map[datacontext.ObjectKey]Repository +} + +func NewRepositoryCache() *RepositoryCache { + return &RepositoryCache{ + repositories: map[datacontext.ObjectKey]Repository{}, + } +} + +func (c *RepositoryCache) Reset() { + c.lock.Lock() + defer c.lock.Unlock() + + c.repositories = map[datacontext.ObjectKey]Repository{} +} + +func (c *RepositoryCache) LookupRepository(ctx Context, spec RepositorySpec) (Repository, bool, error) { + spec, err := ctx.RepositoryTypes().Convert(spec) + if err != nil { + return nil, false, err + } + keyName, err := utils.Key(spec) + if err != nil { + return nil, false, err + } + key := datacontext.ObjectKey{ + Object: ctx, + Name: keyName, + } + + c.lock.Lock() + defer c.lock.Unlock() + + if r := c.repositories[key]; r != nil { + return r, true, nil + } + repo, err := ctx.RepositoryForSpec(spec) + if err != nil { + return nil, false, err + } + c.repositories[key] = repo + return repo, false, err +} + +func NewResolverRule(prefix string, spec RepositorySpec, prio ...int) *ResolverRule { + p := registrations.NewNamePath(prefix) + return &ResolverRule{ + prefix: prefix, + path: p, + spec: spec, + prio: general.OptionalDefaulted(10, prio...), + } +} + +func (r *ResolverRule) Compare(o *ResolverRule) int { + if d := r.prio - o.prio; d != 0 { + return d + } + return r.path.Compare(o.path) +} + +func (r *ResolverRule) Match(name string) bool { + return r.prefix == "" || r.prefix == name || strings.HasPrefix(name, r.prefix+"/") +} + +// MatchingResolver hosts rule to match component version names. +// Matched names will be mapped to a specification for repository +// which should be used to look up the component version. +// Therefore, it keeps a reference to the context to use. +// +// ATTENTION: Because such an object is used by the context +// implementation, the context must be kept as ContextProvider +// to provide context views to outbound calls. +type MatchingResolver struct { + lock sync.Mutex + ctx ContextProvider + finalize finalizer.Finalizer + cache *RepositoryCache + rules []*ResolverRule +} + +func NewMatchingResolver(ctx ContextProvider, rules ...*ResolverRule) *MatchingResolver { + return &MatchingResolver{ + lock: sync.Mutex{}, + ctx: ctx, + cache: NewRepositoryCache(), + rules: nil, + } +} + +func (r *MatchingResolver) OCMContext() Context { + return r.ctx.OCMContext() +} + +func (r *MatchingResolver) Finalize() error { + r.lock.Lock() + defer r.lock.Unlock() + defer r.cache.Reset() + return r.finalize.Finalize() +} + +func (r *MatchingResolver) GetRules() []*ResolverRule { + r.lock.Lock() + defer r.lock.Unlock() + return slices.Clone(r.rules) +} + +func (r *MatchingResolver) AddRule(prefix string, spec RepositorySpec, prio ...int) { + r.lock.Lock() + defer r.lock.Unlock() + + rule := NewResolverRule(prefix, spec, prio...) + found := len(r.rules) + for i, o := range r.rules { + if o.Compare(rule) < 0 { + found = i + break + } + } + r.rules = slices.Insert(r.rules, found, rule) +} + +func (r *MatchingResolver) LookupComponentVersion(name string, version string) (ComponentVersionAccess, error) { + r.lock.Lock() + defer r.lock.Unlock() + + ctx := r.ctx.OCMContext() + for _, rule := range r.rules { + if rule.Match(name) { + repo, cached, err := r.cache.LookupRepository(ctx, rule.spec) + if err != nil { + return nil, err + } + if !cached { + // Even though the matching resolver is closed, there might be components or component versions, which + // contain a reference to the repository. Still, it shall be possible to close the matching resolver. + refmgmt.Lazy(repo) + r.finalize.Close(repo) + } + cv, err := repo.LookupComponentVersion(name, version) + if err == nil && cv != nil { + return cv, nil + } + if !errors.IsErrNotFoundKind(err, KIND_COMPONENTVERSION) { + return nil, err + } + } + } + return nil, errors.ErrNotFound(KIND_COMPONENTVERSION, common.NewNameVersion(name, version).String()) +} diff --git a/pkg/contexts/ocm/internal/suite_test.go b/api/ocm/internal/suite_test.go similarity index 100% rename from pkg/contexts/ocm/internal/suite_test.go rename to api/ocm/internal/suite_test.go diff --git a/api/ocm/internal/uniform.go b/api/ocm/internal/uniform.go new file mode 100644 index 000000000..3aea8d8aa --- /dev/null +++ b/api/ocm/internal/uniform.go @@ -0,0 +1,212 @@ +package internal + +import ( + "encoding/json" + "fmt" + "sync" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/maputils" + "github.com/sirupsen/logrus" + "golang.org/x/exp/slices" + + "ocm.software/ocm/api/utils/runtime" +) + +const ( + dockerHubDomain = "docker.io" + dockerHubLegacyDomain = "index.docker.io" +) + +// UniformRepositorySpec is generic specification of the repository +// for handling as part of standard references. +type UniformRepositorySpec struct { + // Type + Type string `json:"type,omitempty"` + // Scheme + Scheme string `json:"scheme,omitempty"` + // Host is the hostname of an ocm ref. + Host string `json:"host,omitempty"` + // SubPath is the sub path spec used to host component versions + SubPath string `json:"subPath,omitempty"` + // Info is the file path used to host ctf component versions + Info string `json:"filePath,omitempty"` + + // CreateIfMissing indicates whether a file based or dynamic repo should be created if it does not exist + CreateIfMissing bool `json:"createIfMissing,omitempty"` + // TypeHint should be set if CreateIfMissing is true to help to decide what kind of repo to create + TypeHint string `json:"typeHint,omitempty"` +} + +// CredHost fallback to legacy docker domain if applicable +// this is how containerd translates the old domain for DockerHub to the new one, taken from containerd/reference/docker/reference.go:674. +func (r *UniformRepositorySpec) CredHost() string { + if r.Host == dockerHubDomain { + return dockerHubLegacyDomain + } + return r.Host +} + +func (u *UniformRepositorySpec) String() string { + t := u.Type + if t != "" { + t += "::" + } + if u.Info != "" { + return fmt.Sprintf("%s%s", t, u.Info) + } else { + s := u.SubPath + if s != "" { + s = "/" + s + } + return fmt.Sprintf("%s%s%s", t, u.Host, s) + } +} + +func UniformRepositorySpecForUnstructured(un *runtime.UnstructuredVersionedTypedObject) *UniformRepositorySpec { + m := un.Object.FlatCopy() + delete(m, runtime.ATTR_TYPE) + + d, err := json.Marshal(m) + if err != nil { + logrus.Error(err) + } + + return &UniformRepositorySpec{Type: un.Type, Info: string(d)} +} + +type RepositorySpecHandler interface { + MapReference(ctx Context, u *UniformRepositorySpec) (RepositorySpec, error) +} + +type RepositorySpecHandlers interface { + Register(hdlr RepositorySpecHandler, types ...string) + Copy() RepositorySpecHandlers + KnownTypeNames() []string + GetHandlers(typ string) []RepositorySpecHandler + MapUniformRepositorySpec(ctx Context, u *UniformRepositorySpec) (RepositorySpec, error) +} + +var DefaultRepositorySpecHandlers = NewRepositorySpecHandlers() + +func RegisterRepositorySpecHandler(hdlr RepositorySpecHandler, types ...string) { + DefaultRepositorySpecHandlers.Register(hdlr, types...) +} + +type specHandlers struct { + lock sync.RWMutex + handlers map[string][]RepositorySpecHandler +} + +func NewRepositorySpecHandlers() RepositorySpecHandlers { + return &specHandlers{handlers: map[string][]RepositorySpecHandler{}} +} + +func (s *specHandlers) Register(hdlr RepositorySpecHandler, types ...string) { + s.lock.Lock() + defer s.lock.Unlock() + + if hdlr != nil { + for _, typ := range types { + s.handlers[typ] = append(s.handlers[typ], hdlr) + } + } +} + +func (s *specHandlers) Copy() RepositorySpecHandlers { + s.lock.RLock() + defer s.lock.RUnlock() + + n := NewRepositorySpecHandlers().(*specHandlers) + for typ, hdlrs := range s.handlers { + n.handlers[typ] = slices.Clone(hdlrs) + } + return n +} + +func (s *specHandlers) KnownTypeNames() []string { + s.lock.RLock() + defer s.lock.RUnlock() + + return maputils.OrderedKeys(s.handlers) +} + +func (s *specHandlers) GetHandlers(typ string) []RepositorySpecHandler { + s.lock.RLock() + defer s.lock.RUnlock() + + hdlrs := s.handlers[typ] + if len(hdlrs) == 0 { + return nil + } + result := make([]RepositorySpecHandler, len(hdlrs)) + copy(result, hdlrs) + return result +} + +func (s *specHandlers) MapUniformRepositorySpec(ctx Context, u *UniformRepositorySpec) (RepositorySpec, error) { + var err error + s.lock.RLock() + defer s.lock.RUnlock() + + if u.Info != "" && string(u.Info[0]) == "{" && u.Host == "" && u.Scheme == "" && u.SubPath == "" { + data, err := runtime.CompleteSpecWithType(u.Type, []byte(u.Info)) + if err != nil { + return nil, err + } + return ctx.RepositorySpecForConfig(data, runtime.DefaultJSONEncoding) + } + + deferr := errors.ErrNotSupported("uniform repository ref", u.String()) + if u.Type == "" { + if u.Info != "" { + spec := ctx.GetAlias(u.Info) + if spec != nil { + return spec, nil + } + deferr = errors.ErrUnknown("repository", u.Info) + } + if u.Host != "" { + spec := ctx.GetAlias(u.Host) + if spec != nil { + return spec, nil + } + deferr = errors.ErrUnknown("repository", u.Host) + } + } + + spec, err := s.handle(ctx, u.Type, u) + if err != nil || spec != nil { + return spec, err + } + + if u.Info != "" { + spec := &runtime.UnstructuredVersionedTypedObject{} + err = runtime.DefaultJSONEncoding.Unmarshal([]byte(u.Info), spec) + if err == nil { + if spec.GetType() == spec.GetKind() && spec.GetVersion() == "v1" { // only type set, use it as version + spec.SetType(u.Type + runtime.VersionSeparator + spec.GetType()) + } + if spec.GetKind() != u.Type { + return nil, errors.ErrInvalid() + } + return ctx.RepositoryTypes().Convert(spec) + } + } + + spec, err = s.handle(ctx, "*", u) + if spec == nil && err == nil { + err = deferr + } + return spec, err +} + +func (s *specHandlers) handle(ctx Context, typ string, u *UniformRepositorySpec) (RepositorySpec, error) { + for _, h := range s.handlers[typ] { + spec, err := h.MapReference(ctx, u) + if err != nil || spec != nil { + return spec, err + } + } + return nil, nil +} diff --git a/pkg/contexts/ocm/internal/uniform_test.go b/api/ocm/internal/uniform_test.go similarity index 93% rename from pkg/contexts/ocm/internal/uniform_test.go rename to api/ocm/internal/uniform_test.go index 5405070f7..9f718a9e5 100644 --- a/pkg/contexts/ocm/internal/uniform_test.go +++ b/api/ocm/internal/uniform_test.go @@ -4,7 +4,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" + "ocm.software/ocm/api/ocm/internal" ) type SpecHandler struct { diff --git a/api/ocm/modopts.go b/api/ocm/modopts.go new file mode 100644 index 000000000..5c271f0e6 --- /dev/null +++ b/api/ocm/modopts.go @@ -0,0 +1,118 @@ +package ocm + +import ( + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/attrs/hashattr" + "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" + "ocm.software/ocm/api/ocm/internal" + "ocm.software/ocm/api/tech/signing/hasher/sha256" +) + +type ( + TargetElement = internal.TargetElement + TargetOption = internal.TargetOption + TargetOptions = internal.TargetOptions + + ModificationOption = internal.ModificationOption + ModificationOptions = internal.ModificationOptions + + BlobModificationOption = internal.BlobModificationOption + BlobModificationOptions = internal.BlobModificationOptions + + BlobUploadOption = internal.BlobUploadOption + BlobUploadOptions = internal.BlobUploadOptions + + AddVersionOption = internal.AddVersionOption + AddVersionOptions = internal.AddVersionOptions +) + +//////////////////////////////////////////////////////////////////////////////// + +func NewAddVersionOptions(list ...AddVersionOption) *AddVersionOptions { + return internal.NewAddVersionOptions(list...) +} + +// Overwrite enabled the overwrite mode for adding a component version. +func Overwrite(flag ...bool) AddVersionOption { + return internal.Overwrite(flag...) +} + +//////////////////////////////////////////////////////////////////////////////// + +func NewBlobModificationOptions(list ...BlobModificationOption) *BlobModificationOptions { + return internal.NewBlobModificationOptions(list...) +} + +//////////////////////////////////////////////////////////////////////////////// + +func NewBlobUploadOptions(list ...BlobUploadOption) *BlobUploadOptions { + return internal.NewBlobUploadOptions(list...) +} + +func UseBlobHandlers(h BlobHandlerProvider) internal.BlobOptionImpl { + return internal.UseBlobHandlers(h) +} + +//////////////////////////////////////////////////////////////////////////////// + +func NewModificationOptions(list ...ModificationOption) *ModificationOptions { + return internal.NewModificationOptions(list...) +} + +func TargetIndex(idx int) internal.TargetOptionImpl { + return internal.TargetIndex(-1) +} + +const AppendElement = internal.TargetIndex(-1) + +var UpdateElement = internal.UpdateElement + +func TargetIdentity(id v1.Identity) internal.TargetOptionImpl { + return internal.TargetIdentity(id) +} + +func TargetIdentityOrCreate(id v1.Identity) internal.TargetOptionImpl { + return internal.TargetIdentityOrAppend(id) +} + +func ModifyResource(flag ...bool) internal.ModOptionImpl { + return internal.ModifyResource(flag...) +} + +func AcceptExistentDigests(flag ...bool) internal.ModOptionImpl { + return internal.AcceptExistentDigests(flag...) +} + +func WithDefaultHashAlgorithm(algo ...string) internal.ModOptionImpl { + return internal.WithDefaultHashAlgorithm(algo...) +} + +func WithHasherProvider(prov HasherProvider) internal.ModOptionImpl { + return internal.WithHasherProvider(prov) +} + +func SkipVerify(flag ...bool) internal.ModOptionImpl { + return internal.SkipVerify(flag...) +} + +// SkipDigest disables digest creation if enabled. +// +// Deprecated: for legacy code, only. +func SkipDigest(flag ...bool) internal.ModOptionImpl { + return internal.SkipDigest(flag...) +} + +/////////////////////////////////////////////////////// + +func CompleteModificationOptions(ctx ContextProvider, m *ModificationOptions) { + attr := hashattr.Get(ctx.OCMContext()) + if m.DefaultHashAlgorithm == "" { + m.DefaultHashAlgorithm = attr.DefaultHasher + } + if m.DefaultHashAlgorithm == "" { + m.DefaultHashAlgorithm = sha256.Algorithm + } + if m.HasherProvider == nil { + m.HasherProvider = signingattr.Get(ctx.OCMContext()) + } +} diff --git a/api/ocm/ocmutils/check/check.go b/api/ocm/ocmutils/check/check.go new file mode 100644 index 000000000..e1153a0d7 --- /dev/null +++ b/api/ocm/ocmutils/check/check.go @@ -0,0 +1,148 @@ +package check + +import ( + "encoding/json" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + common "ocm.software/ocm/api/utils/misc" +) + +type Result struct { + Missing Missing `json:"missing,omitempty"` + Resources []metav1.Identity `json:"resources,omitempty"` + Sources []metav1.Identity `json:"sources,omitempty"` +} + +func newResult() *Result { + return &Result{Missing: Missing{}} +} + +func (r *Result) IsEmpty() bool { + if r == nil { + return true + } + return len(r.Missing) == 0 && len(r.Resources) == 0 && len(r.Sources) == 0 +} + +type Missing map[common.NameVersion]common.History + +func (n Missing) MarshalJSON() ([]byte, error) { + m := map[string]common.History{} + for k, v := range n { + m[k.String()] = v + } + return json.Marshal(m) +} + +type Cache = map[common.NameVersion]*Result + +//////////////////////////////////////////////////////////////////////////////// + +// Check provides a check object for checking component versions +// to completely available in an ocm repository. +// By default, it only checks the component reference closure +// to be in the same repository. +// Optionally, it is possible to check for inlined +// resources and sources, also. +func Check(opts ...Option) *Options { + return optionutils.EvalOptions(opts...) +} + +func (a *Options) For(cv ocm.ComponentVersionAccess) (*Result, error) { + cache := Cache{} + return a.handle(cache, cv, common.History{common.VersionedElementKey(cv)}) +} + +func (a *Options) ForId(repo ocm.Repository, id common.NameVersion) (*Result, error) { + cv, err := repo.LookupComponentVersion(id.GetName(), id.GetVersion()) + if err != nil { + return nil, err + } + defer cv.Close() + return a.For(cv) +} + +func (a *Options) check(cache Cache, repo ocm.Repository, id common.NameVersion, h common.History) (*Result, error) { + if r, ok := cache[id]; ok { + return r, nil + } + + err := h.Add(ocm.KIND_COMPONENTVERSION, id) + if err != nil { + return nil, err + } + cv, err := repo.LookupComponentVersion(id.GetName(), id.GetVersion()) + if err != nil { + if !errors.IsErrNotFound(err) { + return nil, err + } + err = nil + } + + var r *Result + if cv == nil { + r = &Result{Missing: Missing{id: h}} + } else { + defer cv.Close() + r, err = a.handle(cache, cv, h) + } + cache[id] = r + return r, err +} + +func (a *Options) handle(cache Cache, cv ocm.ComponentVersionAccess, h common.History) (*Result, error) { + result := newResult() + + for _, r := range cv.GetDescriptor().References { + id := common.NewNameVersion(r.ComponentName, r.Version) + n, err := a.check(cache, cv.Repository(), id, h) + if err != nil { + return result, err + } + if n != nil && len(n.Missing) > 0 { + for k, v := range n.Missing { + result.Missing[k] = v + } + } + } + + var err error + + list := errors.ErrorList{} + if optionutils.AsBool(a.CheckLocalResources) { + result.Resources, err = a.checkArtifacts(cv.GetContext(), cv.GetDescriptor().Resources) + list.Add(err) + } + if optionutils.AsBool(a.CheckLocalSources) { + result.Sources, err = a.checkArtifacts(cv.GetContext(), cv.GetDescriptor().Sources) + list.Add(err) + } + if result.IsEmpty() { + result = nil + } + return result, list.Result() +} + +func (a *Options) checkArtifacts(ctx ocm.Context, accessor compdesc.ElementAccessor) ([]metav1.Identity, error) { + var result []metav1.Identity + + list := errors.ErrorList{} + for i := 0; i < accessor.Len(); i++ { + e := accessor.Get(i).(compdesc.ElementArtifactAccessor) + + m, err := ctx.AccessSpecForSpec(e.GetAccess()) + if err == nil { + if !m.IsLocal(ctx) { + result = append(result, e.GetMeta().GetIdentity(accessor)) + } + } else { + list.Add(err) + } + } + return result, list.Result() +} diff --git a/pkg/contexts/ocm/utils/check/check_test.go b/api/ocm/ocmutils/check/check_test.go similarity index 90% rename from pkg/contexts/ocm/utils/check/check_test.go rename to api/ocm/ocmutils/check/check_test.go index b2e4de792..2a9a6c9ca 100644 --- a/pkg/contexts/ocm/utils/check/check_test.go +++ b/api/ocm/ocmutils/check/check_test.go @@ -6,16 +6,16 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "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/common" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils/check" + . "ocm.software/ocm/api/helper/builder" + + "ocm.software/ocm/api/ocm" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/ocmutils/check" + "ocm.software/ocm/api/utils/accessio" + common "ocm.software/ocm/api/utils/misc" ) const ( diff --git a/api/ocm/ocmutils/check/options.go b/api/ocm/ocmutils/check/options.go new file mode 100644 index 000000000..3cb14a4a2 --- /dev/null +++ b/api/ocm/ocmutils/check/options.go @@ -0,0 +1,45 @@ +package check + +import ( + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/utils" +) + +type Option = optionutils.Option[*Options] + +type Options struct { + CheckLocalResources *bool + CheckLocalSources *bool +} + +var _ Option = (*Options)(nil) + +func (o *Options) ApplyTo(opts *Options) { + optionutils.ApplyOption(o.CheckLocalResources, &opts.CheckLocalResources) + optionutils.ApplyOption(o.CheckLocalSources, &opts.CheckLocalSources) +} + +//////////////////////////////////////////////////////////////////////////////// + +type localSources bool + +func LocalSourcesOnly(b ...bool) Option { + return localSources(utils.OptionalDefaultedBool(true, b...)) +} + +func (l localSources) ApplyTo(t *Options) { + t.CheckLocalSources = optionutils.PointerTo(bool(l)) +} + +//////////////////////////////////////////////////////////////////////////////// + +type localResources bool + +func LocalResourcesOnly(b ...bool) Option { + return localResources(utils.OptionalDefaultedBool(true, b...)) +} + +func (l localResources) ApplyTo(t *Options) { + t.CheckLocalResources = optionutils.PointerTo(bool(l)) +} diff --git a/pkg/contexts/ocm/utils/check/suite_test.go b/api/ocm/ocmutils/check/suite_test.go similarity index 100% rename from pkg/contexts/ocm/utils/check/suite_test.go rename to api/ocm/ocmutils/check/suite_test.go diff --git a/api/ocm/ocmutils/configure.go b/api/ocm/ocmutils/configure.go new file mode 100644 index 000000000..c7a766522 --- /dev/null +++ b/api/ocm/ocmutils/configure.go @@ -0,0 +1,132 @@ +package utils + +import ( + "fmt" + "os" + "strings" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/pkgutils" + "github.com/mandelsoft/spiff/features" + "github.com/mandelsoft/spiff/spiffing" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/config" + configcfg "ocm.software/ocm/api/config/extensions/config" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/ocmutils/defaultconfigregistry" + "ocm.software/ocm/api/utils" +) + +const DEFAULT_OCM_CONFIG = ".ocmconfig" + +const DEFAULT_OCM_CONFIG_DIR = ".ocm" + +func Configure(ctxp config.ContextProvider, path string, fss ...vfs.FileSystem) (ocm.Context, error) { + ctx, _, err := Configure2(ctxp, path, fss...) + return ctx, err +} + +func Configure2(ctx config.ContextProvider, path string, fss ...vfs.FileSystem) (ocm.Context, config.Config, error) { + var ocmctx ocm.Context + + cfg, err := configcfg.NewAggregator(false) + if err != nil { + return nil, nil, err + } + fs := utils.FileSystem(fss...) + if ctx == nil { + ocmctx = ocm.DefaultContext() + ctx = ocmctx + } else { + if c, ok := ctx.(ocm.Context); ok { + ocmctx = c + } + } + h, _ := os.UserHomeDir() + if path == "" { + if h != "" { + cfg := h + "/" + DEFAULT_OCM_CONFIG + if ok, err := vfs.FileExists(fs, cfg); ok && err == nil { + path = cfg + } else { + cfg := h + "/" + DEFAULT_OCM_CONFIG_DIR + "/ocmconfig" + if ok, err := vfs.FileExists(fs, cfg); ok && err == nil { + path = cfg + } else { + cfg := h + "/" + DEFAULT_OCM_CONFIG_DIR + "/config" + if ok, err := vfs.FileExists(fs, cfg); ok && err == nil { + path = cfg + } + } + } + } + } + if path != "" && path != "None" { + if strings.HasPrefix(path, "~"+string(os.PathSeparator)) { + if len(h) == 0 { + return nil, nil, fmt.Errorf("no home directory found for resolving path of ocm config file %q", path) + } + path = h + path[1:] + } + data, err := vfs.ReadFile(fs, path) + if err != nil { + return nil, nil, errors.Wrapf(err, "cannot read ocm config file %q", path) + } + + if eff, err := ConfigureByData2(ctx, data, path); err != nil { + return nil, nil, err + } else { + err = cfg.AddConfig(eff) + if err != nil { + return nil, nil, err + } + } + } else { + for _, h := range defaultconfigregistry.Get() { + desc, def, err := h(ctx.ConfigContext()) + if err != nil { + return nil, nil, err + } + if def != nil { + name, err := pkgutils.GetPackageName(h) + if err != nil { + name = "unknown handler" + } + err = ctx.ConfigContext().ApplyConfig(def, fmt.Sprintf("%s: %s", name, desc)) + if err != nil { + return nil, nil, errors.Wrapf(err, "cannot apply default config from %s(%s)", name, desc) + } + err = cfg.AddConfig(def) + if err != nil { + return nil, nil, err + } + } + } + } + return ocmctx, cfg.Get(), nil +} + +func ConfigureByData(ctx config.ContextProvider, data []byte, info string) error { + _, err := ConfigureByData2(ctx, data, info) + return err +} + +func ConfigureByData2(ctx config.ContextProvider, data []byte, info string) (config.Config, error) { + var err error + + sctx := spiffing.New().WithFeatures(features.INTERPOLATION, features.CONTROL) + data, err = spiffing.Process(sctx, spiffing.NewSourceData(info, data)) + if err != nil { + return nil, errors.Wrapf(err, "processing ocm config %q", info) + } + cfg, err := ctx.ConfigContext().GetConfigForData(data, nil) + if err != nil { + return nil, errors.Wrapf(err, "invalid ocm config file %q", info) + } + err = ctx.ConfigContext().ApplyConfig(cfg, info) + if err != nil { + return nil, errors.Wrapf(err, "cannot apply ocm config %q", info) + } + return cfg, nil +} diff --git a/api/ocm/ocmutils/defaultconfigregistry/configure.go b/api/ocm/ocmutils/defaultconfigregistry/configure.go new file mode 100644 index 000000000..ab3bd0561 --- /dev/null +++ b/api/ocm/ocmutils/defaultconfigregistry/configure.go @@ -0,0 +1,68 @@ +package defaultconfigregistry + +import ( + "strings" + "sync" + + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/utils/listformat" +) + +type DefaultConfigHandler func(cfg config.Context) (string, config.Config, error) + +type defaultConfigurationRegistry struct { + lock sync.Mutex + + list []entry +} + +type entry struct { + desc string + handler DefaultConfigHandler +} + +func (r *defaultConfigurationRegistry) Register(h DefaultConfigHandler, desc string) { + r.lock.Lock() + defer r.lock.Unlock() + + r.list = append(r.list, entry{desc, h}) +} + +func (r *defaultConfigurationRegistry) Get() []DefaultConfigHandler { + r.lock.Lock() + defer r.lock.Unlock() + + var result []DefaultConfigHandler + for _, h := range r.list { + result = append(result, h.handler) + } + return result +} + +func (r *defaultConfigurationRegistry) Description() string { + var result []string + + r.lock.Lock() + defer r.lock.Unlock() + + for _, h := range defaultConfigRegistry.list { + if h.desc != "" { + result = append(result, strings.TrimSpace(h.desc)) + } + } + return listformat.FormatDescriptionList("", result...) +} + +var defaultConfigRegistry = &defaultConfigurationRegistry{} + +func RegisterDefaultConfigHandler(h DefaultConfigHandler, desc string) { + defaultConfigRegistry.Register(h, desc) +} + +func Get() []DefaultConfigHandler { + return defaultConfigRegistry.Get() +} + +func Description() string { + return defaultConfigRegistry.Description() +} diff --git a/api/ocm/ocmutils/localize/README.md b/api/ocm/ocmutils/localize/README.md new file mode 100644 index 000000000..216c8d4df --- /dev/null +++ b/api/ocm/ocmutils/localize/README.md @@ -0,0 +1,58 @@ +# Localization Tools + +This package (pkg/contexts/ocm/util/localize) contains some +yaml format definitions, (format.go) given by a Go structure. +and functions usable for the modification of filesystem snapshots. + +It covers +- OCM based localization descriptions used to describe image + location substitution and the +- merging with configuration input. + +This mechanism is intended to support the mapping of generic filesystem +snapshots containing deployment descriptions or manifests to +an installation specific snapshot incorporating the local location +of container images based on OCM component versions and installation specific +configuration values. + +The description format describes two basic specifications that incorporate external +information provided by a component version or some user config. + +- struct/format `Localization` describe a specification to + inject/modify image locations based on the information provided + by a component version. The substitution descriptions use relative resources + references to specify the resource whose image reference is used as basis + for the substituted value. + + This specification is intended to be stored as part of a resource artifact in a + component version which is then used to apply it. Thereby the contained relative + [resource reference](../../../../docs/ocm/model.md#resource-reference) + are evaluated against the component version containing the specification to resolve + the final image location. + +- struct/format `Configuration` describes a specification for + applying a dedicated config value taken from a configuration source + to a filesytem snapshot. + +The function `Localize` and `Configure` accept a list of such +specifications and map them into an environment agnostic set of +`Substitution` specifications, which contain resolved data values, only. +A third function `Substitute` takes those environment agnostic specifications +and apply them to a filesystem. + +Finally, a compound specification `InstantiationRules` is provided, +that combines all those descriptions with the specification of the snapshot +resource and further helper parts, like json scheme validation for config files. + +Such a specification object can be applied by the function `Instantiate` +together with configuration values to +a component version. As substitution result it returns a virtual filesystem +with the snapshot according to the resolved substitutions. To get access to the +template resource containing the filesystem snapshot to be instantiated, the +configured downloaders (package `pkg/context/ocm/download`) is used. +Therefore, this method can be used together with any own resource type as long as +an appropriate downloader is configured. + +Additionally, there is a set of more basic types and methods, which can be used +to describe end execute localizations for single data objects (see `ImageMappings`, +`LocalizeMappings` and `SubstituteMappings`). \ No newline at end of file diff --git a/api/ocm/ocmutils/localize/config.go b/api/ocm/ocmutils/localize/config.go new file mode 100644 index 000000000..5c89b9a1b --- /dev/null +++ b/api/ocm/ocmutils/localize/config.go @@ -0,0 +1,129 @@ +package localize + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/sliceutils" + "github.com/mandelsoft/spiff/spiffing" + + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + utils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/api/utils/spiff" +) + +func Configure( + mappings []Configuration, cursubst []Substitution, + cv ocm.ComponentVersionAccess, resolver ocm.ComponentVersionResolver, + template []byte, config []byte, libraries []metav1.ResourceReference, schemedata []byte, +) (Substitutions, error) { + var err error + + if len(mappings) == 0 { + return nil, nil + } + if len(config) == 0 { + if len(schemedata) > 0 { + if err = spiff.ValidateByScheme([]byte("{}"), schemedata); err != nil { + return nil, errors.Wrapf(err, "config validation failed") + } + } + if len(template) == 0 { + return nil, nil + } + } + + stubs := spiff.Options{} + for i, lib := range libraries { + opt, err := func() (spiff.OptionFunction, error) { + res, eff, err := utils.ResolveResourceReference(cv, lib, resolver) + if err != nil { + return nil, errors.ErrNotFound("library resource %s not found", lib.String()) + } + defer eff.Close() + m, err := res.AccessMethod() + if err != nil { + return nil, errors.ErrNotFound("error accessing access method for library resource", lib.String()) + } + data, err := m.Get() + m.Close() + if err != nil { + return nil, errors.ErrNotFound("cannot access library resource", lib.String()) + } + return spiff.StubData(fmt.Sprintf("spiff lib%d", i), data), nil + }() + if err != nil { + return nil, err + } + stubs.Add(opt) + } + + if len(schemedata) > 0 { + err = spiff.ValidateByScheme(config, schemedata) + if err != nil { + return nil, errors.Wrapf(err, "validation failed") + } + } + + extlist := []interface{}{} + for _, e := range cursubst { + // TODO: escape spiff expressions, but should not occur, so omit it so far + extlist = append(extlist, e) + } + + cfglist := []interface{}{} + for _, e := range mappings { + cfglist = append(cfglist, e) + } + + var temp map[string]interface{} + if len(template) == 0 { + temp = map[string]interface{}{ + "adjustments": extlist, + "configRules": cfglist, + } + } else { + if err := runtime.DefaultYAMLEncoding.Unmarshal(template, &temp); err != nil { + return nil, errors.Wrapf(err, "cannot unmarshal template") + } + + if _, ok := temp["adjustments"]; ok { + return nil, errors.Newf("template may not contain 'adjustments'") + } + temp["adjustments"] = extlist + + if cur, ok := temp["configRules"]; ok { + if l, ok := cur.([]interface{}); !ok { + return nil, errors.Newf("node 'configRules' in template must be a list of configuration requests") + } else { + temp["configRules"] = sliceutils.CopyAppend(l, cfglist...) + } + } else { + temp["configRules"] = cfglist + } + } + + if _, ok := temp["utilities"]; !ok { + // prepare merging of spiff libraries using the node utilities as root path + temp["utilities"] = "" + } + + template, err = runtime.DefaultJSONEncoding.Marshal(temp) + if err != nil { + return nil, errors.Wrapf(err, "cannot marshal adjustments") + } + + config, err = spiff.CascadeWith(spiff.TemplateData("adjustments", template), stubs, spiff.Values(config), spiff.Mode(spiffing.MODE_PRIVATE)) + if err != nil { + return nil, errors.Wrapf(err, "error processing template") + } + + var subst struct { + Adjustments Substitutions `json:"adjustments,omitempty"` + ConfigRules Substitutions `json:"configRules,omitempty"` + } + err = runtime.DefaultYAMLEncoding.Unmarshal(config, &subst) + return sliceutils.CopyAppend(subst.Adjustments, subst.ConfigRules...), err +} diff --git a/api/ocm/ocmutils/localize/config_test.go b/api/ocm/ocmutils/localize/config_test.go new file mode 100644 index 000000000..dbc1e30ae --- /dev/null +++ b/api/ocm/ocmutils/localize/config_test.go @@ -0,0 +1,230 @@ +package localize_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/helper/builder" + "ocm.software/ocm/api/ocm" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/ocmutils/localize" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/mime" +) + +var config = []byte(` +values: + a: va + b: vb + c: + a: vca +`) + +var _ = Describe("config value mapping", func() { + It("handles simple values substitution", func() { + configs := UnmarshalConfigurations(` +- name: test1 + file: file1 + path: a.b.c + value: value +`) + subst, err := localize.Configure(configs, nil, nil, nil, nil, config, nil, nil) + Expect(err).To(Succeed()) + Expect(subst).To(Equal(UnmarshalSubstitutions(` +- name: test1 + file: file1 + path: a.b.c + value: value +`))) + }) + + It("handles simple expression substitution", func() { + configs := UnmarshalConfigurations(` +- name: test1 + file: file1 + path: a.b.c + value: (( values.a )) +`) + subst, err := localize.Configure(configs, nil, nil, nil, nil, config, nil, nil) + Expect(err).To(Succeed()) + Expect(subst).To(Equal(UnmarshalSubstitutions(` +- name: test1 + file: file1 + path: a.b.c + value: va +`))) + }) + + It("fails for invalid expression substitution", func() { + configs := UnmarshalConfigurations(` +- file: file1 + path: a.b.c + value: (( values.x )) +`) + _, err := localize.Configure(configs, nil, nil, nil, nil, config, nil, nil) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("*'values.x' not found")) + }) + + It("handles expression substitution with substitution context", func() { + context := ` +- name: context + file: file1 + path: a.b.c + value: contextvalue +` + configs := UnmarshalConfigurations(` +- name: test1 + file: file1 + path: a.b.c + value: (( adjustments.context.value )) +`) + subst, err := localize.Configure(configs, UnmarshalSubstitutions(context), nil, nil, nil, config, nil, nil) + Expect(err).To(Succeed()) + Expect(subst).To(Equal(UnmarshalSubstitutions(context + ` +- name: test1 + file: file1 + path: a.b.c + value: contextvalue +`))) + }) + It("handles expression substitution with template data", func() { + template := []byte(` +helper: + help: (( |x|->"helped " x )) +`) + configs := UnmarshalConfigurations(` +- name: test1 + file: file1 + path: a.b.c + value: (( helper.help(values.a) )) +`) + subst, err := localize.Configure(configs, nil, nil, nil, template, config, nil, nil) + Expect(err).To(Succeed()) + Expect(subst).To(Equal(UnmarshalSubstitutions(` +- name: test1 + file: file1 + path: a.b.c + value: helped va +`))) + }) + + const ( + ARCHIVE = "archive.ctf" + COMPONENT = "github.com/comp" + VERSION = "1.0.0" + LIB = "lib" + ) + + Context("with libs", func() { + var ( + repo ocm.Repository + cv ocm.ComponentVersionAccess + env *builder.Builder + ) + + BeforeEach(func() { + env = builder.NewBuilder(nil) + env.OCMCommonTransport(ARCHIVE, accessio.FormatDirectory, func() { + env.Component(COMPONENT, func() { + env.Version(VERSION, func() { + env.Provider("mandelsoft") + env.Resource(LIB, "", "Spiff", v1.LocalRelation, func() { + env.BlobStringData(mime.MIME_YAML, ` +utilities: + <<<: (( &inject &temporary(merge || ~) )) + + lib: + help: (( |x|->"lib " x )) +`) + }) + }) + }) + }) + + var err error + repo, err = ctf.Open(ocm.DefaultContext(), accessobj.ACC_READONLY, ARCHIVE, 0, env) + Expect(err).To(Succeed()) + + cv, err = repo.LookupComponentVersion(COMPONENT, VERSION) + Expect(err).To(Succeed()) + }) + + AfterEach(func() { + Expect(cv.Close()).To(Succeed()) + Expect(repo.Close()).To(Succeed()) + vfs.Cleanup(env) + }) + + It("uses resolved library from component version", func() { + configs := UnmarshalConfigurations(` +- name: test1 + file: file1 + path: a.b.c + value: (( utilities.lib.help(values.a) )) +`) + + libs := []v1.ResourceReference{ + { + Resource: v1.NewIdentity(LIB), + }, + } + subst, err := localize.Configure(configs, nil, cv, nil, nil, config, libs, nil) + Expect(err).To(Succeed()) + Expect(subst).To(Equal(UnmarshalSubstitutions(` +- name: test1 + file: file1 + path: a.b.c + value: lib va +`))) + }) + + It("uses templated configRules", func() { + configs := UnmarshalConfigurations(` +- name: test1 + file: file1 + path: a.b.c + value: (( values.a )) +`) + + template := ` +list: [ "a", "b" ] +helper: + <<<: (( &template )) + name: (( "gen" k )) + file: file1 + path: (( "some.path." k )) + value: (( values.a )) + +configRules: + - <<<: (( map[.list|k|->*.helper] )) +` + + libs := []v1.ResourceReference{ + { + Resource: v1.NewIdentity(LIB), + }, + } + subst, err := localize.Configure(configs, nil, cv, nil, []byte(template), config, libs, nil) + Expect(err).To(Succeed()) + Expect(subst).To(Equal(UnmarshalSubstitutions(` +- name: gena + file: file1 + path: some.path.a + value: va +- name: genb + file: file1 + path: some.path.b + value: va +- name: test1 + file: file1 + path: a.b.c + value: va +`))) + }) + }) +}) diff --git a/api/ocm/ocmutils/localize/format.go b/api/ocm/ocmutils/localize/format.go new file mode 100644 index 000000000..b84df070b --- /dev/null +++ b/api/ocm/ocmutils/localize/format.go @@ -0,0 +1,223 @@ +package localize + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/ocm" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + utils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/utils/runtime" +) + +// Definition of inbound substitution requests. +// - Localizations image locations substitution resolved using a component version +// - Configurations configuration substitution resolved by provided value data. +// +// Such requests can be given to merge externally provided data into +// some filesystem template. +// The evaluation of such requests results in a list +// of resolved substitution requests that can be applied without +// further value context to a filesystem structure. + +// ImageMapping describes a dedicated substitution of parts +// of container image names based on a relative OCM resource reference. +type ImageMapping struct { + // The optional but unique(!) name of the mapping to support referencing mapping entries + Name string `json:"name,omitempty"` + + // The resource reference used to resolve the substitution + v1.ResourceReference `json:",inline"` + + // The optional variants for the value determination + + // Path in target to substitute by the image tag/digest + Tag string `json:"tag,omitempty"` + // Path in target to substitute the image repository + Repository string `json:"repository,omitempty"` + // Path in target to substitute the complete image + Image string `json:"image,omitempty"` +} + +type ImageMappings []ImageMapping + +func (m *ImageMapping) Evaluate(idx int, cv ocm.ComponentVersionAccess, resolver ocm.ComponentVersionResolver) (ValueMappings, error) { + name := "image mapping" + if m.Name != "" { + name = fmt.Sprintf("%s %q", name, m.Name) + } else { + if idx >= 0 { + name = fmt.Sprintf("%s %d", name, idx+1) + } + } + acc, rcv, err := utils.ResolveResourceReference(cv, m.ResourceReference, resolver) + if err != nil { + return nil, errors.Wrapf(err, "mapping", fmt.Sprintf("%s (%s)", name, &m.ResourceReference)) + } + defer rcv.Close() + ref, err := utils.GetOCIArtifactRef(cv.GetContext(), acc) + if err != nil { + return nil, errors.Wrapf(err, "mapping %s: cannot resolve resource %s to an OCI Reference", name, &m.ResourceReference) + } + ix := strings.Index(ref, ":") + if ix < 0 { + ix = strings.Index(ref, "@") + if ix < 0 { + return nil, errors.Wrapf(err, "mapping %s: image tag or digest missing (%s)", name, ref) + } + } + repo := ref[:ix] + tag := ref[ix+1:] + + cnt := 0 + if m.Repository != "" { + cnt++ + } + if m.Tag != "" { + cnt++ + } + if m.Image != "" { + cnt++ + } + if cnt == 0 { + return nil, fmt.Errorf("no substitution target given for %s", name) + } + + var result ValueMappings + var r *ValueMapping + if m.Repository != "" { + if r, err = NewValueMapping(substitutionName(name, "repository", cnt), m.Repository, repo); err != nil { + return nil, errors.Wrapf(err, "setting repository for %s", substitutionName(name, "repository", cnt)) + } + result = append(result, *r) + } + if m.Tag != "" { + if r, err = NewValueMapping(substitutionName(name, "tag", cnt), m.Tag, tag); err != nil { + return nil, errors.Wrapf(err, "setting tag for %s", substitutionName(name, "tag", cnt)) + } + result = append(result, *r) + } + if m.Image != "" { + if r, err = NewValueMapping(substitutionName(name, "image", cnt), m.Image, ref); err != nil { + return nil, errors.Wrapf(err, "setting image for %s", substitutionName(name, "image", cnt)) + } + result = append(result, *r) + } + return result, nil +} + +// Localization is a request to substitute an image location. +// The specification describes substitution targets given by the file path and +// the YAML/JSON value paths of the elements in this file. +// The substitution value is calculated +// from the access specification of the given resource provided by the actual +// component version. +type Localization struct { + // The path of the file for the substitution + FilePath string `json:"file"` + // The image mapping request + ImageMapping `json:",inline"` +} + +// Configuration is a request to substitute a configuration value. +// The specification describes substitution targets given by the file path and +// the YAML/JSON value paths of the elements in this file. +// The substitution value is calculated +// by the value expression (spiff) based on given config data. +// It has the same structure as Substitution, but is a request based +// on external configuration data, while a Substitution describes a fixed target +// value. +type Configuration Substitution + +type ValueMapping struct { + // The optional but unique(!) name of the mapping to support referencing mapping entries + Name string `json:"name,omitempty"` + // The target path for the value substitution + ValuePath string `json:"path"` + // The value to set + Value json.RawMessage `json:"value"` +} + +func NewValueMapping(name, path string, value interface{}) (*ValueMapping, error) { + var ( + v []byte + err error + ) + + if value != nil { + v, err = runtime.DefaultJSONEncoding.Marshal(value) + if err != nil { + return nil, fmt.Errorf("cannot marshal substitution value: %w", err) + } + } + return &ValueMapping{ + Name: name, + ValuePath: path, + Value: v, + }, nil +} + +type ValueMappings []ValueMapping + +func (s *ValueMappings) Add(name, path string, value interface{}) error { + m, err := NewValueMapping(name, path, value) + if err != nil { + return err + } + *s = append(*s, *m) + return nil +} + +// Here comes the structure used for resolved execution requests. +// They can be applied to a filesystem content without further external information. +// It basically has the same structure as the configuration request, but +// the given value is just the target value without any further interpretation. +// This way configuration requests could just provide dedicated values, also + +// Substitution is a request to substitute the YAML/JSON +// element given by the value path in the given file path by the given +// direct value. +type Substitution struct { + // The path of the file for the substitution + FilePath string `json:"file"` + // The field mapping toapply to given file path + ValueMapping `json:",inline"` +} + +func (s *Substitution) GetValue() (interface{}, error) { + var value interface{} + err := runtime.DefaultYAMLEncoding.Unmarshal(s.Value, &value) + return value, err +} + +type Substitutions []Substitution + +func (s *Substitutions) AddValueMapping(m *ValueMapping, file string) { + *s = append(*s, Substitution{ + FilePath: file, + ValueMapping: *m, + }) +} + +func (s *Substitutions) Add(name, file, path string, value interface{}) error { + m, err := NewValueMapping(name, path, value) + if err != nil { + return err + } + s.AddValueMapping(m, file) + return nil +} + +// InstantiationRules bundle the localization of a filesystem resource +// covering image localization and applying instance configuration. +type InstantiationRules struct { + Template v1.ResourceReference `json:"templateResource,omitempty"` + LocalizationRules []Localization `json:"localizationRules,omitempty"` + ConfigRules []Configuration `json:"configRules,omitempty"` + ConfigScheme json.RawMessage `json:"configScheme,omitempty"` + ConfigTemplate json.RawMessage `json:"configTemplate,omitempty"` + ConfigLibraries []v1.ResourceReference `json:"configLibraries,omitempty"` +} diff --git a/pkg/contexts/ocm/utils/localize/instantiate.go b/api/ocm/ocmutils/localize/instantiate.go similarity index 82% rename from pkg/contexts/ocm/utils/localize/instantiate.go rename to api/ocm/ocmutils/localize/instantiate.go index 8792cc175..a510ca800 100644 --- a/pkg/contexts/ocm/utils/localize/instantiate.go +++ b/api/ocm/ocmutils/localize/instantiate.go @@ -4,11 +4,11 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" + "ocm.software/ocm/api/ocm" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/download" + utils "ocm.software/ocm/api/ocm/ocmutils" + common "ocm.software/ocm/api/utils/misc" ) func Instantiate(rules *InstantiationRules, cv ocm.ComponentVersionAccess, resolver ocm.ComponentVersionResolver, config []byte, fs vfs.FileSystem, types ...string) error { diff --git a/pkg/contexts/ocm/utils/localize/instantiate_test.go b/api/ocm/ocmutils/localize/instantiate_test.go similarity index 78% rename from pkg/contexts/ocm/utils/localize/instantiate_test.go rename to api/ocm/ocmutils/localize/instantiate_test.go index ad5f2f562..a08f76d8d 100644 --- a/pkg/contexts/ocm/utils/localize/instantiate_test.go +++ b/api/ocm/ocmutils/localize/instantiate_test.go @@ -11,18 +11,18 @@ import ( "github.com/mandelsoft/vfs/pkg/osfs" "github.com/mandelsoft/vfs/pkg/vfs" - "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/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download/handlers/dirtree" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils/localize" - "github.com/open-component-model/ocm/pkg/env/builder" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/utils/tarutils" + "ocm.software/ocm/api/helper/builder" + "ocm.software/ocm/api/ocm" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + "ocm.software/ocm/api/ocm/extensions/download" + "ocm.software/ocm/api/ocm/extensions/download/handlers/dirtree" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/ocmutils/localize" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/api/utils/tarutils" ) const RESOURCE_TYPE = "gitOpsTemplate" diff --git a/pkg/contexts/ocm/utils/localize/localize.go b/api/ocm/ocmutils/localize/localize.go similarity index 95% rename from pkg/contexts/ocm/utils/localize/localize.go rename to api/ocm/ocmutils/localize/localize.go index d4f7e7ac6..07a463f66 100644 --- a/pkg/contexts/ocm/utils/localize/localize.go +++ b/api/ocm/ocmutils/localize/localize.go @@ -1,7 +1,7 @@ package localize import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm" + "ocm.software/ocm/api/ocm" ) // Localize maps a list of filesystem related localization requests to an diff --git a/pkg/contexts/ocm/utils/localize/localize_test.go b/api/ocm/ocmutils/localize/localize_test.go similarity index 81% rename from pkg/contexts/ocm/utils/localize/localize_test.go rename to api/ocm/ocmutils/localize/localize_test.go index 005e31737..5e616d1db 100644 --- a/pkg/contexts/ocm/utils/localize/localize_test.go +++ b/api/ocm/ocmutils/localize/localize_test.go @@ -6,14 +6,14 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" - "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/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils/localize" - "github.com/open-component-model/ocm/pkg/env/builder" + "ocm.software/ocm/api/helper/builder" + "ocm.software/ocm/api/ocm" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/ocmutils/localize" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" ) var _ = Describe("image value mapping", func() { diff --git a/api/ocm/ocmutils/localize/subst.go b/api/ocm/ocmutils/localize/subst.go new file mode 100644 index 000000000..c9162ac5d --- /dev/null +++ b/api/ocm/ocmutils/localize/subst.go @@ -0,0 +1,80 @@ +package localize + +import ( + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/utils/subst" +) + +func Substitute(subs Substitutions, fs vfs.FileSystem) error { + files := map[string]subst.SubstitutionTarget{} + + for i, s := range subs { + file, err := vfs.Canonical(fs, s.FilePath, true) + if err != nil { + return errors.Wrapf(err, "entry %d", i) + } + + fi, ok := files[file] + if !ok { + s, err := subst.ParseFile(file, fs) + if err != nil { + return errors.Wrapf(err, "entry %d", i) + } + files[file], fi = s, s + } + + if err = fi.SubstituteByData(s.ValuePath, s.Value); err != nil { + return errors.Wrapf(err, "entry %d: cannot substitute value", i+1) + } + } + + for file, fi := range files { + data, err := fi.Content() + if err != nil { + return errors.Wrapf(err, "cannot marshal %q after substitution ", file) + } + + if err := vfs.WriteFile(fs, file, data, vfs.ModePerm); err != nil { + return errors.Wrapf(err, "file %q", file) + } + } + return nil +} + +// SubstituteMappings substitutes value mappings for a dedicated substitution target. +func SubstituteMappings(subs ValueMappings, target subst.SubstitutionTarget) error { + for i, s := range subs { + if err := target.SubstituteByData(s.ValuePath, s.Value); err != nil { + return errors.Wrapf(err, "entry %d: cannot substitute value", i+1) + } + } + return nil +} + +// SubstituteMappingsForData substitutes value mappings for some data. +func SubstituteMappingsForData(subs ValueMappings, data []byte) ([]byte, error) { + target, err := subst.Parse(data) + if err != nil { + return nil, err + } + err = SubstituteMappings(subs, target) + if err != nil { + return nil, err + } + return target.Content() +} + +/* +2024-04-28 Don't see any use of this in either ocm or ocm-controller +As it exposes the implementation detail of what YAML model we use +_and_ we're switching to yqlib from goccy comment it out. +func Set(content *ast.File, path string, value *ast.File) error { + p, err := yaml.PathString("$." + path) + if err != nil { + return errors.Wrapf(err, "invalid substitution path") + } + return p.ReplaceWithFile(content, value) +} +*/ diff --git a/pkg/contexts/ocm/utils/localize/subst_test.go b/api/ocm/ocmutils/localize/subst_test.go similarity index 91% rename from pkg/contexts/ocm/utils/localize/subst_test.go rename to api/ocm/ocmutils/localize/subst_test.go index 8d4abd874..d2be590a3 100644 --- a/pkg/contexts/ocm/utils/localize/subst_test.go +++ b/api/ocm/ocmutils/localize/subst_test.go @@ -7,9 +7,9 @@ import ( "github.com/mandelsoft/vfs/pkg/projectionfs" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils/localize" - env2 "github.com/open-component-model/ocm/pkg/env" - "github.com/open-component-model/ocm/pkg/env/builder" + "ocm.software/ocm/api/helper/builder" + env2 "ocm.software/ocm/api/helper/env" + "ocm.software/ocm/api/ocm/ocmutils/localize" ) var _ = Describe("value substitution in filesystem", func() { diff --git a/pkg/contexts/ocm/utils/localize/suite_test.go b/api/ocm/ocmutils/localize/suite_test.go similarity index 100% rename from pkg/contexts/ocm/utils/localize/suite_test.go rename to api/ocm/ocmutils/localize/suite_test.go diff --git a/pkg/contexts/ocm/utils/localize/target_test.go b/api/ocm/ocmutils/localize/target_test.go similarity index 85% rename from pkg/contexts/ocm/utils/localize/target_test.go rename to api/ocm/ocmutils/localize/target_test.go index 0da19d9fc..d266f0fa4 100644 --- a/pkg/contexts/ocm/utils/localize/target_test.go +++ b/api/ocm/ocmutils/localize/target_test.go @@ -6,14 +6,14 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" - "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/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils/localize" - "github.com/open-component-model/ocm/pkg/env/builder" + "ocm.software/ocm/api/helper/builder" + "ocm.software/ocm/api/ocm" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/ocmutils/localize" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" ) var _ = Describe("value substitution in single target", func() { diff --git a/pkg/contexts/ocm/utils/localize/testdata/dir/manifest1.yaml b/api/ocm/ocmutils/localize/testdata/dir/manifest1.yaml similarity index 100% rename from pkg/contexts/ocm/utils/localize/testdata/dir/manifest1.yaml rename to api/ocm/ocmutils/localize/testdata/dir/manifest1.yaml diff --git a/pkg/contexts/ocm/utils/localize/testdata/dir/manifest2.yaml b/api/ocm/ocmutils/localize/testdata/dir/manifest2.yaml similarity index 100% rename from pkg/contexts/ocm/utils/localize/testdata/dir/manifest2.yaml rename to api/ocm/ocmutils/localize/testdata/dir/manifest2.yaml diff --git a/pkg/contexts/ocm/utils/localize/testdata/dir/some.json b/api/ocm/ocmutils/localize/testdata/dir/some.json similarity index 100% rename from pkg/contexts/ocm/utils/localize/testdata/dir/some.json rename to api/ocm/ocmutils/localize/testdata/dir/some.json diff --git a/api/ocm/ocmutils/localize/utils_test.go b/api/ocm/ocmutils/localize/utils_test.go new file mode 100644 index 000000000..9c2ab3f4c --- /dev/null +++ b/api/ocm/ocmutils/localize/utils_test.go @@ -0,0 +1,58 @@ +package localize_test + +import ( + . "github.com/onsi/gomega" + + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/ocm/ocmutils/localize" + "ocm.software/ocm/api/utils/runtime" +) + +func UnmarshalLocalizations(data string) []localize.Localization { + var v []localize.Localization + Expect(runtime.DefaultYAMLEncoding.Unmarshal([]byte(data), &v)).To(Succeed()) + return v +} + +func UnmarshalConfigurations(data string) []localize.Configuration { + var v []localize.Configuration + Expect(runtime.DefaultYAMLEncoding.Unmarshal([]byte(data), &v)).To(Succeed()) + return v +} + +func UnmarshalSubstitutions(data string) localize.Substitutions { + var v localize.Substitutions + Expect(runtime.DefaultYAMLEncoding.Unmarshal([]byte(data), &v)).To(Succeed()) + return v +} + +func UnmarshalImageMappings(data string) localize.ImageMappings { + var v localize.ImageMappings + Expect(runtime.DefaultYAMLEncoding.Unmarshal([]byte(data), &v)).To(Succeed()) + return v +} + +func UnmarshalValueMappings(data string) localize.ValueMappings { + var v localize.ValueMappings + Expect(runtime.DefaultYAMLEncoding.Unmarshal([]byte(data), &v)).To(Succeed()) + return v +} + +func UnmarshalInstRules(data string) *localize.InstantiationRules { + var v localize.InstantiationRules + Expect(runtime.DefaultYAMLEncoding.Unmarshal([]byte(data), &v)).To(Succeed()) + return &v +} + +func CheckYAMLFile(path string, fs vfs.FileSystem, content string) { + data, err := vfs.ReadFile(fs, path) + ExpectWithOffset(1, err).To(Succeed()) + ExpectWithOffset(1, string(data)).To(MatchYAML(content)) +} + +func CheckJSONFile(path string, fs vfs.FileSystem, content string) { + data, err := vfs.ReadFile(fs, path) + ExpectWithOffset(1, err).To(Succeed()) + ExpectWithOffset(1, string(data)).To(MatchJSON(content)) +} diff --git a/api/ocm/ocmutils/registry/registry.go b/api/ocm/ocmutils/registry/registry.go new file mode 100644 index 000000000..4ed3c41d9 --- /dev/null +++ b/api/ocm/ocmutils/registry/registry.go @@ -0,0 +1,109 @@ +package registry + +import ( + "strings" + + "github.com/mandelsoft/goutils/set" + "golang.org/x/exp/slices" + + "ocm.software/ocm/api/utils/mime" +) + +type Key[K any] interface { + comparable + IsValid() bool + + GetMediaType() string + GetArtifactType() string + + SetArtifact(arttype, medtatype string) K +} + +type Registry[H any, K Key[K]] struct { + mappings map[K][]H +} + +func NewRegistry[H any, K Key[K]]() *Registry[H, K] { + return &Registry[H, K]{ + mappings: map[K][]H{}, + } +} + +func (p *Registry[H, K]) Copy() *Registry[H, K] { + r := &Registry[H, K]{ + mappings: map[K][]H{}, + } + for k, v := range p.mappings { + r.mappings[k] = slices.Clone(v) + } + return r +} + +func (p *Registry[H, K]) lookupMedia(key K) []H { + lookup := key + for { + if h, ok := p.mappings[lookup]; ok { + return h + } + if i := strings.LastIndex(lookup.GetMediaType(), "+"); i > 0 { + lookup = lookup.SetArtifact(lookup.GetArtifactType(), lookup.GetMediaType()[:i]) + } else { + break + } + } + return nil +} + +func (p *Registry[H, K]) GetHandler(key K) []H { + r := p.mappings[key] + if r == nil { + return nil + } + return slices.Clone(r) +} + +func (p *Registry[H, K]) LookupHandler(key K) []H { + h := p.lookupMedia(key) + if len(h) > 0 { + return h + } + + mediatype := key.GetMediaType() + arttype := key.GetArtifactType() + if h := p.mappings[key.SetArtifact(arttype, "")]; len(h) > 0 { + return h + } + return p.lookupMedia(key.SetArtifact("", mediatype)) +} + +func (p *Registry[H, K]) LookupKeys(key K) set.Set[K] { + found := set.New[K]() + + if len(p.LookupHandler(key)) > 0 { + found.Add(key) + } + if key.GetArtifactType() == "" { + for k := range p.mappings { + if k.GetArtifactType() != "" { + c := k.SetArtifact(k.GetArtifactType(), key.GetMediaType()) + if !found.Contains(c) && len(p.LookupHandler(c)) > 0 { + found.Add(c) + } + } + } + } else { + for k := range p.mappings { + if mime.IsMoreGeneral(key.GetMediaType(), k.GetMediaType()) { + c := k.SetArtifact(key.GetArtifactType(), k.GetMediaType()) + if !found.Contains(c) && len(p.LookupHandler(c)) > 0 { + found.Add(c) + } + } + } + } + return found +} + +func (p *Registry[H, K]) Register(key K, h H) { + p.mappings[key] = append(p.mappings[key], h) +} diff --git a/api/ocm/ocmutils/registry/registry_test.go b/api/ocm/ocmutils/registry/registry_test.go new file mode 100644 index 000000000..878090289 --- /dev/null +++ b/api/ocm/ocmutils/registry/registry_test.go @@ -0,0 +1,111 @@ +package registry_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/mandelsoft/goutils/set" + + "ocm.software/ocm/api/ocm/ocmutils/registry" +) + +var ( + aKey = registry.RegistrationKey{}.SetArtifact("a", "") + mKey = registry.RegistrationKey{}.SetArtifact("", "m") + amKey = registry.RegistrationKey{}.SetArtifact("a", "m") + a1mKey = registry.RegistrationKey{}.SetArtifact("a1", "m") + am1Key = registry.RegistrationKey{}.SetArtifact("a", "m1") + amtarKey = registry.RegistrationKey{}.SetArtifact("a", "m+tar") +) + +var _ = Describe("lookup", func() { + var reg *registry.Registry[string, registry.RegistrationKey] + + BeforeEach(func() { + reg = registry.NewRegistry[string, registry.RegistrationKey]() + }) + + Context("lookup handler", func() { + It("looks up complete", func() { + reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m"), "test") + reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m1"), "testm") + reg.Register(registry.RegistrationKey{}.SetArtifact("a1", "m"), "testa") + + h := reg.LookupHandler(amKey) + Expect(h).To(Equal([]string{"test"})) + }) + + It("looks up partial artifact", func() { + reg.Register(registry.RegistrationKey{}.SetArtifact("a", ""), "test") + reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m1"), "testm") + reg.Register(registry.RegistrationKey{}.SetArtifact("a1", "m"), "testa") + + h := reg.LookupHandler(amKey) + Expect(h).To(Equal([]string{"test"})) + }) + + It("looks up partial media", func() { + reg.Register(registry.RegistrationKey{}.SetArtifact("", "m"), "test") + reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m1"), "testm") + reg.Register(registry.RegistrationKey{}.SetArtifact("a1", "m"), "testa") + + h := reg.LookupHandler(amKey) + Expect(h).To(Equal([]string{"test"})) + }) + + It("looks complete with media sub type", func() { + reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m"), "test") + reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m1"), "testm") + reg.Register(registry.RegistrationKey{}.SetArtifact("a1", "m"), "testa") + + h := reg.LookupHandler(amtarKey) + Expect(h).To(Equal([]string{"test"})) + }) + + It("looks partial with media sub type", func() { + reg.Register(registry.RegistrationKey{}.SetArtifact("", "m"), "test") + reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m1"), "testm") + reg.Register(registry.RegistrationKey{}.SetArtifact("a1", "m"), "testa") + + h := reg.LookupHandler(amtarKey) + Expect(h).To(Equal([]string{"test"})) + }) + + It("prefers art", func() { + reg.Register(registry.RegistrationKey{}.SetArtifact("", "m"), "testm") + reg.Register(registry.RegistrationKey{}.SetArtifact("a", ""), "test") + reg.Register(registry.RegistrationKey{}.SetArtifact("a1", "m"), "testa") + + h := reg.LookupHandler(amtarKey) + Expect(h).To(Equal([]string{"test"})) + }) + }) + + Context("lookup keys", func() { + It("fills missing", func() { + reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m"), "test") + reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m1"), "testm") + reg.Register(registry.RegistrationKey{}.SetArtifact("a1", "m"), "testa") + + keys := reg.LookupKeys(aKey) + Expect(keys).To(Equal(set.New(amKey, am1Key))) + }) + + It("fills missing", func() { + reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m+tar"), "test") + reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m1"), "testm") + reg.Register(registry.RegistrationKey{}.SetArtifact("a1", "m"), "testa") + + keys := reg.LookupKeys(mKey) + Expect(keys).To(Equal(set.New(a1mKey))) + }) + It("fills more specific media", func() { + reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m+tar"), "test") + reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m1"), "testm") + reg.Register(registry.RegistrationKey{}.SetArtifact("a1", "m"), "testa") + + keys := reg.LookupKeys(amKey) + Expect(keys).To(Equal(set.New(amtarKey))) + }) + }) +}) diff --git a/pkg/contexts/ocm/utils/registry/regkey.go b/api/ocm/ocmutils/registry/regkey.go similarity index 100% rename from pkg/contexts/ocm/utils/registry/regkey.go rename to api/ocm/ocmutils/registry/regkey.go diff --git a/pkg/contexts/ocm/utils/registry/suite_test.go b/api/ocm/ocmutils/registry/suite_test.go similarity index 100% rename from pkg/contexts/ocm/utils/registry/suite_test.go rename to api/ocm/ocmutils/registry/suite_test.go diff --git a/api/ocm/ocmutils/resource.go b/api/ocm/ocmutils/resource.go new file mode 100644 index 000000000..aea241335 --- /dev/null +++ b/api/ocm/ocmutils/resource.go @@ -0,0 +1,58 @@ +package utils + +import ( + "io" + + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/iotools" +) + +func GetResourceData(acc ocm.AccessProvider) ([]byte, error) { + return blobaccess.DataFromProvider(acc) +} + +func GetResourceDataForPath(cv ocm.ComponentVersionAccess, id metav1.Identity, path []metav1.Identity, resolvers ...ocm.ComponentVersionResolver) ([]byte, error) { + return GetResourceDataForRef(cv, metav1.NewNestedResourceRef(id, path), resolvers...) +} + +func GetResourceDataForRef(cv ocm.ComponentVersionAccess, ref metav1.ResourceReference, resolvers ...ocm.ComponentVersionResolver) ([]byte, error) { + var res ocm.ComponentVersionResolver + if len(resolvers) > 0 { + res = ocm.NewCompoundResolver(resolvers...) + } + a, c, err := ResolveResourceReference(cv, ref, res) + if err != nil { + return nil, err + } + defer c.Close() + + return GetResourceData(a) +} + +func GetResourceReader(acc ocm.AccessProvider) (io.ReadCloser, error) { + return blobaccess.ReaderFromProvider(acc) +} + +func GetResourceReaderForPath(cv ocm.ComponentVersionAccess, id metav1.Identity, path []metav1.Identity, resolvers ...ocm.ComponentVersionResolver) (io.ReadCloser, error) { + return GetResourceReaderForRef(cv, metav1.NewNestedResourceRef(id, path), resolvers...) +} + +func GetResourceReaderForRef(cv ocm.ComponentVersionAccess, ref metav1.ResourceReference, resolvers ...ocm.ComponentVersionResolver) (io.ReadCloser, error) { + var res ocm.ComponentVersionResolver + if len(resolvers) > 0 { + res = ocm.NewCompoundResolver(resolvers...) + } + a, c, err := ResolveResourceReference(cv, ref, res) + if err != nil { + return nil, err + } + + reader, err := GetResourceReader(a) + if err != nil { + c.Close() + return nil, err + } + return iotools.AddReaderCloser(reader, c), nil +} diff --git a/pkg/contexts/ocm/utils/resourceref.go b/api/ocm/ocmutils/resourceref.go similarity index 93% rename from pkg/contexts/ocm/utils/resourceref.go rename to api/ocm/ocmutils/resourceref.go index a567b2cac..60bbc01b5 100644 --- a/pkg/contexts/ocm/utils/resourceref.go +++ b/api/ocm/ocmutils/resourceref.go @@ -7,9 +7,9 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + common "ocm.software/ocm/api/utils/misc" ) func ResolveReferencePath(cv ocm.ComponentVersionAccess, path []metav1.Identity, resolver ocm.ComponentVersionResolver) (ocm.ComponentVersionAccess, error) { diff --git a/pkg/contexts/ocm/utils/resourceref_test.go b/api/ocm/ocmutils/resourceref_test.go similarity index 87% rename from pkg/contexts/ocm/utils/resourceref_test.go rename to api/ocm/ocmutils/resourceref_test.go index 1194ca3ec..6df6658a2 100644 --- a/pkg/contexts/ocm/utils/resourceref_test.go +++ b/api/ocm/ocmutils/resourceref_test.go @@ -6,16 +6,16 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "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/common/accessio" - "github.com/open-component-model/ocm/pkg/common/accessobj" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" + . "ocm.software/ocm/api/helper/builder" + + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + utils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/mime" ) const ( diff --git a/pkg/contexts/ocm/utils/suite_test.go b/api/ocm/ocmutils/suite_test.go similarity index 100% rename from pkg/contexts/ocm/utils/suite_test.go rename to api/ocm/ocmutils/suite_test.go diff --git a/api/ocm/ocmutils/utils.go b/api/ocm/ocmutils/utils.go new file mode 100644 index 000000000..341a0073f --- /dev/null +++ b/api/ocm/ocmutils/utils.go @@ -0,0 +1,29 @@ +package utils + +import ( + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" +) + +func GetOCIArtifactRef(ctxp ocm.ContextProvider, r ocm.ResourceAccess) (string, error) { + ctx := ctxp.OCMContext() + + acc, err := r.Access() + if err != nil || acc == nil { + return "", err + } + + var cv cpi.ComponentVersionAccess + if p, ok := r.(cpi.ComponentVersionProvider); ok { + cv, err = p.GetComponentVersion() + if err != nil { + return "", errors.Wrapf(err, "cannot access component version for re/source") + } + defer cv.Close() + } + + return ociartifact.GetOCIArtifactReference(ctx, acc, cv) +} diff --git a/api/ocm/ocmutils/walk.go b/api/ocm/ocmutils/walk.go new file mode 100644 index 000000000..a2f0c15c9 --- /dev/null +++ b/api/ocm/ocmutils/walk.go @@ -0,0 +1,53 @@ +package utils + +import ( + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/ocm" + common "ocm.software/ocm/api/utils/misc" +) + +// WalkingStep is used to process a component version during graph traversal. +// If returned true, the traversal process follows local component references- +// If an error is returned the traversal is aborted with this error, +// Additionally, an info object of type T can be registered in the state for the +// component version. +type WalkingStep[T any] func(state common.WalkingState[T, ocm.ComponentVersionAccess]) (bool, error) + +// Walk traverses a component version graph using the WalkingStep to +// process found component version. +func Walk[T any](closure common.NameVersionInfo[T], cv ocm.ComponentVersionAccess, resolver ocm.ComponentVersionResolver, step WalkingStep[T]) (common.NameVersionInfo[T], error) { + if closure == nil { + closure = common.NameVersionInfo[T]{} + } + state := common.WalkingState[T, ocm.ComponentVersionAccess]{ + Closure: closure, + Context: cv, + } + err := walk[T](state, cv, resolver, step) + return closure, err +} + +func walk[T any](state common.WalkingState[T, ocm.ComponentVersionAccess], cv ocm.ComponentVersionAccess, resolver ocm.ComponentVersionResolver, step WalkingStep[T]) error { + nv := common.VersionedElementKey(cv) + if ok, err := state.Add(ocm.KIND_COMPONENTVERSION, nv); !ok || err != nil { + return err + } + c, err := step(state) + if err != nil { + return errors.Wrapf(err, "%s", state.History) + } + if c { + for _, ref := range cv.GetDescriptor().References { + n, err := resolver.LookupComponentVersion(ref.ComponentName, ref.Version) + if err != nil { + return errors.Wrapf(err, "%s: cannot resolve ref %s", state.History, ref) + } + err = errors.Join(walk[T](state, n, resolver, step), n.Close()) + if err != nil { + return err + } + } + } + return nil +} diff --git a/pkg/contexts/ocm/plugin/cache/dircache.go b/api/ocm/plugin/cache/dircache.go similarity index 100% rename from pkg/contexts/ocm/plugin/cache/dircache.go rename to api/ocm/plugin/cache/dircache.go diff --git a/pkg/contexts/ocm/plugin/cache/doc.go b/api/ocm/plugin/cache/doc.go similarity index 100% rename from pkg/contexts/ocm/plugin/cache/doc.go rename to api/ocm/plugin/cache/doc.go diff --git a/api/ocm/plugin/cache/exec.go b/api/ocm/plugin/cache/exec.go new file mode 100644 index 000000000..29488b287 --- /dev/null +++ b/api/ocm/plugin/cache/exec.go @@ -0,0 +1,70 @@ +package cache + +import ( + "encoding/json" + "fmt" + "io" + "os/exec" + "strings" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/ocm/plugin/ppi/cmds" + "ocm.software/ocm/api/utils/accessio" +) + +func Exec(execpath string, config json.RawMessage, r io.Reader, w io.Writer, args ...string) ([]byte, error) { + if len(config) > 0 { + args = append([]string{"-c", string(config)}, args...) + } + cmd := exec.Command(execpath, args...) + stdout := w + if w == nil { + stdout = accessio.LimitBuffer(accessio.DESCRIPTOR_LIMIT) + } + + stderr := accessio.LimitBuffer(accessio.DESCRIPTOR_LIMIT) + + cmd.Stdin = r + cmd.Stdout = stdout + cmd.Stderr = stderr + + err := cmd.Run() + if err != nil { + var result cmds.Error + var cerr error + data := strings.TrimSpace(string(stderr.Bytes())) + if len(data) == 0 { + cerr = errors.New(err.Error()) + } else { + // handle implicit error output from go run + i := strings.LastIndex(data, "\n") + for i > 0 && (i == len(data)-1 || strings.HasPrefix(data[i+1:], "exit status")) { + data = data[:i] + i = strings.LastIndex(data, "\n") + } + if err := json.Unmarshal([]byte(data), &result); err == nil { + cerr = errors.New(result.Error) + } else { + if err := json.Unmarshal([]byte(data[i+1:]), &result); err == nil { + cerr = errors.New(result.Error) + // TODO pass effective stderr from CLI + data = strings.TrimSpace(data[:i]) + if len(data) > 0 { + cerr = fmt.Errorf("%w: with stderr\n%s", cerr, data) + } + } else { + cerr = fmt.Errorf("[%s]", data) + } + } + } + return nil, cerr + } + if l, ok := stdout.(*accessio.LimitedBuffer); ok { + if l.Exceeded() { + return nil, fmt.Errorf("stdout limit exceeded") + } + return l.Bytes(), nil + } + return nil, nil +} diff --git a/api/ocm/plugin/cache/plugin.go b/api/ocm/plugin/cache/plugin.go new file mode 100644 index 000000000..f6412b7a2 --- /dev/null +++ b/api/ocm/plugin/cache/plugin.go @@ -0,0 +1,243 @@ +package cache + +import ( + "golang.org/x/exp/slices" + + "ocm.software/ocm/api/ocm/plugin/descriptor" +) + +type Plugin = *pluginImpl + +// //nolint: errname // is no error. +type pluginImpl struct { + name string + info *PluginInstallationInfo + descriptor *descriptor.Descriptor + path string + error string + uploaders *ConstraintRegistry[descriptor.UploaderDescriptor, descriptor.UploaderKey] + downloaders *ConstraintRegistry[descriptor.DownloaderDescriptor, descriptor.DownloaderKey] +} + +func NewPlugin(name string, path string, desc *descriptor.Descriptor, errmsg string) Plugin { + p := &pluginImpl{ + name: name, + path: path, + descriptor: desc, + error: errmsg, + } + if desc != nil { + p.uploaders = NewConstraintRegistry[descriptor.UploaderDescriptor, descriptor.UploaderKey](desc.Uploaders) + p.downloaders = NewConstraintRegistry[descriptor.DownloaderDescriptor, descriptor.DownloaderKey](desc.Downloaders) + } else { + p.uploaders = NewConstraintRegistry[descriptor.UploaderDescriptor, descriptor.UploaderKey](nil) + p.downloaders = NewConstraintRegistry[descriptor.DownloaderDescriptor, descriptor.DownloaderKey](nil) + } + return p +} + +func (p *pluginImpl) GetSourceInfo() *PluginSourceInfo { + if p.info == nil { + return nil + } + if p.info.HasSourceInfo() { + return &p.info.PluginSourceInfo + } + return nil +} + +func (p *pluginImpl) GetDescriptor() *descriptor.Descriptor { + return p.descriptor +} + +func (p *pluginImpl) Name() string { + return p.name +} + +func (p *pluginImpl) Path() string { + return p.path +} + +func (p *pluginImpl) Version() string { + if !p.IsValid() { + return "-" + } + return p.descriptor.PluginVersion +} + +func (p *pluginImpl) IsValid() bool { + return p.descriptor != nil +} + +func (p *pluginImpl) Error() string { + return p.error +} + +func (p *pluginImpl) GetActionDescriptor(name string) *descriptor.ActionDescriptor { + if !p.IsValid() { + return nil + } + + for _, a := range p.descriptor.Actions { + if a.Name == name { + return &a + } + } + return nil +} + +func (p *pluginImpl) GetValueMergeHandlerDescriptor(name string) *descriptor.ValueMergeHandlerDescriptor { + if !p.IsValid() { + return nil + } + + for _, a := range p.descriptor.ValueMergeHandlers { + if a.Name == name { + return &a + } + } + return nil +} + +func (p *pluginImpl) GetValueMappingDescriptor(name string) *descriptor.ValueMergeHandlerDescriptor { + if !p.IsValid() { + return nil + } + + for _, a := range p.descriptor.ValueMergeHandlers { + if a.Name == name { + return &a + } + } + return nil +} + +func (p *pluginImpl) GetLabelMergeSpecification(name, version string) *descriptor.LabelMergeSpecification { + if !p.IsValid() { + return nil + } + + var fallback *descriptor.LabelMergeSpecification + for i, s := range p.descriptor.LabelMergeSpecifications { + if s.Name == name { + if s.Version == version { + return &s + } + if s.Version == "" { + fallback = &p.descriptor.LabelMergeSpecifications[i] + } + } + } + return fallback +} + +func (p *pluginImpl) GetAccessMethodDescriptor(name, version string) *descriptor.AccessMethodDescriptor { + if !p.IsValid() { + return nil + } + + var fallback descriptor.AccessMethodDescriptor + fallbackFound := false + for _, m := range p.descriptor.AccessMethods { + if m.Name == name { + if m.Version == version { + return &m + } + if m.Version == "" || m.Version == "v1" { + fallback = m + fallbackFound = true + } + } + } + if fallbackFound && (version == "" || version == "v1") { + return &fallback + } + return nil +} + +func (p *pluginImpl) GetValueSetDescriptor(purpose, name, version string) *descriptor.ValueSetDescriptor { + if !p.IsValid() { + return nil + } + + var fallback descriptor.ValueSetDescriptor + fallbackFound := false + for _, s := range p.descriptor.ValueSets { + if !slices.Contains(s.Purposes, purpose) { + continue + } + if s.Name == name { + if s.Version == version { + return &s + } + if s.Version == "" || s.Version == "v1" { + fallback = s + fallbackFound = true + } + } + } + if fallbackFound && (version == "" || version == "v1") { + return &fallback + } + return nil +} + +func (p *pluginImpl) LookupDownloader(name string, artType, mediaType string) descriptor.List[*descriptor.DownloaderDescriptor] { + if !p.IsValid() { + return nil + } + + return p.downloaders.LookupFor(name, descriptor.NewDownloaderKey(artType, mediaType)) +} + +func (p *pluginImpl) GetDownloaderDescriptor(name string) *descriptor.DownloaderDescriptor { + if !p.IsValid() { + return nil + } + return p.descriptor.Downloaders.Get(name) +} + +func (p *pluginImpl) LookupUploader(name string, artType, mediaType string) descriptor.List[*descriptor.UploaderDescriptor] { + if !p.IsValid() { + return nil + } + + return p.uploaders.LookupFor(name, descriptor.UploaderKey{}.SetArtifact(artType, mediaType)) +} + +func (p *pluginImpl) LookupUploadersForKeys(name string, keys descriptor.UploaderKeySet) descriptor.List[*descriptor.UploaderDescriptor] { + if !p.IsValid() { + return nil + } + + var r descriptor.List[*descriptor.UploaderDescriptor] + for k := range keys { + r = r.MergeWith(p.uploaders.LookupFor(name, k)) + } + return r +} + +func (p *pluginImpl) LookupUploaderKeys(name string, artType, mediaType string) descriptor.UploaderKeySet { + if !p.IsValid() { + return nil + } + + return p.uploaders.LookupKeysFor(name, descriptor.UploaderKey{}.SetArtifact(artType, mediaType)) +} + +func (p *pluginImpl) GetUploaderDescriptor(name string) *descriptor.UploaderDescriptor { + if !p.IsValid() { + return nil + } + return p.descriptor.Uploaders.Get(name) +} + +func (p *pluginImpl) Message() string { + if p.IsValid() { + return p.descriptor.Short + } + if p.error != "" { + return "Error: " + p.error + } + return "unknown state" +} diff --git a/pkg/contexts/ocm/plugin/cache/plugindir.go b/api/ocm/plugin/cache/plugindir.go similarity index 94% rename from pkg/contexts/ocm/plugin/cache/plugindir.go rename to api/ocm/plugin/cache/plugindir.go index 2c8548ee3..a09d1b0bd 100644 --- a/pkg/contexts/ocm/plugin/cache/plugindir.go +++ b/api/ocm/plugin/cache/plugindir.go @@ -12,9 +12,9 @@ import ( "github.com/mandelsoft/vfs/pkg/osfs" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/info" - "github.com/open-component-model/ocm/pkg/filelock" + "ocm.software/ocm/api/ocm/plugin/descriptor" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/info" + "ocm.software/ocm/api/utils/filelock" ) type PluginDir = *pluginDirImpl diff --git a/api/ocm/plugin/cache/registry.go b/api/ocm/plugin/cache/registry.go new file mode 100644 index 000000000..4201f01a5 --- /dev/null +++ b/api/ocm/plugin/cache/registry.go @@ -0,0 +1,66 @@ +package cache + +import ( + "github.com/mandelsoft/goutils/set" + + "ocm.software/ocm/api/ocm/ocmutils/registry" + "ocm.software/ocm/api/ocm/plugin/descriptor" +) + +type ConstraintRegistry[T any, K registry.Key[K]] struct { + mapping *registry.Registry[*T, K] + elems map[string]*registry.Registry[*T, K] +} + +func (r *ConstraintRegistry[T, K]) Lookup(key K) []*T { + return r.mapping.LookupHandler(key) +} + +func (r *ConstraintRegistry[T, K]) LookupKeys(key K) set.Set[K] { + return r.mapping.LookupKeys(key) +} + +func (r *ConstraintRegistry[T, K]) LookupFor(name string, key K) []*T { + if name == "" { + return r.Lookup(key) + } + m := r.elems[name] + if m == nil { + return nil + } + return m.LookupHandler(key) +} + +func (r *ConstraintRegistry[T, K]) LookupKeysFor(name string, key K) set.Set[K] { + if name == "" { + return r.LookupKeys(key) + } + m := r.elems[name] + if m == nil { + return nil + } + return m.LookupKeys(key) +} + +func NewConstraintRegistry[T descriptor.Element[K], K registry.Key[K]](list []T) *ConstraintRegistry[T, K] { + reg := registry.NewRegistry[*T, K]() + m := map[string]*registry.Registry[*T, K]{} + + for i := range list { + d := list[i] + nested := registry.NewRegistry[*T, K]() + if len(d.GetConstraints()) == 0 { + var zero K + nested.Register(zero, &d) + } else { + for _, c := range d.GetConstraints() { + if c.IsValid() { + reg.Register(c, &d) + nested.Register(c, &d) + } + } + } + m[d.GetName()] = nested + } + return &ConstraintRegistry[T, K]{reg, m} +} diff --git a/pkg/contexts/ocm/plugin/cache/updater.go b/api/ocm/plugin/cache/updater.go similarity index 94% rename from pkg/contexts/ocm/plugin/cache/updater.go rename to api/ocm/plugin/cache/updater.go index d3b6ed32a..eaab6600f 100644 --- a/pkg/contexts/ocm/plugin/cache/updater.go +++ b/api/ocm/plugin/cache/updater.go @@ -16,16 +16,16 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" "sigs.k8s.io/yaml" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugindirattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "github.com/open-component-model/ocm/pkg/contexts/ocm/extraid" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" - "github.com/open-component-model/ocm/pkg/filelock" - "github.com/open-component-model/ocm/pkg/semverutils" - utils2 "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/attrs/plugindirattr" + "ocm.software/ocm/api/ocm/extensions/download" + "ocm.software/ocm/api/ocm/extraid" + "ocm.software/ocm/api/ocm/plugin/descriptor" + utils2 "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/filelock" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/semverutils" ) type PluginInfo struct { diff --git a/pkg/contexts/ocm/plugin/common/common.go b/api/ocm/plugin/common/common.go similarity index 100% rename from pkg/contexts/ocm/plugin/common/common.go rename to api/ocm/plugin/common/common.go diff --git a/pkg/contexts/ocm/plugin/common/describe.go b/api/ocm/plugin/common/describe.go similarity index 97% rename from pkg/contexts/ocm/plugin/common/describe.go rename to api/ocm/plugin/common/describe.go index c39b21745..f8e416318 100644 --- a/pkg/contexts/ocm/plugin/common/describe.go +++ b/api/ocm/plugin/common/describe.go @@ -8,12 +8,12 @@ import ( "github.com/mandelsoft/goutils/set" "golang.org/x/exp/slices" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/action/api" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/options" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" - "github.com/open-component-model/ocm/pkg/semverutils" - utils2 "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/datacontext/action/api" + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + "ocm.software/ocm/api/ocm/plugin/descriptor" + utils2 "ocm.software/ocm/api/utils" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/semverutils" ) func DescribePluginDescriptor(reg api.ActionTypeRegistry, d *descriptor.Descriptor, out common.Printer) { diff --git a/api/ocm/plugin/config/type.go b/api/ocm/plugin/config/type.go new file mode 100644 index 000000000..f4d130ff8 --- /dev/null +++ b/api/ocm/plugin/config/type.go @@ -0,0 +1,67 @@ +package config + +import ( + "encoding/json" + + "ocm.software/ocm/api/config" + cfgcpi "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ConfigType = "plugin" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX + ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" +) + +func init() { + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1, usage)) +} + +// Config describes a memory based config interface for plugins. +type Config struct { + runtime.ObjectVersionedType `json:",inline"` + Plugin string `json:"plugin"` + Config json.RawMessage `json:"config,omitempty"` + DisableAutoRegistration bool `json:"disableAutoRegistration,omitempty"` +} + +// New creates a new memory ConfigSpec. +func New(name string, data []byte) *Config { + return &Config{ + ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), + Plugin: name, + Config: data, + } +} + +func (a *Config) GetType() string { + return ConfigType +} + +func (a *Config) ApplyTo(ctx config.Context, target interface{}) error { + t, ok := target.(Target) + if !ok { + return config.ErrNoContext(ConfigType) + } + t.ConfigurePlugin(a.Plugin, a.Config) + t.DisableAutoConfiguration(a.Plugin, a.DisableAutoRegistration) + return nil +} + +type Target interface { + ConfigurePlugin(name string, config json.RawMessage) + DisableAutoConfiguration(name string, flag bool) +} + +const usage = ` +The config type ` + ConfigType + ` can be used to configure a +plugin. + +
+    type: ` + ConfigType + `
+    plugin: <plugin name>
+    config: <arbitrary configuration structure>
+    disableAutoRegistration: <boolean flag to disable auto registration for up- and download handlers>
+
+` diff --git a/api/ocm/plugin/descriptor/const.go b/api/ocm/plugin/descriptor/const.go new file mode 100644 index 000000000..d2becd18a --- /dev/null +++ b/api/ocm/plugin/descriptor/const.go @@ -0,0 +1,19 @@ +package descriptor + +import ( + "ocm.software/ocm/api/datacontext/action" + "ocm.software/ocm/api/utils/errkind" + ocmlog "ocm.software/ocm/api/utils/logging" +) + +const ( + KIND_PLUGIN = "plugin" + KIND_DOWNLOADER = "downloader" + KIND_UPLOADER = "uploader" + KIND_ACCESSMETHOD = errkind.KIND_ACCESSMETHOD + KIND_ACTION = action.KIND_ACTION + KIND_VALUESET = "value set" + KIND_PURPOSE = "purposet" +) + +var REALM = ocmlog.DefineSubRealm("OCM plugin handling", "plugins") diff --git a/pkg/contexts/ocm/plugin/descriptor/descriptor.go b/api/ocm/plugin/descriptor/descriptor.go similarity index 98% rename from pkg/contexts/ocm/plugin/descriptor/descriptor.go rename to api/ocm/plugin/descriptor/descriptor.go index ec339a48b..3a371040c 100644 --- a/pkg/contexts/ocm/plugin/descriptor/descriptor.go +++ b/api/ocm/plugin/descriptor/descriptor.go @@ -3,7 +3,7 @@ package descriptor import ( "encoding/json" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" ) const VERSION = "v1" diff --git a/pkg/contexts/ocm/plugin/descriptor/doc.go b/api/ocm/plugin/descriptor/doc.go similarity index 100% rename from pkg/contexts/ocm/plugin/descriptor/doc.go rename to api/ocm/plugin/descriptor/doc.go diff --git a/pkg/contexts/ocm/plugin/descriptor/keys.go b/api/ocm/plugin/descriptor/keys.go similarity index 100% rename from pkg/contexts/ocm/plugin/descriptor/keys.go rename to api/ocm/plugin/descriptor/keys.go diff --git a/pkg/contexts/ocm/plugin/descriptor/suite_test.go b/api/ocm/plugin/descriptor/suite_test.go similarity index 100% rename from pkg/contexts/ocm/plugin/descriptor/suite_test.go rename to api/ocm/plugin/descriptor/suite_test.go diff --git a/api/ocm/plugin/descriptor/utils.go b/api/ocm/plugin/descriptor/utils.go new file mode 100644 index 000000000..e51e9dc06 --- /dev/null +++ b/api/ocm/plugin/descriptor/utils.go @@ -0,0 +1,58 @@ +package descriptor + +import ( + "sort" + + "github.com/mandelsoft/goutils/sliceutils" + + "ocm.software/ocm/api/ocm/ocmutils/registry" +) + +type Named interface { + GetName() string +} + +type StringName string + +func (e StringName) GetName() string { + return string(e) +} + +type Element[K registry.Key[K]] interface { + Named + GetConstraints() []K +} + +type List[T Named] []T + +func (l List[T]) Get(name string) *T { + for _, m := range l { + if m.GetName() == name { + return &m + } + } + return nil +} + +func (l List[T]) GetNames() []string { + var n []string + for _, e := range l { + n = append(n, e.GetName()) + } + sort.Strings(n) + return n +} + +func (l List[T]) MergeWith(o List[T]) List[T] { + var list []T +next: + for _, e := range o { + for _, f := range l { + if e.GetName() == f.GetName() { + continue next + } + } + list = append(list, e) + } + return sliceutils.CopyAppend(l, list...) +} diff --git a/pkg/contexts/ocm/plugin/descriptor/utils_test.go b/api/ocm/plugin/descriptor/utils_test.go similarity index 100% rename from pkg/contexts/ocm/plugin/descriptor/utils_test.go rename to api/ocm/plugin/descriptor/utils_test.go diff --git a/pkg/contexts/ocm/plugin/doc.go b/api/ocm/plugin/doc.go similarity index 100% rename from pkg/contexts/ocm/plugin/doc.go rename to api/ocm/plugin/doc.go diff --git a/api/ocm/plugin/interface.go b/api/ocm/plugin/interface.go new file mode 100644 index 000000000..4d4091179 --- /dev/null +++ b/api/ocm/plugin/interface.go @@ -0,0 +1,33 @@ +package plugin + +import ( + "ocm.software/ocm/api/ocm/plugin/descriptor" + "ocm.software/ocm/api/ocm/plugin/internal" +) + +const ( + KIND_PLUGIN = descriptor.KIND_PLUGIN + KIND_UPLOADER = descriptor.KIND_UPLOADER + KIND_ACCESSMETHOD = descriptor.KIND_ACCESSMETHOD + KIND_ACTION = descriptor.KIND_ACTION +) + +var TAG = descriptor.REALM + +type ( + Descriptor = descriptor.Descriptor + ActionDescriptor = descriptor.ActionDescriptor + ValueMergeHandlerDescriptor = descriptor.ValueMergeHandlerDescriptor + AccessMethodDescriptor = descriptor.AccessMethodDescriptor + DownloaderDescriptor = descriptor.DownloaderDescriptor + DownloaderKey = descriptor.DownloaderKey + UploaderDescriptor = descriptor.UploaderDescriptor + UploaderKey = descriptor.UploaderKey + UploaderKeySet = descriptor.UploaderKeySet + ValueSetDefinition = descriptor.ValueSetDefinition + ValueSetDescriptor = descriptor.ValueSetDescriptor + CommandDescriptor = descriptor.CommandDescriptor + + AccessSpecInfo = internal.AccessSpecInfo + UploadTargetSpecInfo = internal.UploadTargetSpecInfo +) diff --git a/api/ocm/plugin/internal/access.go b/api/ocm/plugin/internal/access.go new file mode 100644 index 000000000..a4238a653 --- /dev/null +++ b/api/ocm/plugin/internal/access.go @@ -0,0 +1,12 @@ +package internal + +import ( + "ocm.software/ocm/api/credentials" +) + +type AccessSpecInfo struct { + Short string `json:"short"` + MediaType string `json:"mediaType"` + Hint string `json:"hint"` + ConsumerId credentials.ConsumerIdentity `json:"consumerId"` +} diff --git a/pkg/contexts/ocm/plugin/internal/action.go b/api/ocm/plugin/internal/action.go similarity index 100% rename from pkg/contexts/ocm/plugin/internal/action.go rename to api/ocm/plugin/internal/action.go diff --git a/api/ocm/plugin/internal/upload.go b/api/ocm/plugin/internal/upload.go new file mode 100644 index 000000000..495cc1aee --- /dev/null +++ b/api/ocm/plugin/internal/upload.go @@ -0,0 +1,9 @@ +package internal + +import ( + "ocm.software/ocm/api/credentials" +) + +type UploadTargetSpecInfo struct { + ConsumerId credentials.ConsumerIdentity `json:"consumerId"` +} diff --git a/pkg/contexts/ocm/plugin/internal/valueset.go b/api/ocm/plugin/internal/valueset.go similarity index 100% rename from pkg/contexts/ocm/plugin/internal/valueset.go rename to api/ocm/plugin/internal/valueset.go diff --git a/api/ocm/plugin/plugin.go b/api/ocm/plugin/plugin.go new file mode 100644 index 000000000..f546a0781 --- /dev/null +++ b/api/ocm/plugin/plugin.go @@ -0,0 +1,445 @@ +package plugin + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "os" + "sync" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/finalizer" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/credentials/identity/hostpath" + "ocm.software/ocm/api/datacontext/attrs/clicfgattr" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/plugin/cache" + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/accessmethod" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/accessmethod/compose" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/accessmethod/get" + accval "ocm.software/ocm/api/ocm/plugin/ppi/cmds/accessmethod/validate" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/action" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/action/execute" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/command" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/download" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/mergehandler" + merge "ocm.software/ocm/api/ocm/plugin/ppi/cmds/mergehandler/execute" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/upload" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/upload/put" + uplval "ocm.software/ocm/api/ocm/plugin/ppi/cmds/upload/validate" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/valueset" + vscompose "ocm.software/ocm/api/ocm/plugin/ppi/cmds/valueset/compose" + vsval "ocm.software/ocm/api/ocm/plugin/ppi/cmds/valueset/validate" + "ocm.software/ocm/api/ocm/valuemergehandler" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/api/utils/cobrautils/logopts/logging" + "ocm.software/ocm/api/utils/runtime" +) + +type Plugin = *pluginImpl + +type impl = cache.Plugin + +// //nolint: errname // is no error. +type pluginImpl struct { + lock sync.RWMutex + ctx ocm.Context + impl + config json.RawMessage + disableAutoConfiguration bool +} + +func NewPlugin(ctx ocm.Context, impl cache.Plugin, config json.RawMessage) Plugin { + return &pluginImpl{ + ctx: ctx, + impl: impl, + config: config, + } +} + +func (p *pluginImpl) Context() ocm.Context { + return p.ctx +} + +func (p *pluginImpl) DisableAutoConfiguration(flag bool) { + p.disableAutoConfiguration = flag +} + +func (p *pluginImpl) IsAutoConfigurationEnabled() bool { + return !p.disableAutoConfiguration +} + +func (p *pluginImpl) SetConfig(config json.RawMessage) { + p.lock.Lock() + defer p.lock.Unlock() + p.config = config +} + +func (p *pluginImpl) Exec(r io.Reader, w io.Writer, args ...string) (result []byte, rerr error) { + var ( + finalize finalizer.Finalizer + err error + logfile *os.File + ) + + defer finalize.FinalizeWithErrorPropagationf(&rerr, "error processing plugin command %s", args[0]) + + if p.GetDescriptor().ForwardLogging { + logfile, err = os.CreateTemp("", "ocm-plugin-log-*") + if rerr != nil { + return nil, err + } + logfile.Close() + finalize.With(func() error { + return os.Remove(logfile.Name()) + }, "failed to remove temporary log file %s", logfile.Name()) + + lcfg := &logging.LoggingConfiguration{} + _, err = p.Context().ConfigContext().ApplyTo(0, lcfg) + if err != nil { + return nil, errors.Wrapf(err, "cannot extract plugin logging configration") + } + lcfg.LogFileName = logfile.Name() + data, err := json.Marshal(lcfg) + if err != nil { + return nil, errors.Wrapf(err, "cannot marshal plugin logging configration") + } + args = append([]string{"--" + ppi.OptPlugingLogConfig, string(data)}, args...) + } + + if len(p.config) == 0 { + p.ctx.Logger(TAG).Debug("execute plugin action", "path", p.Path(), "args", args) + } else { + p.ctx.Logger(TAG).Debug("execute plugin action", "path", p.Path(), "args", args, "config", p.config) + } + data, err := cache.Exec(p.Path(), p.config, r, w, args...) + + if logfile != nil { + r, oerr := os.OpenFile(logfile.Name(), vfs.O_RDONLY, 0o600) + if oerr == nil { + finalize.Close(r, "plugin logfile", logfile.Name()) + w := p.ctx.LoggingContext().Tree().LogWriter() + if w == nil { + if logging.GlobalLogFile != nil { + w = logging.GlobalLogFile.File() + } + if w == nil { + w = os.Stderr + } + } + + // weaken the sync problem when merging log files. + // If a SyncWriter is used, the copy is done under a write lock. + // This is only a solution, if the log records are written + // by single write calls. + // The underlying logging apis do not expose their + // sync mechanism for writing log records. + if writer, ok := w.(io.ReaderFrom); ok { + writer.ReadFrom(r) + } else { + io.Copy(w, r) + } + } + } + return data, err +} + +func (p *pluginImpl) MergeValue(specification *valuemergehandler.Specification, local, inbound valuemergehandler.Value) (bool, *valuemergehandler.Value, error) { + desc := p.GetValueMappingDescriptor(specification.Algorithm) + if desc == nil { + return false, nil, errors.ErrNotSupported(valuemergehandler.KIND_VALUE_MERGE_ALGORITHM, specification.Algorithm, KIND_PLUGIN, p.Name()) + } + input, err := json.Marshal(ppi.ValueMergeData{ + Local: local, + Inbound: inbound, + }) + if err != nil { + return false, nil, err + } + + args := []string{mergehandler.Name, merge.Name, specification.Algorithm} + if len(specification.Config) > 0 { + args = append(args, string(specification.Config)) + } + + var buf bytes.Buffer + _, err = p.Exec(bytes.NewReader(input), &buf, args...) + if err != nil { + return false, nil, errors.Wrapf(err, "plugin %s", p.Name()) + } + var r ppi.ValueMergeResult + + err = json.Unmarshal(buf.Bytes(), &r) + if err != nil { + if r.Message != "" { + return false, nil, fmt.Errorf("%w: %s", err, r.Message) + } + return false, nil, err + } + return r.Modified, &r.Value, nil +} + +func (p *pluginImpl) Action(spec ppi.ActionSpec, creds json.RawMessage) (ppi.ActionResult, error) { + desc := p.GetActionDescriptor(spec.GetKind()) + if desc == nil { + return nil, errors.ErrNotSupported(KIND_ACTION, spec.GetKind(), KIND_PLUGIN, p.Name()) + } + if desc.ConsumerType != "" { + cid := spec.GetConsumerAttributes() + cid[cpi.ID_TYPE] = desc.ConsumerType + c, err := credentials.CredentialsForConsumer(p.Context(), credentials.ConsumerIdentity(cid), hostpath.Matcher) + if err != nil || c == nil { + return nil, errors.ErrNotFound(credentials.KIND_CREDENTIALS, cid.String()) + } + creds, err = json.Marshal(c.Properties()) + if err != nil { + return nil, errors.Wrapf(err, "cannot marshal credentials") + } + } + + data, err := p.ctx.GetActions().GetActionTypes().EncodeActionSpec(spec, runtime.DefaultJSONEncoding) + if err != nil { + return nil, err + } + + args := []string{action.Name, execute.Name, string(data)} + if creds != nil { + args = append(args, "--"+get.OptCreds, string(creds)) + } + + result, err := p.Exec(nil, nil, args...) + if err != nil { + return nil, errors.Wrapf(err, "plugin %s", p.Name()) + } + + info, err := p.ctx.GetActions().GetActionTypes().DecodeActionResult(result, runtime.DefaultJSONEncoding) + if err != nil { + return nil, errors.Wrapf(err, "plugin %s: cannot unmarshal action result", p.Name()) + } + return info, nil +} + +func (p *pluginImpl) ValidateAccessMethod(spec []byte) (*ppi.AccessSpecInfo, error) { + result, err := p.Exec(nil, nil, accessmethod.Name, accval.Name, string(spec)) + if err != nil { + return nil, errors.Wrapf(err, "plugin %s", p.Name()) + } + + var info ppi.AccessSpecInfo + err = json.Unmarshal(result, &info) + if err != nil { + return nil, errors.Wrapf(err, "plugin %s: cannot unmarshal access spec info", p.Name()) + } + return &info, nil +} + +func (p *pluginImpl) ComposeAccessMethod(name string, opts flagsets.ConfigOptions, base flagsets.Config) error { + cfg := flagsets.Config{} + for _, o := range opts.Options() { + cfg[o.GetName()] = o.Value() + } + optsdata, err := json.Marshal(cfg) + if err != nil { + return errors.Wrapf(err, "cannot marshal option values") + } + basedata, err := json.Marshal(base) + if err != nil { + return errors.Wrapf(err, "cannot marshal access specification base value") + } + result, err := p.Exec(nil, nil, accessmethod.Name, compose.Name, name, string(optsdata), string(basedata)) + if err != nil { + return err + } + var r flagsets.Config + err = json.Unmarshal(result, &r) + if err != nil { + return errors.Wrapf(err, "cannot unmarshal composition result") + } + + for k := range base { + delete(base, k) + } + for k, v := range r { + base[k] = v + } + return nil +} + +func (p *pluginImpl) ValidateUploadTarget(name string, spec []byte) (*ppi.UploadTargetSpecInfo, error) { + result, err := p.Exec(nil, nil, upload.Name, uplval.Name, name, string(spec)) + if err != nil { + return nil, errors.Wrapf(err, "plugin uploader %s/%s", p.Name(), name) + } + + var info ppi.UploadTargetSpecInfo + err = json.Unmarshal(result, &info) + if err != nil { + return nil, errors.Wrapf(err, "plugin uploader %s/%s: cannot unmarshal upload target info", p.Name(), name) + } + return &info, nil +} + +func (p *pluginImpl) Get(w io.Writer, creds, spec json.RawMessage) error { + args := []string{accessmethod.Name, get.Name, string(spec)} + if creds != nil { + args = append(args, "--"+get.OptCreds, string(creds)) + } + _, err := p.Exec(nil, w, args...) + return err +} + +func (p *pluginImpl) Put(name string, r io.Reader, artType, mimeType, hint string, creds, target json.RawMessage) (ocm.AccessSpec, error) { + args := []string{upload.Name, put.Name, name, string(target)} + + if creds != nil { + args = append(args, "--"+put.OptCreds, string(creds)) + } + if hint != "" { + args = append(args, "--"+put.OptHint, hint) + } + if mimeType != "" { + args = append(args, "--"+put.OptMedia, mimeType) + } + if artType != "" { + args = append(args, "--"+put.OptArt, artType) + } + result, err := p.Exec(r, nil, args...) + if err != nil { + return nil, err + } + var m map[string]interface{} + err = json.Unmarshal(result, &m) + if err != nil { + return nil, errors.Wrapf(err, "cannot unmarshal put result") + } + if len(m) == 0 { + return nil, nil // not used + } + return p.ctx.AccessSpecForConfig(result, runtime.DefaultJSONEncoding) +} + +func (p *pluginImpl) Download(name string, r io.Reader, artType, mimeType, target string, config json.RawMessage) (bool, string, error) { + args := []string{download.Name, name, target} + + if mimeType != "" { + args = append(args, "--"+download.OptMedia, mimeType) + } + if artType != "" { + args = append(args, "--"+download.OptArt, artType) + } + + // new attribute can only be set for extended plugin format version + // so, omitting config if not set is compatible with former CLI. + if d := p.GetDescriptor().Downloaders.Get(name); len(config) > 0 && d != nil && d.ConfigScheme != "" { + args = append(args, "--"+download.OptConfig, string(config)) + } + result, err := p.Exec(r, nil, args...) + if err != nil { + return true, "", err + } + var m download.Result + err = json.Unmarshal(result, &m) + if err != nil { + return true, "", errors.Wrapf(err, "cannot unmarshal put result") + } + if m.Error != "" { + return true, "", fmt.Errorf("%s", m.Error) + } + return m.Path != "", m.Path, nil +} + +func (p *pluginImpl) ValidateValueSet(purpose string, spec []byte) (*ppi.ValueSetInfo, error) { + result, err := p.Exec(nil, nil, valueset.Name, vsval.Name, purpose, string(spec)) + if err != nil { + return nil, errors.Wrapf(err, "plugin %s", p.Name()) + } + + var info ppi.ValueSetInfo + err = json.Unmarshal(result, &info) + if err != nil { + return nil, errors.Wrapf(err, "plugin %s: cannot unmarshal value set info", p.Name()) + } + return &info, nil +} + +func (p *pluginImpl) ComposeValueSet(purpose, name string, opts flagsets.ConfigOptions, base flagsets.Config) error { + cfg := flagsets.Config{} + for _, o := range opts.Options() { + cfg[o.GetName()] = o.Value() + } + optsdata, err := json.Marshal(cfg) + if err != nil { + return errors.Wrapf(err, "cannot marshal option values") + } + basedata, err := json.Marshal(base) + if err != nil { + return errors.Wrapf(err, "cannot marshal access specification base value") + } + result, err := p.Exec(nil, nil, valueset.Name, vscompose.Name, purpose, name, string(optsdata), string(basedata)) + if err != nil { + return err + } + var r flagsets.Config + err = json.Unmarshal(result, &r) + if err != nil { + return errors.Wrapf(err, "cannot unmarshal composition result") + } + + for k := range base { + delete(base, k) + } + for k, v := range r { + base[k] = v + } + return nil +} + +func (p *pluginImpl) Command(name string, reader io.Reader, writer io.Writer, cmdargs []string) (rerr error) { + var finalize finalizer.Finalizer + cmd := p.GetDescriptor().Commands.Get(name) + if cmd == nil { + return errors.ErrNotFound("command", name) + } + + defer finalize.FinalizeWithErrorPropagation(&rerr) + + var f vfs.File + + args := []string{command.Name} + + a := clicfgattr.Get(p.Context()) + if a != nil && cmd.CLIConfigRequired { + cfgdata, err := json.Marshal(a) + if err != nil { + return errors.Wrapf(err, "cannot marshal CLI config") + } + // cannot use a vfs here, since it's not possible to pass it to the plugin + f, err = os.CreateTemp("", "cli-om-config-*") + if err != nil { + return err + } + finalize.With(func() error { + return os.Remove(f.Name()) + }, "failed to remove temporary config file %s", f.Name()) + + _, err = f.Write(cfgdata) + if err != nil { + f.Close() + return err + } + err = f.Close() + if err != nil { + return err + } + args = append(args, "--"+command.OptCliConfig, f.Name()) + } + args = append(append(args, name), cmdargs...) + + _, err := p.Exec(reader, writer, args...) + return err +} diff --git a/pkg/contexts/ocm/plugin/plugin_test.go b/api/ocm/plugin/plugin_test.go similarity index 79% rename from pkg/contexts/ocm/plugin/plugin_test.go rename to api/ocm/plugin/plugin_test.go index 5851d5e1f..89e76b66e 100644 --- a/pkg/contexts/ocm/plugin/plugin_test.go +++ b/api/ocm/plugin/plugin_test.go @@ -6,22 +6,22 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/testutils" - - common2 "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/oci/actions/oci-repository-prepare" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - access "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/plugin" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugincacheattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugindirattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/cache" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/plugins" - "github.com/open-component-model/ocm/pkg/contexts/ocm/registration" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/handlers/defaultmerge" + . "ocm.software/ocm/api/ocm/plugin/testutils" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/oci/extensions/actions/oci-repository-prepare" + "ocm.software/ocm/api/ocm" + access "ocm.software/ocm/api/ocm/extensions/accessmethods/plugin" + "ocm.software/ocm/api/ocm/extensions/attrs/plugincacheattr" + "ocm.software/ocm/api/ocm/extensions/attrs/plugindirattr" + "ocm.software/ocm/api/ocm/plugin" + "ocm.software/ocm/api/ocm/plugin/cache" + "ocm.software/ocm/api/ocm/plugin/common" + "ocm.software/ocm/api/ocm/plugin/plugins" + "ocm.software/ocm/api/ocm/plugin/registration" + "ocm.software/ocm/api/ocm/valuemergehandler" + "ocm.software/ocm/api/ocm/valuemergehandler/handlers/defaultmerge" + common2 "ocm.software/ocm/api/utils/misc" ) var _ = Describe("setup plugin cache", func() { diff --git a/pkg/contexts/ocm/plugin/plugins/plugins.go b/api/ocm/plugin/plugins/plugins.go similarity index 82% rename from pkg/contexts/ocm/plugin/plugins/plugins.go rename to api/ocm/plugin/plugins/plugins.go index 7772c610d..53e20d2f0 100644 --- a/pkg/contexts/ocm/plugin/plugins/plugins.go +++ b/api/ocm/plugin/plugins/plugins.go @@ -4,13 +4,13 @@ import ( "encoding/json" "sync" - cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/cache" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/config" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" - "github.com/open-component-model/ocm/pkg/utils" + cfgcpi "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/plugin" + "ocm.software/ocm/api/ocm/plugin/cache" + "ocm.software/ocm/api/ocm/plugin/config" + "ocm.software/ocm/api/ocm/plugin/descriptor" + "ocm.software/ocm/api/utils" ) type Set = *pluginsImpl diff --git a/pkg/contexts/ocm/plugin/ppi/clicmd/options.go b/api/ocm/plugin/ppi/clicmd/options.go similarity index 100% rename from pkg/contexts/ocm/plugin/ppi/clicmd/options.go rename to api/ocm/plugin/ppi/clicmd/options.go diff --git a/api/ocm/plugin/ppi/clicmd/utils.go b/api/ocm/plugin/ppi/clicmd/utils.go new file mode 100644 index 000000000..437e0e4bc --- /dev/null +++ b/api/ocm/plugin/ppi/clicmd/utils.go @@ -0,0 +1,85 @@ +package clicmd + +import ( + _ "ocm.software/ocm/cmds/ocm/clippi/config" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/optionutils" + "github.com/spf13/cobra" + + "ocm.software/ocm/api/ocm/plugin/ppi" +) + +//////////////////////////////////////////////////////////////////////////////// + +type CobraCommand struct { + cmd *cobra.Command + verb string + realm string + objname string + cliConfigRequired bool +} + +var _ ppi.Command = (*CobraCommand)(nil) + +// NewCLICommand created a CLI command based on a preconfigured cobra.Command. +// Optionally, a verb can be specified. If given additionally a realm +// can be given. +// verb and realm are used to add the command at the appropriate places in +// the command hierarchy of the ocm CLI. +// If nothing is specified, the command will be a new top-level command. +// To access the configured ocm context use the Context attribute +// of the cobra command. The ocm context is bound to it. +// +// ocm.FromContext(cmd.Context()) +func NewCLICommand(cmd *cobra.Command, opts ...Option) (ppi.Command, error) { + eff := optionutils.EvalOptions(opts...) + if eff.Verb == "" && eff.Realm != "" { + return nil, errors.New("realm without verb not allowed") + } + cmd.DisableFlagsInUseLine = true + return &CobraCommand{cmd, eff.Verb, eff.Realm, eff.ObjectType, optionutils.AsBool(eff.RequireCLIConfig, false)}, nil +} + +func (c *CobraCommand) Name() string { + return c.cmd.Name() +} + +func (c *CobraCommand) Description() string { + return c.cmd.Long +} + +func (c *CobraCommand) Usage() string { + return c.cmd.Use +} + +func (c *CobraCommand) Short() string { + return c.cmd.Short +} + +func (c *CobraCommand) Example() string { + return c.cmd.Example +} + +func (c *CobraCommand) ObjectType() string { + if c.objname == "" { + return c.Name() + } + return c.objname +} + +func (c *CobraCommand) Verb() string { + return c.verb +} + +func (c *CobraCommand) Realm() string { + return c.realm +} + +func (c *CobraCommand) CLIConfigRequired() bool { + return c.cliConfigRequired +} + +func (c *CobraCommand) Command() *cobra.Command { + return c.cmd +} diff --git a/api/ocm/plugin/ppi/cmds/accessmethod/cmd.go b/api/ocm/plugin/ppi/cmds/accessmethod/cmd.go new file mode 100644 index 000000000..7ca09d52e --- /dev/null +++ b/api/ocm/plugin/ppi/cmds/accessmethod/cmd.go @@ -0,0 +1,26 @@ +package accessmethod + +import ( + "github.com/spf13/cobra" + + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/accessmethod/compose" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/accessmethod/get" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/accessmethod/validate" +) + +const Name = "accessmethod" + +func New(p ppi.Plugin) *cobra.Command { + cmd := &cobra.Command{ + Use: Name, + Short: "access method operations", + Long: `This command group provides all commands used to implement an access method +described by an access method descriptor (` + p.Name() + ` descriptor.`, + } + + cmd.AddCommand(validate.New(p)) + cmd.AddCommand(get.New(p)) + cmd.AddCommand(compose.New(p)) + return cmd +} diff --git a/api/ocm/plugin/ppi/cmds/accessmethod/compose/cmd.go b/api/ocm/plugin/ppi/cmds/accessmethod/compose/cmd.go new file mode 100644 index 000000000..78e6ac52f --- /dev/null +++ b/api/ocm/plugin/ppi/cmds/accessmethod/compose/cmd.go @@ -0,0 +1,94 @@ +package compose + +import ( + "encoding/json" + + "github.com/mandelsoft/goutils/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/utils/errkind" + "ocm.software/ocm/api/utils/runtime" +) + +const Name = "compose" + +func New(p ppi.Plugin) *cobra.Command { + opts := Options{} + + cmd := &cobra.Command{ + Use: Name + " ", + Short: "compose access specification from options and base specification", + Long: ` +The task of this command is to compose an access specification based on some +explicitly given input options and preconfigured specifications. + +The finally composed access specification has to be returned as JSON document +on *stdout*. + +This command is only used, if for an access method descriptor configuration +options are defined (` + p.Name() + ` descriptor). + +If possible, predefined standard options should be used. In such a case only the +name field should be defined for an option. If required, new options can be +defined by additionally specifying a type and a description. New options should +be used very carefully. The chosen names MUST not conflict with names provided +by other plugins. Therefore, it is highly recommended to use names prefixed +by the plugin name. + +` + options.DefaultRegistry.Usage(), + Args: cobra.ExactArgs(3), + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return opts.Complete(args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return Command(p, cmd, &opts) + }, + } + opts.AddFlags(cmd.Flags()) + return cmd +} + +type Options struct { + Name string + Options ppi.Config + Base ppi.Config +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { +} + +func (o *Options) Complete(args []string) error { + o.Name = args[0] + if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[1]), &o.Options); err != nil { + return errors.Wrapf(err, "invalid access specification options") + } + if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[2]), &o.Base); err != nil { + return errors.Wrapf(err, "invalid base access specification") + } + return nil +} + +func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { + k, v := runtime.KindVersion(opts.Name) + m := p.GetAccessMethod(k, v) + if m == nil { + return errors.ErrUnknown(errkind.KIND_ACCESSMETHOD, opts.Name) + } + err := opts.Options.ConvertFor(m.Options()...) + if err != nil { + return err + } + err = m.ComposeAccessSpecification(p, opts.Options, opts.Base) + if err != nil { + return err + } + data, err := json.Marshal(opts.Base) + if err != nil { + return err + } + cmd.Printf("%s\n", string(data)) + return nil +} diff --git a/api/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go b/api/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go new file mode 100644 index 000000000..3eb3870bd --- /dev/null +++ b/api/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go @@ -0,0 +1,87 @@ +package get + +import ( + "encoding/json" + "fmt" + "io" + "os" + + "github.com/mandelsoft/goutils/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/ocm/plugin/descriptor" + "ocm.software/ocm/api/ocm/plugin/ppi" + commonppi "ocm.software/ocm/api/ocm/plugin/ppi/cmds/common" + "ocm.software/ocm/api/utils/cobrautils/flag" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + Name = "get" + OptCreds = commonppi.OptCreds +) + +func New(p ppi.Plugin) *cobra.Command { + opts := Options{} + + cmd := &cobra.Command{ + Use: Name + " [] ", + Short: "get blob", + Long: ` +Evaluate the given access specification and return the described blob on +*stdout*.`, + Args: cobra.ExactArgs(1), + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return opts.Complete(args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return Command(p, cmd, &opts) + }, + } + opts.AddFlags(cmd.Flags()) + return cmd +} + +type Options struct { + Credentials credentials.DirectCredentials + Specification json.RawMessage +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { + flag.YAMLVarP(fs, &o.Credentials, OptCreds, "c", nil, "credentials") + flag.StringToStringVarPFA(fs, &o.Credentials, "credential", "C", nil, "dedicated credential value") +} + +func (o *Options) Complete(args []string) error { + if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[0]), &o.Specification); err != nil { + return errors.Wrapf(err, "invalid repository specification") + } + + fmt.Fprintf(os.Stderr, "credentials: %s\n", o.Credentials.String()) + return nil +} + +func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { + spec, err := p.DecodeAccessSpecification(opts.Specification) + if err != nil { + return errors.Wrapf(err, "access specification") + } + + m := p.GetAccessMethod(runtime.KindVersion(spec.GetType())) + if m == nil { + return errors.ErrUnknown(descriptor.KIND_ACCESSMETHOD, spec.GetType()) + } + _, err = m.ValidateSpecification(p, spec) + if err != nil { + return err + } + r, err := m.Reader(p, spec, opts.Credentials) + if err != nil { + return err + } + _, err = io.Copy(os.Stdout, r) + r.Close() + return err +} diff --git a/api/ocm/plugin/ppi/cmds/accessmethod/validate/cmd.go b/api/ocm/plugin/ppi/cmds/accessmethod/validate/cmd.go new file mode 100644 index 000000000..596bef2a1 --- /dev/null +++ b/api/ocm/plugin/ppi/cmds/accessmethod/validate/cmd.go @@ -0,0 +1,105 @@ +package validate + +import ( + "encoding/json" + + "github.com/mandelsoft/goutils/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/utils/errkind" + "ocm.software/ocm/api/utils/runtime" +) + +const Name = "validate" + +func New(p ppi.Plugin) *cobra.Command { + opts := Options{} + + cmd := &cobra.Command{ + Use: Name + " ", + Short: "validate access specification", + Long: ` +This command accepts an access specification as argument. It is used to +validate the specification and to provide some metadata for the given +specification. + +This metadata has to be provided as JSON string on *stdout* and has the +following fields: + +- **mediaType** *string* + + The media type of the artifact described by the specification. It may be part + of the specification or implicitly determined by the access method. + +- **description** *string* + + A short textual description of the described location. + +- **hint** *string* + + A name hint of the described location used to reconstruct a useful + name for local blobs uploaded to a dedicated repository technology. + +- **consumerId** *map[string]string* + + The consumer id used to determine optional credentials for the + underlying repository. If specified, at least the type field must be set. +`, + Args: cobra.ExactArgs(1), + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return opts.Complete(args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return Command(p, cmd, &opts) + }, + } + opts.AddFlags(cmd.Flags()) + return cmd +} + +type Options struct { + Specification json.RawMessage +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { +} + +func (o *Options) Complete(args []string) error { + if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[0]), &o.Specification); err != nil { + return errors.Wrapf(err, "invalid access specification") + } + return nil +} + +type Result struct { + MediaType string `json:"mediaType"` + Short string `json:"description"` + Hint string `json:"hint"` + ConsumerId credentials.ConsumerIdentity `json:"consumerId"` +} + +func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { + spec, err := p.DecodeAccessSpecification(opts.Specification) + if err != nil { + return errors.Wrapf(err, "access specification") + } + + m := p.GetAccessMethod(runtime.KindVersion(spec.GetType())) + if m == nil { + return errors.ErrUnknown(errkind.KIND_ACCESSMETHOD, spec.GetType()) + } + info, err := m.ValidateSpecification(p, spec) + if err != nil { + return err + } + result := Result{MediaType: info.MediaType, ConsumerId: info.ConsumerId, Hint: info.Hint, Short: info.Short} + data, err := json.Marshal(result) + if err != nil { + return err + } + cmd.Printf("%s\n", string(data)) + return nil +} diff --git a/api/ocm/plugin/ppi/cmds/action/cmd.go b/api/ocm/plugin/ppi/cmds/action/cmd.go new file mode 100644 index 000000000..9e1ac14b4 --- /dev/null +++ b/api/ocm/plugin/ppi/cmds/action/cmd.go @@ -0,0 +1,21 @@ +package action + +import ( + "github.com/spf13/cobra" + + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/action/execute" +) + +const Name = "action" + +func New(p ppi.Plugin) *cobra.Command { + cmd := &cobra.Command{ + Use: Name, + Short: "action operations", + Long: `This command group provides all commands used to implement an action.`, + } + + cmd.AddCommand(execute.New(p)) + return cmd +} diff --git a/api/ocm/plugin/ppi/cmds/action/execute/cmd.go b/api/ocm/plugin/ppi/cmds/action/execute/cmd.go new file mode 100644 index 000000000..50e09da69 --- /dev/null +++ b/api/ocm/plugin/ppi/cmds/action/execute/cmd.go @@ -0,0 +1,97 @@ +package execute + +import ( + "encoding/json" + + "github.com/mandelsoft/goutils/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext/action" + "ocm.software/ocm/api/datacontext/action/api" + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/common" + "ocm.software/ocm/api/utils/cobrautils/flag" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + Name = "execute" + OptCreds = common.OptCreds +) + +func New(p ppi.Plugin) *cobra.Command { + opts := Options{} + + cmd := &cobra.Command{ + Use: Name + " ", + Short: "execute an action", + Long: ` +This command executes an action. + +This action has to provide an execution result as JSON string on *stdout*. It has the +following fields: + +- **name** *string* + + The name and version of the action result. It must match the value + from the action specification. + +- **message** *string* + + An error message. + +Additional fields depend on the kind of action. +`, + Args: cobra.ExactArgs(1), + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return opts.Complete(args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return Command(p, cmd, &opts) + }, + } + opts.AddFlags(cmd.Flags()) + return cmd +} + +type Options struct { + Credentials credentials.DirectCredentials + Specification json.RawMessage +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { + flag.YAMLVarP(fs, &o.Credentials, OptCreds, "c", nil, "credentials") + flag.StringToStringVarPFA(fs, &o.Credentials, "credential", "C", nil, "dedicated credential value") +} + +func (o *Options) Complete(args []string) error { + if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[0]), &o.Specification); err != nil { + return errors.Wrapf(err, "invalid access specification") + } + return nil +} + +func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { + spec, err := action.DefaultRegistry().DecodeActionSpec(opts.Specification, runtime.DefaultJSONEncoding) + if err != nil { + return errors.Wrapf(err, "action specification") + } + + a := p.GetAction(spec.GetKind()) + if a == nil { + return errors.ErrUnknown(api.KIND_ACTION, spec.GetKind()) + } + result, err := a.Execute(p, spec, opts.Credentials) + if err != nil { + return err + } + result.SetType(spec.GetType()) + data, err := action.DefaultRegistry().EncodeActionResult(result, runtime.DefaultJSONEncoding) + if err != nil { + return err + } + cmd.Printf("%s\n", string(data)) + return nil +} diff --git a/api/ocm/plugin/ppi/cmds/app.go b/api/ocm/plugin/ppi/cmds/app.go new file mode 100644 index 000000000..b2129bfef --- /dev/null +++ b/api/ocm/plugin/ppi/cmds/app.go @@ -0,0 +1,106 @@ +//go:generate go run -mod=mod ./doc ../../../../../docs/pluginreference + +package cmds + +import ( + "encoding/json" + "os" + + "github.com/spf13/cobra" + + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/accessmethod" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/action" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/command" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/describe" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/download" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/info" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/mergehandler" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/topics/descriptor" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/upload" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/valueset" + "ocm.software/ocm/api/utils/cobrautils" +) + +type PluginCommand struct { + command *cobra.Command + plugin ppi.Plugin +} + +func (p *PluginCommand) Command() *cobra.Command { + return p.command +} + +func NewPluginCommand(p ppi.Plugin) *PluginCommand { + short := p.Descriptor().Short + if short == "" { + short = "OCM plugin " + p.Name() + } + + pcmd := &PluginCommand{ + plugin: p, + } + cmd := &cobra.Command{ + Use: p.Name() + " ", + Short: short, + Long: p.Descriptor().Long, + Version: p.Version(), + PersistentPreRunE: pcmd.PreRunE, + TraverseChildren: true, + SilenceUsage: true, + DisableFlagsInUseLine: true, + SilenceErrors: true, + } + + cmd.SetOut(os.Stdout) + cmd.SetErr(os.Stderr) + + cobrautils.TweakCommand(cmd, nil) + + cmd.AddCommand(describe.New(p)) + cmd.AddCommand(info.New(p)) + cmd.AddCommand(action.New(p)) + cmd.AddCommand(mergehandler.New(p)) + cmd.AddCommand(accessmethod.New(p)) + cmd.AddCommand(upload.New(p)) + cmd.AddCommand(download.New(p)) + cmd.AddCommand(valueset.New(p)) + cmd.AddCommand(command.New(p)) + + cmd.InitDefaultHelpCmd() + help := cobrautils.GetHelpCommand(cmd) + + // help.Use="help " + help.DisableFlagsInUseLine = true + cmd.AddCommand(descriptor.New()) + + help.AddCommand(descriptor.New()) + + p.GetOptions().AddFlags(cmd.Flags()) + pcmd.command = cmd + return pcmd +} + +type Error struct { + Error string `json:"error"` +} + +func (p *PluginCommand) PreRunE(cmd *cobra.Command, args []string) error { + if handler != nil { + return handler.HandleConfig(p.plugin.GetOptions().LogConfig) + } + return nil +} + +func (p *PluginCommand) Execute(args []string) error { + p.command.SetArgs(args) + err := p.command.Execute() + if err != nil { + result, err2 := json.Marshal(Error{err.Error()}) + if err2 != nil { + return err2 + } + p.command.PrintErrln(string(result)) + } + return err +} diff --git a/api/ocm/plugin/ppi/cmds/command/cmd.go b/api/ocm/plugin/ppi/cmds/command/cmd.go new file mode 100644 index 000000000..0de2b9de3 --- /dev/null +++ b/api/ocm/plugin/ppi/cmds/command/cmd.go @@ -0,0 +1,71 @@ +package command + +import ( + "context" + "os" + + "github.com/spf13/cobra" + + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/common" + "ocm.software/ocm/api/utils/cobrautils" +) + +const ( + Name = "command" + OptCliConfig = common.OptCliConfig +) + +func New(p ppi.Plugin) *cobra.Command { + cmd := &cobra.Command{ + Use: Name, + Short: "CLI command extensions", + Long: `This command group provides all CLI command extensions +described by an access method descriptor (` + p.Name() + ` descriptor.`, + TraverseChildren: true, + } + var cliconfig string + cmd.Flags().StringVarP(&cliconfig, OptCliConfig, "", "", "path to cli configuration file") + + found := false + for _, n := range p.Commands() { + found = true + c := n.Command() + c.TraverseChildren = true + + nested := c.PreRunE + c.PreRunE = func(cmd *cobra.Command, args []string) error { + var err error + + ctx := context.Background() + if cliconfig != "" { + ctx, err = ConfigureFromFile(ctx, cliconfig) + if err != nil { + return err + } + } + c.SetContext(ctx) + if nested != nil { + return nested(cmd, args) + } + return nil + } + cmd.AddCommand(n.Command()) + } + if found { + cobrautils.TweakHelpCommandFor(cmd) + } + return cmd +} + +func ConfigureFromFile(ctx context.Context, path string) (context.Context, error) { + data, err := os.ReadFile(path) + if err != nil { + return ctx, err + } + + if handler != nil { + return handler.HandleConfig(ctx, data) + } + return ctx, nil +} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/command/config.go b/api/ocm/plugin/ppi/cmds/command/config.go similarity index 100% rename from pkg/contexts/ocm/plugin/ppi/cmds/command/config.go rename to api/ocm/plugin/ppi/cmds/command/config.go diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/common/const.go b/api/ocm/plugin/ppi/cmds/common/const.go similarity index 100% rename from pkg/contexts/ocm/plugin/ppi/cmds/common/const.go rename to api/ocm/plugin/ppi/cmds/common/const.go diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/desc.go b/api/ocm/plugin/ppi/cmds/desc.go similarity index 100% rename from pkg/contexts/ocm/plugin/ppi/cmds/desc.go rename to api/ocm/plugin/ppi/cmds/desc.go diff --git a/api/ocm/plugin/ppi/cmds/describe/cmd.go b/api/ocm/plugin/ppi/cmds/describe/cmd.go new file mode 100644 index 000000000..decfa2e2a --- /dev/null +++ b/api/ocm/plugin/ppi/cmds/describe/cmd.go @@ -0,0 +1,28 @@ +package describe + +import ( + "os" + + "github.com/spf13/cobra" + + "ocm.software/ocm/api/datacontext/action" + "ocm.software/ocm/api/ocm/plugin/common" + "ocm.software/ocm/api/ocm/plugin/ppi" + common2 "ocm.software/ocm/api/utils/misc" +) + +const NAME = "describe" + +func New(p ppi.Plugin) *cobra.Command { + return &cobra.Command{ + Use: NAME, + Short: "describe plugin", + Long: "Display a detailed description of the capabilities of this OCM plugin.", + Args: cobra.MaximumNArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + d := p.Descriptor() + common.DescribePluginDescriptor(action.DefaultRegistry(), &d, common2.NewPrinter(os.Stdout)) + return nil + }, + } +} diff --git a/api/ocm/plugin/ppi/cmds/doc/generate.go b/api/ocm/plugin/ppi/cmds/doc/generate.go new file mode 100644 index 000000000..ca3193916 --- /dev/null +++ b/api/ocm/plugin/ppi/cmds/doc/generate.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "os" + + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds" + "ocm.software/ocm/api/version" + "ocm.software/ocm/hack/generate-docs/cobradoc" +) + +func main() { + fmt.Println("> Generate Docs for OCM Plugins") + + if len(os.Args) != 2 { // expect 2 as the first one is the program name + fmt.Fprintf(os.Stderr, "Expected exactly one argument, but got %d", len(os.Args)-1) + os.Exit(1) + } + + p := ppi.NewPlugin("plugin", version.Get().String()) + p.SetLong(cmds.Description(p.Name())) + p.SetShort("OCM Plugin") + cmd := cmds.NewPluginCommand(p).Command() + cmd.DisableAutoGenTag = true + cobradoc.Generate("OCM Plugin", cmd, os.Args[1], true) +} diff --git a/api/ocm/plugin/ppi/cmds/download/cmd.go b/api/ocm/plugin/ppi/cmds/download/cmd.go new file mode 100644 index 000000000..7b3aa7867 --- /dev/null +++ b/api/ocm/plugin/ppi/cmds/download/cmd.go @@ -0,0 +1,112 @@ +package download + +import ( + "encoding/json" + "fmt" + "io" + "os" + + "github.com/mandelsoft/goutils/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "ocm.software/ocm/api/ocm/plugin/descriptor" + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/common" +) + +const ( + Name = "download" + OptMedia = common.OptMedia + OptArt = common.OptArt + OptConfig = common.OptConfig +) + +func New(p ppi.Plugin) *cobra.Command { + opts := Options{} + + cmd := &cobra.Command{ + Use: Name + " [] ", + Short: "download blob into filesystem", + Long: ` +This command accepts a target filepath as argument. It is used as base name +to store the downloaded content. The blob content is provided on the +*stdin*. The first argument specified the downloader to use for the operation. + +The task of this command is to transform the content of the provided +blob into a filesystem structure applicable to the type specific tools working +with content of the given artifact type. +`, + Args: cobra.ExactArgs(2), + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return opts.Complete(args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return Command(p, cmd, &opts) + }, + } + opts.AddFlags(cmd.Flags()) + return cmd +} + +type Options struct { + Name string + Path string + + MediaType string + ArtifactType string + Config string +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { + fs.StringVarP(&o.MediaType, OptMedia, "m", "", "media type of input blob") + fs.StringVarP(&o.ArtifactType, OptArt, "a", "", "artifact type of input blob") + fs.StringVarP(&o.Config, OptConfig, "c", "", "registration config") +} + +func (o *Options) Complete(args []string) error { + o.Name = args[0] + o.Path = args[1] + return nil +} + +type Result struct { + Path string `json:"path"` + Error string `json:"error"` +} + +func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { + d := p.GetDownloader(opts.Name) + if d == nil { + return errors.ErrNotFound(descriptor.KIND_DOWNLOADER, fmt.Sprintf("%s:%s", opts.ArtifactType, opts.MediaType)) + } + var cfg []byte + if opts.Config != "" { + cfg = []byte(opts.Config) + } + w, h, err := d.Writer(p, opts.ArtifactType, opts.MediaType, opts.Path, cfg) + if err != nil { + return err + } + _, err = io.Copy(w, os.Stdin) + if err != nil { + w.Close() + return err + } + err = w.Close() + if err != nil { + return err + } + path, err := h() + result := Result{ + Path: path, + } + if err != nil { + result.Error = err.Error() + } + data, err := json.Marshal(result) + if err == nil { + cmd.Printf("%s\n", string(data)) + } + return err +} diff --git a/api/ocm/plugin/ppi/cmds/info/cmd.go b/api/ocm/plugin/ppi/cmds/info/cmd.go new file mode 100644 index 000000000..3c317c8ea --- /dev/null +++ b/api/ocm/plugin/ppi/cmds/info/cmd.go @@ -0,0 +1,28 @@ +package info + +import ( + "encoding/json" + + "github.com/spf13/cobra" + + "ocm.software/ocm/api/ocm/plugin/ppi" +) + +const NAME = "info" + +func New(p ppi.Plugin) *cobra.Command { + return &cobra.Command{ + Use: NAME, + Short: "show plugin descriptor", + Long: "", + Args: cobra.MaximumNArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + data, err := json.Marshal(p.Descriptor()) + if err != nil { + return err + } + cmd.Printf("%s\n", string(data)) + return nil + }, + } +} diff --git a/api/ocm/plugin/ppi/cmds/logging.go b/api/ocm/plugin/ppi/cmds/logging.go new file mode 100644 index 000000000..60fba4f22 --- /dev/null +++ b/api/ocm/plugin/ppi/cmds/logging.go @@ -0,0 +1,29 @@ +package cmds + +import ( + "encoding/json" +) + +type LoggingHandler interface { + HandleConfig(data []byte) error +} + +var handler LoggingHandler + +// RegisterLoggingConfigHandler is used to register a configuration handler +// for logging configration passed by the OCM library. +// If standard mandelsoft logging is used, it can be adapted +// by adding the ananymous import of the ppi/logging package. +func RegisterLoggingConfigHandler(h LoggingHandler) { + handler = h +} + +// LoggingConfiguration describes logging configuration for a slave executables like +// plugins. +// If mandelsoft logging is used please use ocm.software/ocm/api/utils/cobrautils/logging.LoggingConfiguration, +// instead. +type LoggingConfiguration struct { + LogFileName string `json:"logFileName"` + LogConfig json.RawMessage `json:"logConfig"` + Json bool `json:"json,omitempty"` +} diff --git a/api/ocm/plugin/ppi/cmds/mergehandler/cmd.go b/api/ocm/plugin/ppi/cmds/mergehandler/cmd.go new file mode 100644 index 000000000..5215d4f69 --- /dev/null +++ b/api/ocm/plugin/ppi/cmds/mergehandler/cmd.go @@ -0,0 +1,21 @@ +package mergehandler + +import ( + "github.com/spf13/cobra" + + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/mergehandler/execute" +) + +const Name = "valuemergehandler" + +func New(p ppi.Plugin) *cobra.Command { + cmd := &cobra.Command{ + Use: Name, + Short: "value merge handler operations", + Long: `This command group provides all commands used to implement an value merge handlers.`, + } + + cmd.AddCommand(execute.New(p)) + return cmd +} diff --git a/api/ocm/plugin/ppi/cmds/mergehandler/execute/cmd.go b/api/ocm/plugin/ppi/cmds/mergehandler/execute/cmd.go new file mode 100644 index 000000000..2f07f5653 --- /dev/null +++ b/api/ocm/plugin/ppi/cmds/mergehandler/execute/cmd.go @@ -0,0 +1,116 @@ +package execute + +import ( + "encoding/json" + "fmt" + "io" + "os" + + "github.com/mandelsoft/goutils/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/ocm/valuemergehandler/hpi" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + Name = "execute" +) + +func New(p ppi.Plugin) *cobra.Command { + opts := Options{} + + cmd := &cobra.Command{ + Use: Name + " ", + Short: "execute a value merge", + Long: ` +This command executes a value merge. The values are taken from *stdin* as JSON +string. It has the following fields: + +- **local** *any* + + The local value to merge into the inbound value. + +- **inbound** *any* + + The value to merge into. This value is based on the original inbound value. + +This action has to provide an execution result as JSON string on *stdout*. It has the +following fields: + +- **modified** *bool* + + Whether the inbound value has been modified by merging with the local value. + +- **value** *string* + + The merged value + +- **message** *string* + + An error message. +`, + Args: cobra.ExactArgs(2), + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return opts.Complete(args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return Command(p, cmd, &opts) + }, + } + opts.AddFlags(cmd.Flags()) + return cmd +} + +type Options struct { + Name string + Configuration json.RawMessage +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { +} + +func (o *Options) Complete(args []string) error { + if len(args) == 0 { + return fmt.Errorf("algorithm name missing") + } + o.Name = args[0] + if len(args) > 1 { + o.Configuration = []byte(args[1]) + } + if len(args) > 2 { + return fmt.Errorf("too many arguments") + } + return nil +} + +func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { + h := p.GetValueMergeHandler(opts.Name) + if h == nil { + return errors.ErrUnknown(hpi.KIND_VALUE_MERGE_ALGORITHM, opts.Name) + } + + data, err := io.ReadAll(os.Stdin) + if err != nil { + return err + } + + var input ppi.ValueMergeData + err = json.Unmarshal(data, &input) + if err != nil { + return err + } + + result, err := h.Execute(p, input.Local, input.Inbound, opts.Configuration) + if err != nil { + return err + } + data, err = runtime.DefaultJSONEncoding.Marshal(result) + if err != nil { + return err + } + cmd.Printf("%s\n", string(data)) + return nil +} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/topics/descriptor/topic.go b/api/ocm/plugin/ppi/cmds/topics/descriptor/topic.go similarity index 99% rename from pkg/contexts/ocm/plugin/ppi/cmds/topics/descriptor/topic.go rename to api/ocm/plugin/ppi/cmds/topics/descriptor/topic.go index 0f92d562d..417ebe828 100644 --- a/pkg/contexts/ocm/plugin/ppi/cmds/topics/descriptor/topic.go +++ b/api/ocm/plugin/ppi/cmds/topics/descriptor/topic.go @@ -3,7 +3,7 @@ package descriptor import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/options" + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" ) func New() *cobra.Command { diff --git a/api/ocm/plugin/ppi/cmds/upload/cmd.go b/api/ocm/plugin/ppi/cmds/upload/cmd.go new file mode 100644 index 000000000..2196ee853 --- /dev/null +++ b/api/ocm/plugin/ppi/cmds/upload/cmd.go @@ -0,0 +1,25 @@ +package upload + +import ( + "github.com/spf13/cobra" + + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/upload/put" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/upload/validate" +) + +const Name = "upload" + +func New(p ppi.Plugin) *cobra.Command { + cmd := &cobra.Command{ + Use: Name, + Short: "upload specific operations", + Long: ` +This command group provides all commands used to implement an uploader +described by an uploader descriptor.`, + } + + cmd.AddCommand(validate.New(p)) + cmd.AddCommand(put.New(p)) + return cmd +} diff --git a/api/ocm/plugin/ppi/cmds/upload/put/cmd.go b/api/ocm/plugin/ppi/cmds/upload/put/cmd.go new file mode 100644 index 000000000..7bdac8633 --- /dev/null +++ b/api/ocm/plugin/ppi/cmds/upload/put/cmd.go @@ -0,0 +1,110 @@ +package put + +import ( + "encoding/json" + "fmt" + "io" + "os" + + "github.com/mandelsoft/goutils/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/ocm/plugin/descriptor" + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/common" + "ocm.software/ocm/api/utils/cobrautils/flag" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + Name = "put" + OptCreds = common.OptCreds + OptHint = common.OptHint + OptMedia = common.OptMedia + OptArt = common.OptArt +) + +func New(p ppi.Plugin) *cobra.Command { + opts := Options{} + + cmd := &cobra.Command{ + Use: Name + " [] ", + Short: "upload blob to external repository", + Long: ` +Read the blob content from *stdin*, store the blob in the repository specified +by the given repository specification and return the access specification +(as JSON document string) usable to retrieve the blob, again, on * stdout*. +The uploader to use is specified by the first argument. This might only be +relevant, if the plugin supports multiple uploaders. +`, + Args: cobra.ExactArgs(2), + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return opts.Complete(args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return Command(p, cmd, &opts) + }, + } + opts.AddFlags(cmd.Flags()) + return cmd +} + +type Options struct { + Name string + Specification json.RawMessage + + Credentials credentials.DirectCredentials + MediaType string + ArtifactType string + + Hint string +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { + flag.YAMLVarP(fs, &o.Credentials, OptCreds, "c", nil, "credentials") + flag.StringToStringVarPF(fs, &o.Credentials, "credential", "C", nil, "dedicated credential value") + fs.StringVarP(&o.MediaType, OptMedia, "m", "", "media type of input blob") + fs.StringVarP(&o.ArtifactType, OptArt, "a", "", "artifact type of input blob") + fs.StringVarP(&o.Hint, OptHint, "H", "", "reference hint for storing blob") +} + +func (o *Options) Complete(args []string) error { + o.Name = args[0] + if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[1]), &o.Specification); err != nil { + return errors.Wrapf(err, "invalid repository specification") + } + return nil +} + +func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { + spec, err := p.DecodeUploadTargetSpecification(opts.Specification) + if err != nil { + return errors.Wrapf(err, "target specification") + } + + u := p.GetUploader(opts.Name) + if u == nil { + return errors.ErrNotFound(descriptor.KIND_UPLOADER, fmt.Sprintf("%s:%s", opts.ArtifactType, opts.MediaType)) + } + w, h, err := u.Writer(p, opts.ArtifactType, opts.MediaType, opts.Hint, spec, opts.Credentials) + if err != nil { + return err + } + _, err = io.Copy(w, os.Stdin) + if err != nil { + w.Close() + return err + } + err = w.Close() + if err != nil { + return err + } + acc := h() + data, err := json.Marshal(acc) + if err == nil { + cmd.Printf("%s\n", string(data)) + } + return err +} diff --git a/api/ocm/plugin/ppi/cmds/upload/validate/cmd.go b/api/ocm/plugin/ppi/cmds/upload/validate/cmd.go new file mode 100644 index 000000000..845334906 --- /dev/null +++ b/api/ocm/plugin/ppi/cmds/upload/validate/cmd.go @@ -0,0 +1,101 @@ +package validate + +import ( + "encoding/json" + + "github.com/mandelsoft/goutils/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/ocm/plugin/descriptor" + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/common" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + Name = "validate" + OptMedia = common.OptMedia + OptArt = common.OptArt +) + +func New(p ppi.Plugin) *cobra.Command { + opts := Options{} + + cmd := &cobra.Command{ + Use: Name + " [] ", + Short: "validate upload specification", + Long: ` +This command accepts a target specification as argument. It is used to +validate the specification for the specified upoader and to provide some +metadata for the given specification. + +This metadata has to be provided as JSON document string on *stdout* and has the +following fields: + +- **consumerId** *map[string]string* + + The consumer id used to determine optional credentials for the + underlying repository. If specified, at least the type field must + be set. +`, + Args: cobra.ExactArgs(2), + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return opts.Complete(args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return Command(p, cmd, &opts) + }, + } + opts.AddFlags(cmd.Flags()) + return cmd +} + +type Options struct { + Name string + Specification json.RawMessage + + ArtifactType string + MediaType string +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { + fs.StringVarP(&o.MediaType, OptMedia, "m", "", "media type of input blob") + fs.StringVarP(&o.ArtifactType, OptArt, "a", "", "artifact type of input blob") +} + +func (o *Options) Complete(args []string) error { + o.Name = args[0] + if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[1]), &o.Specification); err != nil { + return errors.Wrapf(err, "invalid repository specification") + } + return nil +} + +type Result struct { + ConsumerId credentials.ConsumerIdentity `json:"consumerId"` +} + +func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { + spec, err := p.DecodeUploadTargetSpecification(opts.Specification) + if err != nil { + return errors.Wrapf(err, "target specification") + } + + m := p.GetUploader(opts.Name) + if m == nil { + return errors.ErrUnknown(descriptor.KIND_UPLOADER, spec.GetType()) + } + info, err := m.ValidateSpecification(p, spec) + if err != nil { + return err + } + result := Result{info.ConsumerId} + data, err := json.Marshal(result) + if err != nil { + return err + } + cmd.Printf("%s\n", string(data)) + return nil +} diff --git a/api/ocm/plugin/ppi/cmds/valueset/cmd.go b/api/ocm/plugin/ppi/cmds/valueset/cmd.go new file mode 100644 index 000000000..86d77ec3b --- /dev/null +++ b/api/ocm/plugin/ppi/cmds/valueset/cmd.go @@ -0,0 +1,24 @@ +package valueset + +import ( + "github.com/spf13/cobra" + + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/valueset/compose" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/valueset/validate" +) + +const Name = "valueset" + +func New(p ppi.Plugin) *cobra.Command { + cmd := &cobra.Command{ + Use: Name, + Short: "valueset operations", + Long: `This command group provides all commands used to implement a value set +described by a value set descriptor (` + p.Name() + ` descriptor.`, + } + + cmd.AddCommand(compose.New(p)) + cmd.AddCommand(validate.New(p)) + return cmd +} diff --git a/api/ocm/plugin/ppi/cmds/valueset/compose/cmd.go b/api/ocm/plugin/ppi/cmds/valueset/compose/cmd.go new file mode 100644 index 000000000..fbfbec860 --- /dev/null +++ b/api/ocm/plugin/ppi/cmds/valueset/compose/cmd.go @@ -0,0 +1,96 @@ +package compose + +import ( + "encoding/json" + + "github.com/mandelsoft/goutils/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + "ocm.software/ocm/api/ocm/plugin/descriptor" + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/utils/runtime" +) + +const Name = "compose" + +func New(p ppi.Plugin) *cobra.Command { + opts := Options{} + + cmd := &cobra.Command{ + Use: Name + " ", + Short: "compose value set from options and base specification", + Long: ` +The task of this command is ued to compose and validate a value set based on +some explicitly given input options and preconfigured specifications. + +The finally composed set has to be returned as JSON document +on *stdout*. + +This command is only used, if for a value set descriptor configuration +na direct composition rules are configured (` + p.Name() + ` descriptor). + +If possible, predefined standard options should be used. In such a case only the +name field should be defined for an option. If required, new options can be +defined by additionally specifying a type and a description. New options should +be used very carefully. The chosen names MUST not conflict with names provided +by other plugins. Therefore, it is highly recommended to use names prefixed +by the plugin name. + +` + options.DefaultRegistry.Usage(), + Args: cobra.ExactArgs(4), + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return opts.Complete(args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return Command(p, cmd, &opts) + }, + } + opts.AddFlags(cmd.Flags()) + return cmd +} + +type Options struct { + Purpose string + Name string + Options ppi.Config + Base ppi.Config +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { +} + +func (o *Options) Complete(args []string) error { + o.Purpose = args[0] + o.Name = args[1] + if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[2]), &o.Options); err != nil { + return errors.Wrapf(err, "invalid avalue set options") + } + if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[3]), &o.Base); err != nil { + return errors.Wrapf(err, "invalid base set specification") + } + return nil +} + +func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { + k, v := runtime.KindVersion(opts.Name) + s := p.GetValueSet(opts.Purpose, k, v) + if s == nil { + return errors.ErrUnknown(descriptor.KIND_VALUESET, opts.Name) + } + err := opts.Options.ConvertFor(s.Options()...) + if err != nil { + return err + } + err = s.ComposeSpecification(p, opts.Options, opts.Base) + if err != nil { + return err + } + data, err := json.Marshal(opts.Base) + if err != nil { + return err + } + cmd.Printf("%s\n", string(data)) + return nil +} diff --git a/api/ocm/plugin/ppi/cmds/valueset/validate/cmd.go b/api/ocm/plugin/ppi/cmds/valueset/validate/cmd.go new file mode 100644 index 000000000..a1ad30ea6 --- /dev/null +++ b/api/ocm/plugin/ppi/cmds/valueset/validate/cmd.go @@ -0,0 +1,89 @@ +package validate + +import ( + "encoding/json" + + "github.com/mandelsoft/goutils/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "ocm.software/ocm/api/ocm/plugin/descriptor" + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/utils/runtime" +) + +const Name = "validate" + +func New(p ppi.Plugin) *cobra.Command { + opts := Options{} + + cmd := &cobra.Command{ + Use: Name + " ", + Short: "validate value set", + Long: ` +This command accepts a value set as argument. It is used to +validate the specification and to provide some metadata for the given +specification. + +This metadata has to be provided as JSON string on *stdout* and has the +following fields: + +- **description** *string* + + A short textual description of the described value set. +`, + Args: cobra.ExactArgs(2), + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return opts.Complete(args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return Command(p, cmd, &opts) + }, + } + opts.AddFlags(cmd.Flags()) + return cmd +} + +type Options struct { + Purpose string + Specification json.RawMessage +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { +} + +func (o *Options) Complete(args []string) error { + o.Purpose = args[0] + if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[1]), &o.Specification); err != nil { + return errors.Wrapf(err, "invalid valueset specification") + } + return nil +} + +type Result struct { + Short string `json:"description"` +} + +func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { + spec, err := p.DecodeValueSet(opts.Purpose, opts.Specification) + if err != nil { + return errors.Wrapf(err, "access specification") + } + + k, v := runtime.KindVersion(spec.GetType()) + m := p.GetValueSet(opts.Purpose, k, v) + if m == nil { + return errors.ErrUnknown(descriptor.KIND_VALUESET, spec.GetType()) + } + info, err := m.ValidateSpecification(p, spec) + if err != nil { + return err + } + result := Result{Short: info.Short} + data, err := json.Marshal(result) + if err != nil { + return err + } + cmd.Printf("%s\n", string(data)) + return nil +} diff --git a/api/ocm/plugin/ppi/config/config.go b/api/ocm/plugin/ppi/config/config.go new file mode 100644 index 000000000..b1b1e8262 --- /dev/null +++ b/api/ocm/plugin/ppi/config/config.go @@ -0,0 +1,28 @@ +package config + +import ( + "context" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds/command" + "ocm.software/ocm/api/utils/runtime" +) + +func init() { + command.RegisterCommandConfigHandler(&commandHandler{}) +} + +type commandHandler struct{} + +func (c commandHandler) HandleConfig(ctx context.Context, data []byte) (context.Context, error) { + var err error + + octx := ocm.DefaultContext() + ctx = octx.BindTo(ctx) + if len(data) != 0 { + _, err = octx.ConfigContext().ApplyData(data, runtime.DefaultYAMLEncoding, " cli config") + // Ugly, enforce configuration update + octx.GetResolver() + } + return ctx, err +} diff --git a/pkg/contexts/ocm/plugin/ppi/config/doc.go b/api/ocm/plugin/ppi/config/doc.go similarity index 100% rename from pkg/contexts/ocm/plugin/ppi/config/doc.go rename to api/ocm/plugin/ppi/config/doc.go diff --git a/pkg/contexts/ocm/plugin/ppi/doc.go b/api/ocm/plugin/ppi/doc.go similarity index 100% rename from pkg/contexts/ocm/plugin/ppi/doc.go rename to api/ocm/plugin/ppi/doc.go diff --git a/api/ocm/plugin/ppi/interface.go b/api/ocm/plugin/ppi/interface.go new file mode 100644 index 000000000..c0a590576 --- /dev/null +++ b/api/ocm/plugin/ppi/interface.go @@ -0,0 +1,213 @@ +package ppi + +import ( + "encoding/json" + "io" + + "github.com/spf13/cobra" + + "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext/action" + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + "ocm.software/ocm/api/ocm/plugin/descriptor" + "ocm.software/ocm/api/ocm/plugin/internal" + "ocm.software/ocm/api/utils/runtime" +) + +type ( + Descriptor = descriptor.Descriptor + UploaderKey = descriptor.UploaderKey + UploaderDescriptor = descriptor.UploaderDescriptor + DownloaderKey = descriptor.DownloaderKey + DownloaderDescriptor = descriptor.DownloaderDescriptor + AccessMethodDescriptor = descriptor.AccessMethodDescriptor + CLIOption = descriptor.CLIOption + + ActionSpecInfo = internal.ActionSpecInfo + AccessSpecInfo = internal.AccessSpecInfo + ValueSetInfo = internal.ValueSetInfo + UploadTargetSpecInfo = internal.UploadTargetSpecInfo +) + +var REALM = descriptor.REALM + +type Plugin interface { + Name() string + Version() string + Descriptor() descriptor.Descriptor + + SetDescriptorTweaker(func(descriptor descriptor.Descriptor) descriptor.Descriptor) + + SetShort(s string) + SetLong(s string) + SetConfigParser(config func(raw json.RawMessage) (interface{}, error)) + ForwardLogging(b ...bool) + + RegisterDownloader(arttype, mediatype string, u Downloader) error + GetDownloader(name string) Downloader + GetDownloaderFor(arttype, mediatype string) Downloader + + RegisterUploader(arttype, mediatype string, u Uploader) error + GetUploader(name string) Uploader + GetUploaderFor(arttype, mediatype string) Uploader + DecodeUploadTargetSpecification(data []byte) (UploadTargetSpec, error) + + RegisterAccessMethod(m AccessMethod) error + DecodeAccessSpecification(data []byte) (AccessSpec, error) + GetAccessMethod(name string, version string) AccessMethod + + RegisterAction(a Action) error + DecodeAction(data []byte) (ActionSpec, error) + GetAction(name string) Action + + RegisterValueMergeHandler(h ValueMergeHandler) error + GetValueMergeHandler(name string) ValueMergeHandler + + RegisterValueSet(h ValueSet) error + DecodeValueSet(purpose string, data []byte) (runtime.TypedObject, error) + GetValueSet(purpose, name, version string) ValueSet + + RegisterCommand(c Command) error + GetCommand(name string) Command + Commands() []Command + + RegisterConfigType(c cpi.ConfigType) error + GetConfigType(name string) *descriptor.ConfigTypeDescriptor + ConfigTypes() []descriptor.ConfigTypeDescriptor + + GetOptions() *Options + GetConfig() (interface{}, error) +} + +type AccessMethod interface { + runtime.TypedObjectDecoder[AccessSpec] + + Name() string + Version() string + + // Options provides the list of CLI options supported to compose the access + // specification. + Options() []options.OptionType + + // Description provides a general description for the access mehod kind. + Description() string + // Format describes the attributes of the dedicated version. + Format() string + + ValidateSpecification(p Plugin, spec AccessSpec) (info *AccessSpecInfo, err error) + Reader(p Plugin, spec AccessSpec, creds credentials.Credentials) (io.ReadCloser, error) + ComposeAccessSpecification(p Plugin, opts Config, config Config) error +} + +type AccessSpec = runtime.TypedObject + +type AccessSpecProvider func() AccessSpec + +type UploadFormats runtime.KnownTypes[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]] + +type Uploader interface { + Decoders() UploadFormats + + Name() string + Description() string + + ValidateSpecification(p Plugin, spec UploadTargetSpec) (info *UploadTargetSpecInfo, err error) + Writer(p Plugin, arttype, mediatype string, hint string, spec UploadTargetSpec, creds credentials.Credentials) (io.WriteCloser, AccessSpecProvider, error) +} + +type UploadTargetSpec = runtime.TypedObject + +type DownloadResultProvider func() (string, error) + +type Downloader interface { + Name() string + Description() string + ConfigSchema() []byte + + Writer(p Plugin, arttype, mediatype string, filepath string, config []byte) (io.WriteCloser, DownloadResultProvider, error) +} + +type ActionSpec = action.ActionSpec + +type ActionResult = action.ActionResult + +type Action interface { + Name() string + Description() string + DefaultSelectors() []string + ConsumerType() string + + Execute(p Plugin, spec ActionSpec, creds credentials.DirectCredentials) (result ActionResult, err error) +} + +type Value = runtime.RawValue + +type ValueMergeResult struct { + Modified bool `json:"modified"` + Value Value `json:"value"` + Message string `json:"message,omitempty"` +} + +type ValueMergeData struct { + Local Value `json:"local"` + Inbound Value `json:"inbound"` +} + +type ValueMergeHandler interface { + Name() string + Description() string + + Execute(p Plugin, local Value, inbound Value, config json.RawMessage) (result ValueMergeResult, err error) +} + +type ValueSet interface { + runtime.TypedObjectDecoder[AccessSpec] + + Name() string + Version() string + + // Purposes describes the purposes the set should be ued for. + // So far, only the purpose PURPOSE_ROUTINGSLIP is defined. + Purposes() []string + + // Options provides the list of CLI options supported to compose the access + // specification. + Options() []options.OptionType + + // Description provides a general description for the access mehod kind. + Description() string + // Format describes the attributes of the dedicated version. + Format() string + + ValidateSpecification(p Plugin, spec runtime.TypedObject) (info *ValueSetInfo, err error) + ComposeSpecification(p Plugin, opts Config, config Config) error +} + +// Command is the interface for a CLI command provided by a plugin. +type Command interface { + // Name of command used in the plugin. + // This is also the default object type and is used to + // name top-level commands in the CLI. + Name() string + Description() string + Usage() string + Short() string + Example() string + // ObjectType is optional and can be used + // together with a verb. It then is used as + // sub command name for the object type. + // By default, the command name is used. + ObjectType() string + // Verb is optional and can be set + // to place the command in the verb hierarchy of + // the OCM CLI. It is used together with the ObjectType. + // (command will be *ocm *. + Verb() string + // Realm is optional and is used to place the command + // in a realm. This requires a verb. + Realm() string + CLIConfigRequired() bool + + Command() *cobra.Command +} diff --git a/api/ocm/plugin/ppi/logging/config.go b/api/ocm/plugin/ppi/logging/config.go new file mode 100644 index 000000000..bcf29644b --- /dev/null +++ b/api/ocm/plugin/ppi/logging/config.go @@ -0,0 +1,25 @@ +package logging + +import ( + "sigs.k8s.io/yaml" + + "ocm.software/ocm/api/ocm/plugin/ppi/cmds" + "ocm.software/ocm/api/utils/cobrautils/logopts/logging" +) + +func init() { + cmds.RegisterLoggingConfigHandler(&loggingConfigHandler{}) +} + +type loggingConfigHandler struct{} + +func (l loggingConfigHandler) HandleConfig(data []byte) error { + var cfg logging.LoggingConfiguration + + err := yaml.Unmarshal(data, &cfg) + if err != nil { + return err + } + + return cfg.Apply() +} diff --git a/pkg/contexts/ocm/plugin/ppi/logging/doc.go b/api/ocm/plugin/ppi/logging/doc.go similarity index 100% rename from pkg/contexts/ocm/plugin/ppi/logging/doc.go rename to api/ocm/plugin/ppi/logging/doc.go diff --git a/api/ocm/plugin/ppi/options.go b/api/ocm/plugin/ppi/options.go new file mode 100644 index 000000000..bc982e79b --- /dev/null +++ b/api/ocm/plugin/ppi/options.go @@ -0,0 +1,24 @@ +package ppi + +import ( + "encoding/json" + + "github.com/spf13/pflag" + + "ocm.software/ocm/api/utils/cobrautils/flag" +) + +const ( + OptPluginConfig = "config" + OptPlugingLogConfig = "log-config" +) + +type Options struct { + Config json.RawMessage + LogConfig json.RawMessage +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { + flag.YAMLVarP(fs, &o.Config, OptPluginConfig, "c", nil, "plugin configuration") + flag.YAMLVarP(fs, &o.LogConfig, OptPlugingLogConfig, "", nil, "ocm logging configuration") +} diff --git a/api/ocm/plugin/ppi/plugin.go b/api/ocm/plugin/ppi/plugin.go new file mode 100644 index 000000000..319a2891e --- /dev/null +++ b/api/ocm/plugin/ppi/plugin.go @@ -0,0 +1,640 @@ +package ppi + +import ( + "encoding/json" + "fmt" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/general" + "github.com/mandelsoft/goutils/generics" + "github.com/mandelsoft/goutils/maputils" + "github.com/spf13/cobra" + "golang.org/x/exp/slices" + + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/datacontext/action" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + "ocm.software/ocm/api/ocm/ocmutils/registry" + "ocm.software/ocm/api/ocm/plugin/descriptor" + "ocm.software/ocm/api/utils/cobrautils" + "ocm.software/ocm/api/utils/errkind" + "ocm.software/ocm/api/utils/runtime" +) + +type plugin struct { + name string + version string + descriptor descriptor.Descriptor + tweaker func(descriptor descriptor.Descriptor) descriptor.Descriptor + options Options + + downloaders map[string]Downloader + downmappings *registry.Registry[Downloader, DownloaderKey] + + uploaders map[string]Uploader + upmappings *registry.Registry[Uploader, UploaderKey] + uploaderScheme runtime.Scheme[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]] + + methods map[string]AccessMethod + accessScheme runtime.Scheme[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]] + + actions map[string]Action + mergehandlers map[string]ValueMergeHandler + mergespecs map[string]*descriptor.LabelMergeSpecification + + valuesets map[string]map[string]ValueSet + setScheme map[string]runtime.Scheme[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]] + + clicmds map[string]Command + + configParser func(message json.RawMessage) (interface{}, error) +} + +func NewPlugin(name string, version string) Plugin { + return &plugin{ + name: name, + version: version, + methods: map[string]AccessMethod{}, + + downloaders: map[string]Downloader{}, + downmappings: registry.NewRegistry[Downloader, DownloaderKey](), + + uploaders: map[string]Uploader{}, + upmappings: registry.NewRegistry[Uploader, UploaderKey](), + + accessScheme: runtime.MustNewDefaultScheme[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]](&runtime.UnstructuredVersionedTypedObject{}, false, nil), + uploaderScheme: runtime.MustNewDefaultScheme[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]](&runtime.UnstructuredVersionedTypedObject{}, false, nil), + + actions: map[string]Action{}, + mergehandlers: map[string]ValueMergeHandler{}, + mergespecs: map[string]*descriptor.LabelMergeSpecification{}, + + valuesets: map[string]map[string]ValueSet{}, + setScheme: map[string]runtime.Scheme[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]]{}, + + clicmds: map[string]Command{}, + + descriptor: descriptor.Descriptor{ + Version: descriptor.VERSION, + PluginName: name, + PluginVersion: version, + }, + } +} + +func (p *plugin) Name() string { + return p.name +} + +func (p *plugin) Version() string { + return p.version +} + +func (p *plugin) Descriptor() descriptor.Descriptor { + if p.tweaker != nil { + return p.tweaker(p.descriptor) + } + return p.descriptor +} + +func (p *plugin) GetOptions() *Options { + return &p.options +} + +func (p *plugin) SetLong(s string) { + p.descriptor.Long = s +} + +func (p *plugin) SetShort(s string) { + p.descriptor.Short = s +} + +func (p *plugin) SetDescriptorTweaker(t func(descriptor descriptor.Descriptor) descriptor.Descriptor) { + p.tweaker = t +} + +func (p *plugin) SetConfigParser(config func(raw json.RawMessage) (interface{}, error)) { + p.configParser = config +} + +func (p *plugin) ForwardLogging(b ...bool) { + p.descriptor.ForwardLogging = general.OptionalDefaultedBool(true, b...) +} + +func (p *plugin) GetConfig() (interface{}, error) { + if len(p.options.Config) == 0 { + return nil, nil + } + if p.configParser == nil { + var cfg interface{} + if err := json.Unmarshal(p.options.Config, &cfg); err != nil { + return nil, err + } + return &cfg, nil + } + return p.configParser(p.options.Config) +} + +//////////////////////////////////////////////////////////////////////////////// + +func (p *plugin) RegisterDownloader(arttype, mediatype string, hdlr Downloader) error { + key := DownloaderKey{}.SetArtifact(arttype, mediatype) + if !key.IsValid() { + return errors.ErrInvalid("artifact context") + } + + old := p.downloaders[hdlr.Name()] + if old != nil && old != hdlr { + return fmt.Errorf("downloader name %q already in use", hdlr.Name()) + } + + var desc *DownloaderDescriptor + if old == nil { + schema := "" + if len(hdlr.ConfigSchema()) > 0 { + schema = string(hdlr.ConfigSchema()) + } + desc = &DownloaderDescriptor{ + Name: hdlr.Name(), + Description: hdlr.Description(), + Constraints: []DownloaderKey{}, + ConfigScheme: schema, + } + p.descriptor.Downloaders = append(p.descriptor.Downloaders, *desc) + desc = &p.descriptor.Downloaders[len(p.descriptor.Downloaders)-1] + } else { + for i := range p.descriptor.Downloaders { + if p.descriptor.Downloaders[i].Name == hdlr.Name() { + desc = &p.descriptor.Downloaders[i] + } + } + } + + cur := p.downmappings.GetHandler(key) + if len(cur) > 0 && cur[0] != hdlr { + return fmt.Errorf("downloader mapping key %q already in use", key) + } + if cur == nil { + p.downmappings.Register(key, hdlr) + desc.Constraints = append(desc.Constraints, DownloaderKey{ArtifactType: key.ArtifactType, MediaType: key.MediaType}) + } + p.downloaders[hdlr.Name()] = hdlr + return nil +} + +func (p *plugin) GetDownloader(name string) Downloader { + return p.downloaders[name] +} + +func (p *plugin) GetDownloaderFor(arttype, mediatype string) Downloader { + h := p.downmappings.LookupHandler(DownloaderKey{}.SetArtifact(arttype, mediatype)) + if len(h) == 0 { + return nil + } + return h[0] +} + +//////////////////////////////////////////////////////////////////////////////// + +func (p *plugin) RegisterRepositoryContextUploader(contexttype, repotype, arttype, mediatype string, u Uploader) error { + if contexttype == "" || repotype == "" { + return fmt.Errorf("repository context required") + } + return p.registerUploader(UploaderKey{}.SetArtifact(arttype, mediatype).SetRepo(contexttype, repotype), u) +} + +func (p *plugin) RegisterUploader(arttype, mediatype string, u Uploader) error { + return p.registerUploader(UploaderKey{}.SetArtifact(arttype, mediatype), u) +} + +func (p *plugin) registerUploader(key UploaderKey, hdlr Uploader) error { + if !key.RepositoryContext.IsValid() { + return errors.ErrInvalid("repository context") + } + if !key.ArtifactContext.IsValid() { + return errors.ErrInvalid("artifact context") + } + old := p.uploaders[hdlr.Name()] + if old != nil && old != hdlr { + return fmt.Errorf("uploader name %q already in use", hdlr.Name()) + } + + var desc *UploaderDescriptor + if old == nil { + desc = &UploaderDescriptor{ + Name: hdlr.Name(), + Description: hdlr.Description(), + Constraints: []UploaderKey{}, + } + p.descriptor.Uploaders = append(p.descriptor.Uploaders, *desc) + desc = &p.descriptor.Uploaders[len(p.descriptor.Uploaders)-1] + } else { + for i := range p.descriptor.Uploaders { + if p.descriptor.Uploaders[i].Name == hdlr.Name() { + desc = &p.descriptor.Uploaders[i] + } + } + } + + cur := p.upmappings.GetHandler(key) + if len(cur) > 0 && cur[0] != hdlr { + return fmt.Errorf("uploader mapping key %q already in use", key) + } + list := errors.ErrListf("uploader decoders") + for n, d := range hdlr.Decoders() { + list.Add(p.uploaderScheme.RegisterByDecoder(n, d)) + } + if list.Len() > 0 { + return list.Result() + } + if cur == nil { + p.upmappings.Register(key, hdlr) + desc.Constraints = append(desc.Constraints, key) + } + p.uploaders[hdlr.Name()] = hdlr + return nil +} + +func (p *plugin) GetUploader(name string) Uploader { + return p.uploaders[name] +} + +func (p *plugin) GetUploaderFor(arttype, mediatype string) Uploader { + h := p.upmappings.LookupHandler(UploaderKey{}.SetArtifact(arttype, mediatype)) + if len(h) == 0 { + return nil + } + return h[0] +} + +func (p *plugin) DecodeUploadTargetSpecification(data []byte) (UploadTargetSpec, error) { + o, err := p.uploaderScheme.Decode(data, nil) + if err != nil { + return nil, err + } + return o, nil +} + +//////////////////////////////////////////////////////////////////////////////// + +func (p *plugin) RegisterAccessMethod(m AccessMethod) error { + if p.GetAccessMethod(m.Name(), m.Version()) != nil { + n := m.Name() + if m.Version() != "" { + n += runtime.VersionSeparator + m.Version() + } + return errors.ErrAlreadyExists(errkind.KIND_ACCESSMETHOD, n) + } + + var optlist []CLIOption + for _, o := range m.Options() { + known := options.DefaultRegistry.GetOptionType(o.GetName()) + if known != nil { + if o.ValueType() != known.ValueType() { + return fmt.Errorf("option type %s[%s] conflicts with standard option type using value type %s", o.GetName(), o.ValueType(), known.ValueType()) + } + optlist = append(optlist, CLIOption{ + Name: o.GetName(), + }) + } else { + optlist = append(optlist, CLIOption{ + Name: o.GetName(), + Type: o.ValueType(), + Description: o.GetDescriptionText(), + }) + } + } + vers := m.Version() + if vers == "" { + meth := descriptor.AccessMethodDescriptor{ + ValueSetDefinition: descriptor.ValueSetDefinition{ + ValueTypeDefinition: descriptor.ValueTypeDefinition{ + Name: m.Name(), + Description: m.Description(), + Format: m.Format(), + }, + }, + } + p.descriptor.AccessMethods = append(p.descriptor.AccessMethods, meth) + p.accessScheme.RegisterByDecoder(m.Name(), m) + p.methods[m.Name()] = m + vers = "v1" + } + meth := descriptor.AccessMethodDescriptor{ + ValueSetDefinition: descriptor.ValueSetDefinition{ + ValueTypeDefinition: descriptor.ValueTypeDefinition{ + Name: m.Name(), + Version: vers, + Description: m.Description(), + Format: m.Format(), + }, + CLIOptions: optlist, + }, + } + p.descriptor.AccessMethods = append(p.descriptor.AccessMethods, meth) + p.accessScheme.RegisterByDecoder(m.Name()+"/"+vers, m) + p.methods[m.Name()+"/"+vers] = m + return nil +} + +func (p *plugin) DecodeAccessSpecification(data []byte) (AccessSpec, error) { + return p.accessScheme.Decode(data, nil) +} + +func (p *plugin) GetAccessMethod(name string, version string) AccessMethod { + n := name + if version != "" { + n += "/" + version + } + return p.methods[n] +} + +//////////////////////////////////////////////////////////////////////////////// + +func (p *plugin) RegisterAction(a Action) error { + if p.GetAction(a.Name()) != nil { + return errors.ErrAlreadyExists("action", a.Name()) + } + vers := action.DefaultRegistry().SupportedActionVersions(a.Name()) + if len(vers) == 0 { + return errors.ErrNotSupported("action", a.Name()) + } + + act := descriptor.ActionDescriptor{ + Name: a.Name(), + Versions: vers, + Description: a.Description(), + DefaultSelectors: a.DefaultSelectors(), + ConsumerType: a.ConsumerType(), + } + p.descriptor.Actions = append(p.descriptor.Actions, act) + p.actions[a.Name()] = a + return nil +} + +func (p *plugin) DecodeAction(data []byte) (ActionSpec, error) { + return action.DefaultRegistry().DecodeActionSpec(data, runtime.DefaultJSONEncoding) +} + +func (p *plugin) GetAction(name string) Action { + return p.actions[name] +} + +//////////////////////////////////////////////////////////////////////////////// + +func (p *plugin) RegisterValueMergeHandler(a ValueMergeHandler) error { + if p.GetValueMergeHandler(a.Name()) != nil { + return errors.ErrAlreadyExists("value mergehandler", a.Name()) + } + + hd := descriptor.ValueMergeHandlerDescriptor{ + Name: a.Name(), + Description: a.Description(), + } + p.descriptor.ValueMergeHandlers = append(p.descriptor.ValueMergeHandlers, hd) + p.mergehandlers[a.Name()] = a + return nil +} + +func (p *plugin) GetValueMergeHandler(name string) ValueMergeHandler { + return p.mergehandlers[name] +} + +func (p *plugin) RegisterLabelMergeSpecification(name, version string, spec *metav1.MergeAlgorithmSpecification, desc string) error { + e := descriptor.LabelMergeSpecification{ + Name: name, + Version: version, + Description: desc, + MergeAlgorithmSpecification: *spec, + } + + if p.GetLabelMergeSpecification(e.GetName()) != nil { + return errors.ErrAlreadyExists("label merge spec", e.GetName()) + } + + p.descriptor.LabelMergeSpecifications = append(p.descriptor.LabelMergeSpecifications, e) + p.mergespecs[e.GetName()] = &e + return nil +} + +func (p *plugin) GetLabelMergeSpecification(id string) *descriptor.LabelMergeSpecification { + return p.mergespecs[id] +} + +//////////////////////////////////////////////////////////////////////////////// + +func (p *plugin) DecodeValueSet(purpose string, data []byte) (runtime.TypedObject, error) { + schemes := p.setScheme[purpose] + if schemes == nil { + return nil, errors.ErrUnknown(descriptor.KIND_PURPOSE) + } + return schemes.Decode(data, nil) +} + +func (p *plugin) GetValueSet(purpose string, name string, version string) ValueSet { + n := name + if version != "" { + n += "/" + version + } + set := p.valuesets[purpose] + if set == nil { + return nil + } + return set[n] +} + +func (p *plugin) RegisterValueSet(s ValueSet) error { + n := s.Name() + if s.Version() != "" { + n += runtime.VersionSeparator + s.Version() + } + for _, pp := range s.Purposes() { + if p.GetValueSet(pp, s.Name(), s.Version()) != nil { + return errors.ErrAlreadyExists(descriptor.KIND_VALUESET, n) + } + } + + var optlist []CLIOption + for _, o := range s.Options() { + known := options.DefaultRegistry.GetOptionType(o.GetName()) + if known != nil { + if o.ValueType() != known.ValueType() { + return fmt.Errorf("option type %s[%s] conflicts with standard option type using value type %s", o.GetName(), o.ValueType(), known.ValueType()) + } + optlist = append(optlist, CLIOption{ + Name: o.GetName(), + }) + } else { + optlist = append(optlist, CLIOption{ + Name: o.GetName(), + Type: o.ValueType(), + Description: o.GetDescriptionText(), + }) + } + } + vers := s.Version() + if vers == "" { + set := descriptor.ValueSetDescriptor{ + ValueSetDefinition: descriptor.ValueSetDefinition{ + ValueTypeDefinition: descriptor.ValueTypeDefinition{ + Name: s.Name(), + Description: s.Description(), + Format: s.Format(), + }, + }, + Purposes: slices.Clone(s.Purposes()), + } + p.descriptor.ValueSets = append(p.descriptor.ValueSets, set) + for _, pp := range s.Purposes() { + schemes := p.setScheme[pp] + if schemes == nil { + schemes = runtime.MustNewDefaultScheme[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]](&runtime.UnstructuredVersionedTypedObject{}, false, nil) + p.setScheme[pp] = schemes + } + schemes.RegisterByDecoder(s.Name(), s) + sets := p.valuesets[pp] + if sets == nil { + sets = map[string]ValueSet{} + p.valuesets[pp] = sets + } + sets[s.Name()] = s + } + vers = "v1" + } + set := descriptor.ValueSetDescriptor{ + ValueSetDefinition: descriptor.ValueSetDefinition{ + ValueTypeDefinition: descriptor.ValueTypeDefinition{ + Name: s.Name(), + Version: vers, + Description: s.Description(), + Format: s.Format(), + }, + CLIOptions: optlist, + }, + Purposes: slices.Clone(s.Purposes()), + } + p.descriptor.ValueSets = append(p.descriptor.ValueSets, set) + for _, pp := range s.Purposes() { + schemes := p.setScheme[pp] + if schemes == nil { + schemes = runtime.MustNewDefaultScheme[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]](&runtime.UnstructuredVersionedTypedObject{}, false, nil) + p.setScheme[pp] = schemes + } + schemes.RegisterByDecoder(s.Name()+"/"+vers, s) + sets := p.valuesets[pp] + if sets == nil { + sets = map[string]ValueSet{} + p.valuesets[pp] = sets + } + sets[s.Name()+"/"+vers] = s + } + return nil +} + +//////////////////////////////////////////////////////////////////////////////// + +func (p *plugin) GetCommand(name string) Command { + return p.clicmds[name] +} + +func (p *plugin) RegisterCommand(c Command) error { + if p.GetCommand(c.Name()) != nil { + return errors.ErrAlreadyExists("cli command spec", c.Name()) + } + if c.Realm() != "" && c.Verb() == "" { + return errors.Newf("realm requires verb") + } + cmd := c.Command() + if cmd.HasSubCommands() && c.Verb() != "" { + return errors.Newf("no sub commands allowd for CLI command for verb") + } + + objtype := c.ObjectType() + if objtype == c.Name() { + objtype = "" + } + p.descriptor.Commands = append(p.descriptor.Commands, descriptor.CommandDescriptor{ + Name: c.Name(), + Description: c.Description(), + Usage: c.Usage(), + Short: c.Short(), + Example: c.Example(), + Realm: c.Realm(), + ObjectType: objtype, + Verb: c.Verb(), + CLIConfigRequired: c.CLIConfigRequired(), + }) + + path := []string{"ocm"} + if c.Verb() != "" { + path = append(path, c.Verb(), c.ObjectType()) + cobrautils.SetCommandSubstitutionForTree(cmd, 3, path) + } else { + cobrautils.SetCommandSubstitutionForTree(cmd, 2, path) + } + + orig := cmd.HelpFunc() + cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { + var err error + // look for arguments of the command. + // wrong args passed to help function, instead + // of the sub command args, the complete command line is + // passed. + _, args, err = cmd.Root().Traverse(args) + if len(args) > 0 && err == nil { + cmd, args, _ = cmd.Find(args) + } + orig(cmd, args) + }) + p.clicmds[c.Name()] = c + return nil +} + +func (p *plugin) Commands() []Command { + return maputils.OrderedValues(p.clicmds) +} + +//////////////////////////////////////////////////////////////////////////////// + +func (p *plugin) GetConfigType(name string) *descriptor.ConfigTypeDescriptor { + var def *descriptor.ConfigTypeDescriptor + for _, d := range p.descriptor.ConfigTypes { + v := d.Name + if d.Version != "" { + v += runtime.VersionSeparator + d.Version + } + if v == name { + return &d + } + if d.Name == name && (def == nil || d.Version == "v1") { + def = generics.Pointer(d) + } + } + return def +} + +func (p *plugin) RegisterConfigType(t config.ConfigType) error { + name := t.GetKind() + version := "" + if t.GetType() != t.GetKind() { + version = t.GetVersion() + } + if f := p.GetConfigType(t.GetType()); f != nil { + if version == f.Version { + return errors.ErrAlreadyExists("config type", t.GetType()) + } + } + + p.descriptor.ConfigTypes = append(p.descriptor.ConfigTypes, descriptor.ConfigTypeDescriptor{ + Name: name, + Version: version, + Description: t.Usage(), + // TODO: separate format and description + }) + return nil +} + +func (p *plugin) ConfigTypes() []descriptor.ConfigTypeDescriptor { + return slices.Clone(p.descriptor.ConfigTypes) +} diff --git a/api/ocm/plugin/ppi/utils.go b/api/ocm/plugin/ppi/utils.go new file mode 100644 index 000000000..27b0885d7 --- /dev/null +++ b/api/ocm/plugin/ppi/utils.go @@ -0,0 +1,145 @@ +package ppi + +import ( + "encoding/json" + "reflect" + + "github.com/pkg/errors" + "golang.org/x/exp/slices" + + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + "ocm.software/ocm/api/utils/runtime" +) + +type decoder runtime.TypedObjectDecoder[runtime.TypedObject] + +type AccessMethodBase struct { + decoder + nameDescription + + version string + format string +} + +func MustNewAccessMethodBase(name, version string, proto AccessSpec, desc string, format string) AccessMethodBase { + decoder, err := runtime.NewDirectDecoder(proto) + if err != nil { + panic(err) + } + + return AccessMethodBase{ + decoder: decoder, + nameDescription: nameDescription{ + name: name, + desc: desc, + }, + version: version, + format: format, + } +} + +func (b *AccessMethodBase) Version() string { + return b.version +} + +func (b *AccessMethodBase) Format() string { + return b.format +} + +//////////////////////////////////////////////////////////////////////////////// + +type UploaderBase = nameDescription + +func MustNewUploaderBase(name, desc string) UploaderBase { + return UploaderBase{ + name: name, + desc: desc, + } +} + +//////////////////////////////////////////////////////////////////////////////// + +type ValueSetBase struct { + decoder + nameDescription + + version string + format string + + purposes []string +} + +func MustNewValueSetBase(name, version string, proto runtime.TypedObject, purposes []string, desc string, format string) ValueSetBase { + decoder, err := runtime.NewDirectDecoder(proto) + if err != nil { + panic(err) + } + return ValueSetBase{ + decoder: decoder, + nameDescription: nameDescription{ + name: name, + desc: desc, + }, + version: version, + format: format, + purposes: slices.Clone(purposes), + } +} + +func (b *ValueSetBase) Version() string { + return b.version +} + +func (b *ValueSetBase) Format() string { + return b.format +} + +func (b *ValueSetBase) Purposes() []string { + return b.purposes +} + +//////////////////////////////////////////////////////////////////////////////// + +type nameDescription struct { + name string + desc string +} + +func (b *nameDescription) Name() string { + return b.name +} + +func (b *nameDescription) Description() string { + return b.desc +} + +//////////////////////////////////////////////////////////////////////////////// + +// Config is a generic structured config stored in a string map. +type Config map[string]interface{} + +func (c Config) GetValue(name string) (interface{}, bool) { + v, ok := c[name] + return v, ok +} + +func (c Config) ConvertFor(list ...options.OptionType) error { + for _, o := range list { + if v, ok := c[o.GetName()]; ok { + t := reflect.TypeOf(o.Create().Value()) + if t != reflect.TypeOf(v) { + data, err := json.Marshal(v) + if err != nil { + return errors.Wrapf(err, "cannot marshal option value for %q", o.GetName()) + } + value := reflect.New(t) + err = json.Unmarshal(data, value.Interface()) + if err != nil { + return errors.Wrapf(err, "cannot unmarshal option value for %q[%s]", o.GetName(), o.ValueType()) + } + c[o.GetName()] = value.Elem().Interface() + } + } + } + return nil +} diff --git a/api/ocm/plugin/registration/logging.go b/api/ocm/plugin/registration/logging.go new file mode 100644 index 000000000..08b211119 --- /dev/null +++ b/api/ocm/plugin/registration/logging.go @@ -0,0 +1,11 @@ +package registration + +import ( + "github.com/mandelsoft/logging" + + "ocm.software/ocm/api/ocm/plugin/descriptor" +) + +func Logger(c logging.ContextProvider, keyValuePairs ...interface{}) logging.Logger { + return c.LoggingContext().Logger(descriptor.REALM).WithValues(keyValuePairs...) +} diff --git a/api/ocm/plugin/registration/registration.go b/api/ocm/plugin/registration/registration.go new file mode 100644 index 000000000..eb8f2da76 --- /dev/null +++ b/api/ocm/plugin/registration/registration.go @@ -0,0 +1,154 @@ +package registration + +import ( + "golang.org/x/exp/slices" + + "ocm.software/ocm/api/config/plugin" + "ocm.software/ocm/api/datacontext/action" + "ocm.software/ocm/api/datacontext/action/handlers" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/cpi" + pluginaccess "ocm.software/ocm/api/ocm/extensions/accessmethods/plugin" + pluginaction "ocm.software/ocm/api/ocm/extensions/actionhandler/plugin" + "ocm.software/ocm/api/ocm/extensions/attrs/plugincacheattr" + pluginupload "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/generic/plugin" + "ocm.software/ocm/api/ocm/extensions/download" + plugindownload "ocm.software/ocm/api/ocm/extensions/download/handlers/plugin" + "ocm.software/ocm/api/ocm/extensions/labels/routingslip/spi" + pluginroutingslip "ocm.software/ocm/api/ocm/extensions/labels/routingslip/types/plugin" + "ocm.software/ocm/api/ocm/plugin/descriptor" + "ocm.software/ocm/api/ocm/valuemergehandler" + pluginmerge "ocm.software/ocm/api/ocm/valuemergehandler/handlers/plugin" + "ocm.software/ocm/api/ocm/valuemergehandler/hpi" + "ocm.software/ocm/api/utils/runtime" +) + +// RegisterExtensions registers all the extension provided by the found plugin. +func RegisterExtensions(ctxp ocm.ContextProvider) error { + ctx := ctxp.OCMContext() + pi := plugincacheattr.Get(ctx) + + logger := Logger(ctx) + vmreg := valuemergehandler.For(ctx) + for _, n := range pi.PluginNames() { + p := pi.Get(n) + if !p.IsValid() { + continue + } + + for _, a := range p.GetDescriptor().Actions { + h, err := pluginaction.New(p, a.Name) + if err != nil { + logger.Error("cannot create action handler for plugin", "plugin", p.Name(), "handler", a.Name) + } else { + for _, s := range a.DefaultSelectors { + err := ctx.AttributesContext().GetActions().Register(h, handlers.ForAction(a.Name), action.Selector(s)) + if err != nil { + logger.LogError(err, "cannot register action handler for plugin", "plugin", p.Name(), "handler", a.Name, "selector", s) + } + } + } + } + + for _, a := range p.GetDescriptor().ValueMergeHandlers { + h, err := pluginmerge.New(p, a.Name) + if err != nil { + logger.Error("cannot create value merge handler for plugin", "plugin", p.Name(), "handler", a.Name) + } else { + vmreg.RegisterHandler(h) + } + } + + for _, m := range p.GetDescriptor().AccessMethods { + name := m.Name + if m.Version != "" { + name = name + runtime.VersionSeparator + m.Version + } + logger.Info("registering access method", + "plugin", p.Name(), + "type", name) + pi.GetContext().AccessMethods().Register(pluginaccess.NewType(name, p, &m)) + } + + for _, m := range p.GetDescriptor().ValueSets { + if !slices.Contains(m.Purposes, descriptor.PURPOSE_ROUTINGSLIP) { + continue + } + name := m.Name + if m.Version != "" { + name = name + runtime.VersionSeparator + m.Version + } + logger.Info("registering routing slip entry type", + "plugin", p.Name(), + "type", name) + spi.For(pi.GetContext()).Register(pluginroutingslip.NewType(name, p, &m)) + } + + if p.IsAutoConfigurationEnabled() { + for _, u := range p.GetDescriptor().Uploaders { + for _, c := range u.Constraints { + if c.ContextType != "" && c.RepositoryType != "" && c.MediaType != "" { + hdlr, err := pluginupload.New(p, u.Name, nil) + if err != nil { + logger.Error("cannot create blob handler for plugin", "plugin", p.Name(), "handler", u.Name) + } else { + logger.Info("registering repository blob handler", + "context", c.ContextType+":"+c.RepositoryType, + "plugin", p.Name(), + "handler", u.Name) + ctx.BlobHandlers().Register(hdlr, cpi.ForRepo(c.ContextType, c.RepositoryType), cpi.ForMimeType(c.MediaType)) + } + } + } + } + + for _, u := range p.GetDescriptor().Downloaders { + for _, c := range u.AutoRegistration { + if c.ArtifactType != "" || c.MediaType != "" { + hdlr, err := plugindownload.New(p, u.Name, nil) + if err != nil { + logger.Error("cannot create download handler for plugin", "plugin", p.Name(), "handler", u.Name) + } else { + logger.Info("registering download handler", + "context", c.ArtifactType+":"+c.MediaType, + "plugin", p.Name(), + "handler", u.Name, + "priority", c.Priority) + opts := &download.HandlerOptions{ + HandlerKey: download.HandlerKey{ + ArtifactType: c.ArtifactType, + MimeType: c.MediaType, + }, + Priority: c.Priority, + } + download.For(ctx).Register(hdlr, opts) + } + } + } + } + } + + for _, s := range p.GetDescriptor().LabelMergeSpecifications { + h := vmreg.GetHandler(s.GetAlgorithm()) + if h == nil { + logger.Error("cannot assign label merge spec for plugin", "label", s.GetName(), "algorithm", s.GetAlgorithm(), "plugin", p.Name()) + } else { + vmreg.AssignHandler(hpi.LabelHint(s.Name, s.Version), &s.MergeAlgorithmSpecification) + } + } + + registry := ctx.ConfigContext().ConfigTypes() + for _, s := range p.GetDescriptor().ConfigTypes { + name := s.Name + if s.Version != "" { + name += runtime.VersionSeparator + s.Version + } + if registry.GetType(name) != nil { + logger.Error("config type {{type}} already registered", "type", name) + } + t := plugin.New(name, s.Description) + registry.Register(t) + } + } + return nil +} diff --git a/pkg/contexts/ocm/plugin/suite_test.go b/api/ocm/plugin/suite_test.go similarity index 100% rename from pkg/contexts/ocm/plugin/suite_test.go rename to api/ocm/plugin/suite_test.go diff --git a/pkg/contexts/ocm/plugin/testdata/action b/api/ocm/plugin/testdata/action similarity index 100% rename from pkg/contexts/ocm/plugin/testdata/action rename to api/ocm/plugin/testdata/action diff --git a/pkg/contexts/ocm/plugin/testdata/identity b/api/ocm/plugin/testdata/identity similarity index 100% rename from pkg/contexts/ocm/plugin/testdata/identity rename to api/ocm/plugin/testdata/identity diff --git a/pkg/contexts/ocm/plugin/testdata/merge b/api/ocm/plugin/testdata/merge similarity index 100% rename from pkg/contexts/ocm/plugin/testdata/merge rename to api/ocm/plugin/testdata/merge diff --git a/pkg/contexts/ocm/plugin/testdata/test b/api/ocm/plugin/testdata/test similarity index 100% rename from pkg/contexts/ocm/plugin/testdata/test rename to api/ocm/plugin/testdata/test diff --git a/api/ocm/plugin/testutils/plugintests.go b/api/ocm/plugin/testutils/plugintests.go new file mode 100644 index 000000000..d61fbb020 --- /dev/null +++ b/api/ocm/plugin/testutils/plugintests.go @@ -0,0 +1,31 @@ +package testutils + +import ( + "github.com/mandelsoft/goutils/testutils" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/attrs/plugincacheattr" + "ocm.software/ocm/api/ocm/extensions/attrs/plugindirattr" + "ocm.software/ocm/api/ocm/plugin/cache" + "ocm.software/ocm/api/ocm/plugin/plugins" +) + +type TempPluginDir = testutils.TempDir + +func ConfigureTestPlugins2(ctx ocm.ContextProvider, path string) (TempPluginDir, plugins.Set, error) { + t, err := ConfigureTestPlugins(ctx, path) + if err != nil { + return nil, nil, err + } + return t, plugincacheattr.Get(ctx), nil +} + +func ConfigureTestPlugins(ctx ocm.ContextProvider, path string) (TempPluginDir, error) { + t, err := testutils.NewTempDir(testutils.WithDirContent(path)) + if err != nil { + return nil, err + } + cache.DirectoryCache.Reset() + plugindirattr.Set(ctx.OCMContext(), t.Path()) + return t, nil +} diff --git a/api/ocm/plugin/utils.go b/api/ocm/plugin/utils.go new file mode 100644 index 000000000..f3f15ae8d --- /dev/null +++ b/api/ocm/plugin/utils.go @@ -0,0 +1,30 @@ +package plugin + +import ( + "encoding/json" + + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/iotools" +) + +type AccessDataWriter struct { + plugin Plugin + creds json.RawMessage + accspec json.RawMessage +} + +func NewAccessDataWriter(p Plugin, creds, accspec json.RawMessage) *AccessDataWriter { + return &AccessDataWriter{p, creds, accspec} +} + +func (d *AccessDataWriter) WriteTo(w accessio.Writer) (int64, digest.Digest, error) { + dw := iotools.NewDefaultDigestWriter(accessio.NopWriteCloser(w)) + err := d.plugin.Get(dw, d.creds, d.accspec) + if err != nil { + return blobaccess.BLOB_UNKNOWN_SIZE, blobaccess.BLOB_UNKNOWN_DIGEST, err + } + return dw.Size(), dw.Digest(), nil +} diff --git a/api/ocm/ref.go b/api/ocm/ref.go new file mode 100644 index 000000000..cf9de6550 --- /dev/null +++ b/api/ocm/ref.go @@ -0,0 +1,317 @@ +package ocm + +import ( + "fmt" + "strings" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/general" + + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/grammar" + common "ocm.software/ocm/api/utils/misc" +) + +const ( + KIND_OCM_REFERENCE = "ocm reference" +) + +// ParseRepo parses a standard ocm repository reference into a internal representation. +func ParseRepo(ref string) (UniformRepositorySpec, error) { + create := false + if strings.HasPrefix(ref, "+") { + create = true + ref = ref[1:] + } + if strings.HasPrefix(ref, ".") || strings.HasPrefix(ref, "/") { + return cpi.HandleRef(UniformRepositorySpec{ + Info: ref, + CreateIfMissing: create, + }) + } + match := grammar.AnchoredRepositoryRegexp.FindSubmatch([]byte(ref)) + if match != nil { + h := string(match[1]) + t, _ := grammar.SplitTypeSpec(h) + return cpi.HandleRef(UniformRepositorySpec{ + Type: t, + TypeHint: h, + Scheme: string(match[2]), + Host: string(match[3]), + SubPath: string(match[4]), + CreateIfMissing: create, + }) + } + + match = grammar.AnchoredSchemedHostPortRepositoryRegexp.FindSubmatch([]byte(ref)) + if match != nil { + h := string(match[1]) + t, _ := grammar.SplitTypeSpec(h) + return cpi.HandleRef(UniformRepositorySpec{ + Type: t, + TypeHint: h, + Scheme: string(match[2]), + Host: string(match[3]), + SubPath: string(match[4]), + CreateIfMissing: create, + }) + } + + match = grammar.AnchoredHostWithPortRepositoryRegexp.FindSubmatch([]byte(ref)) + if match != nil { + h := string(match[1]) + t, _ := grammar.SplitTypeSpec(h) + return cpi.HandleRef(UniformRepositorySpec{ + Type: t, + TypeHint: h, + Scheme: string(match[2]), + Host: string(match[3]), + SubPath: string(match[4]), + CreateIfMissing: create, + }) + } + + match = grammar.AnchoredGenericRepositoryRegexp.FindSubmatch([]byte(ref)) + if match == nil { + return UniformRepositorySpec{}, errors.ErrInvalid(KIND_OCM_REFERENCE, ref) + } + h := string(match[1]) + t, _ := grammar.SplitTypeSpec(h) + return cpi.HandleRef(UniformRepositorySpec{ + Type: t, + TypeHint: h, + Info: string(match[2]), + CreateIfMissing: create, + }) +} + +func ParseRepoToSpec(ctx Context, ref string, create ...bool) (RepositorySpec, error) { + uni, err := ParseRepo(ref) + if err != nil { + return nil, errors.ErrInvalidWrap(err, KIND_REPOSITORYSPEC, ref) + } + if !uni.CreateIfMissing { + uni.CreateIfMissing = general.Optional(create...) + } + repoSpec, err := ctx.MapUniformRepositorySpec(&uni) + if err != nil { + return nil, errors.ErrInvalidWrap(err, KIND_REPOSITORYSPEC, ref) + } + return repoSpec, nil +} + +// RefSpec is a go internal representation of a oci reference. +type RefSpec struct { + UniformRepositorySpec + CompSpec +} + +// ParseRef parses a standard ocm reference into an internal representation. +func ParseRef(ref string) (RefSpec, error) { + create := false + if strings.HasPrefix(ref, "+") { + create = true + ref = ref[1:] + } + + var spec RefSpec + v := "" + match := grammar.AnchoredReferenceRegexp.FindSubmatch([]byte(ref)) + if match != nil { + v = string(match[6]) + s := string(match[2]) + h := string(match[1]) + t, _ := grammar.SplitTypeSpec(h) + spec = RefSpec{ + UniformRepositorySpec{ + Type: t, + TypeHint: h, + Scheme: s, + Host: string(match[3]), + SubPath: string(match[4]), + CreateIfMissing: create, + }, + CompSpec{ + Component: string(match[5]), + Version: nil, + }, + } + } + + if match == nil { + match = grammar.AnchoredSchemedHostPortReferenceRegexp.FindSubmatch([]byte(ref)) + if match != nil { + v = string(match[6]) + s := string(match[2]) + h := string(match[1]) + t, _ := grammar.SplitTypeSpec(h) + spec = RefSpec{ + UniformRepositorySpec{ + Type: t, + TypeHint: h, + Scheme: s, + Host: string(match[3]), + SubPath: string(match[4]), + CreateIfMissing: create, + }, + CompSpec{ + Component: string(match[5]), + Version: nil, + }, + } + } + } + + if match == nil { + match = grammar.AnchoredHostWithPortReferenceRegexp.FindSubmatch([]byte(ref)) + if match != nil { + v = string(match[6]) + s := string(match[2]) + h := string(match[1]) + t, _ := grammar.SplitTypeSpec(h) + spec = RefSpec{ + UniformRepositorySpec{ + Type: t, + TypeHint: h, + Scheme: s, + Host: string(match[3]), + SubPath: string(match[4]), + CreateIfMissing: create, + }, + CompSpec{ + Component: string(match[5]), + Version: nil, + }, + } + } + } + + if match == nil { + match = grammar.AnchoredGenericReferenceRegexp.FindSubmatch([]byte(ref)) + if match == nil { + return RefSpec{}, errors.ErrInvalid(KIND_OCM_REFERENCE, ref) + } + v = string(match[4]) + h := string(match[1]) + t, _ := grammar.SplitTypeSpec(h) + spec = RefSpec{ + UniformRepositorySpec{ + Type: t, + TypeHint: h, + Info: string(match[2]), + CreateIfMissing: create, + }, + CompSpec{ + Component: string(match[3]), + Version: nil, + }, + } + } + + if v != "" { + spec.Version = &v + } + var err error + if spec.Info == "" || !(string(spec.Info[0]) == "{") { + spec.UniformRepositorySpec, err = cpi.HandleRef(spec.UniformRepositorySpec) + } + return spec, err +} + +func (r *RefSpec) Name() string { + if r.SubPath == "" { + return fmt.Sprintf("%s//%s", r.Host, r.Component) + } + return fmt.Sprintf("%s/%s//%s", r.Host, r.SubPath, r.Component) +} + +func (r *RefSpec) HostPort() (string, string) { + i := strings.Index(r.Host, ":") + if i < 0 { + return r.Host, "" + } + return r.Host[:i], r.Host[i+1:] +} + +func (r *RefSpec) Reference() string { + t := r.Type + if t != "" { + t += "::" + } + s := r.SubPath + if s != "" { + s = "/" + s + } + v := "" + if r.Version != nil && *r.Version != "" { + v = ":" + *r.Version + } + return fmt.Sprintf("%s%s%s//%s%s", t, r.Host, s, r.Component, v) +} + +func (r *RefSpec) IsVersion() bool { + return r.Version != nil +} + +func (r *RefSpec) String() string { + return r.Reference() +} + +func (r RefSpec) DeepCopy() RefSpec { + if r.Version != nil { + v := *r.Version + r.Version = &v + } + return r +} + +//////////////////////////////////////////////////////////////////////////////// + +func ParseComp(ref string) (CompSpec, error) { + match := grammar.AnchoredComponentVersionRegexp.FindSubmatch([]byte(ref)) + + if match == nil { + return CompSpec{}, errors.ErrInvalid(KIND_COMPONENTVERSION, ref) + } + + v := string(match[2]) + r := CompSpec{ + Component: string(match[1]), + Version: nil, + } + if v != "" { + r.Version = &v + } + return r, nil +} + +// CompSpec is a go internal representation of a ocm component version name. +type CompSpec struct { + // Component is the component name part of a component version + Component string + // +optional + Version *string +} + +func (r *CompSpec) IsVersion() bool { + return r.Version != nil +} + +func (r *CompSpec) NameVersion() common.NameVersion { + if r.Version != nil { + return common.NewNameVersion(r.Component, *r.Version) + } + return common.NewNameVersion(r.Component, "-") +} + +func (r *CompSpec) Reference() string { + v := "" + if r.Version != nil && *r.Version != "" { + v = ":" + *r.Version + } + return fmt.Sprintf("%s%s", r.Component, v) +} + +func (r *CompSpec) String() string { + return r.Reference() +} diff --git a/api/ocm/ref_test.go b/api/ocm/ref_test.go new file mode 100644 index 000000000..ac35cefa4 --- /dev/null +++ b/api/ocm/ref_test.go @@ -0,0 +1,344 @@ +package ocm_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" + "ocm.software/ocm/api/utils" +) + +func Type(t string) string { + if t == "" { + return t + } + return t + "::" +} + +func FileFormat(t, f string) string { + if t == "" { + return f + } + if f == "" { + return t + } + return t + "+" + f +} + +func FileType(t, f string) string { + if t != "" { + return t + } else { + return f + } +} + +func Scheme(s string) string { + if s == "" { + return s + } + return s + "://" +} + +func Sub(t string) string { + if t == "" { + return t + } + return "/" + t +} + +func Vers(t string) string { + if t == "" { + return t + } + return ":" + t +} + +func CheckRef(ref, ut, scheme, h, us, c, uv, i string, th ...string) { + var v *string + if uv != "" { + v = &uv + } + if len(th) == 0 && ut != "" { + th = []string{ut} + } + spec, err := ocm.ParseRef(ref) + Expect(err).WithOffset(1).To(Succeed()) + Expect(spec).WithOffset(1).To(Equal(ocm.RefSpec{ + UniformRepositorySpec: ocm.UniformRepositorySpec{ + Type: ut, + Scheme: scheme, + Host: h, + SubPath: us, + Info: i, + TypeHint: utils.Optional(th...), + CreateIfMissing: ref[0] == '+', + }, + CompSpec: ocm.CompSpec{ + Component: c, + Version: v, + }, + })) +} + +var _ = Describe("ref parsing", func() { + Context("file path refs", func() { + t := "ctf" + p := "file/path" + c := "github.com/mandelsoft/ocm" + v := "v1" + + Context("[+][::][./][//[:]", func() { + for _, cm := range []string{"", "+"} { + for _, ut := range []string{"", t} { + for _, uf := range []string{"", "directory", "tar", "tgz"} { + for _, up := range []string{p, "./" + p} { + for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + "+65", v + ".1.2-rc.1", v + ".1.2+65"} { + ref := cm + Type(FileFormat(ut, uf)) + up + "//" + c + Vers(uv) + ut, uf, uv, up := ut, uf, uv, up + + // tests parsing of all permutations of + // [+][::][./]//[:] + It("parses ref "+ref, func() { + if ut != "" || uf != "" { + CheckRef(ref, FileType(ut, uf), "", "", "", c, uv, up, FileFormat(ut, uf)) + } else { + CheckRef(ref, FileType(ut, uf), "", "", "", c, uv, up) + } + }) + } + } + } + } + } + }) + }) + + Context("json repo spec refs", func() { + t := ocireg.Type + s := "mandelsoft/cnudie" + v := "v1" + + h := "ghcr.io" + c := "github.com/mandelsoft/ocm" + + repospec := ocireg.NewRepositorySpec(h, &ocireg.ComponentRepositoryMeta{ + ComponentNameMapping: "", + SubPath: s, + }) + jsonrepospec := string(Must(repospec.MarshalJSON())) + + Context("[::][//][:]", func() { + for _, cm := range []string{"", "+"} { + for _, ut := range []string{t, ""} { + for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + "+65", v + ".1.2-rc.1", v + ".1.2+65"} { + ref := cm + Type(ut) + jsonrepospec + "//" + c + Vers(uv) + ut, uv := ut, uv + + // tests parsing of all permutations of + // [::][//][:] + It("parses ref "+ref, func() { + CheckRef(ref, ut, "", "", "", c, uv, jsonrepospec) + }) + } + } + } + }) + + It("fail if mismatch between type in ref (here, ctf) and type in json repo spec (here, OCIRegistry)", func() { + ctx := ocm.New() + + ref := Must(ocm.ParseRef("ctf::{\"baseUrl\":\"ghcr.io\",\"subPath\":\"mandelsoft/cnudie\",\"type\":\"OCIRegistry\"}//github.com/mandelsoft/ocm:v1")) + spec, err := ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec) + Expect(spec).To(BeNil()) + Expect(err).ToNot(BeNil()) + }) + }) + + Context("domain refs", func() { + t := ocireg.Type + s := "mandelsoft/cnudie" + v := "v1" + + h := "ghcr.io" + c := "github.com/mandelsoft/ocm" + + Context("[+][::][://][:][/]//[:::][scheme://][:][/]//[:::]://[:][/]//[:::]scheme://[:][/]//[:::][://]:[/]//[:::][scheme://]:[/]//[: 0 +} + +func (i *LabelSelectorImpl) MatchSource(list accessors.ElementListAccessor, a accessors.SourceAccessor) bool { + return len(GetLabels(a.GetMeta().GetLabels(), i)) > 0 +} + +func (i *LabelSelectorImpl) MatchReference(list accessors.ElementListAccessor, a accessors.ReferenceAccessor) bool { + return len(GetLabels(a.GetMeta().GetLabels(), i)) > 0 +} + +type LabelErrPropSelectorImpl struct { + LabelSelectorImpl +} + +func (l *LabelErrPropSelectorImpl) GetError() error { + if e, ok := l.LabelSelector.(ErrorProvider); ok { + return e.GetError() + } + return nil +} + +type LabelErrorSelectorImpl struct { + ErrorSelectorBase + LabelSelectorImpl +} + +func NewLabelErrorSelectorImpl(s LabelSelector, err error) *LabelErrorSelectorImpl { + return &LabelErrorSelectorImpl{NewErrorSelectorBase(err), LabelSelectorImpl{s}} +} + +//////////////////////////////////////////////////////////////////////////////// + +type label []LabelSelector + +func (s label) MatchLabel(l *v1.Label) bool { + for _, n := range s { + if !n.MatchLabel(l) { + return false + } + } + return true +} + +func (s label) GetError() error { + return ValidateSubSelectors("and", []LabelSelector(s)...) +} + +func Label(sel ...LabelSelector) *LabelErrPropSelectorImpl { + return &LabelErrPropSelectorImpl{LabelSelectorImpl{label(sel)}} +} diff --git a/api/ocm/selectors/labelsel/interface.go b/api/ocm/selectors/labelsel/interface.go new file mode 100644 index 000000000..823b4f97e --- /dev/null +++ b/api/ocm/selectors/labelsel/interface.go @@ -0,0 +1,178 @@ +package labelsel + +import ( + "bytes" + "container/list" + "encoding/json" + "reflect" + + "github.com/mandelsoft/goutils/errors" + "github.com/mikefarah/yq/v4/pkg/yqlib" + "gopkg.in/op/go-logging.v1" + + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/selectors" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" +) + +func init() { + logging.SetLevel(logging.ERROR, "yq-lib") + yqlib.InitExpressionParser() +} + +type Selector = selectors.LabelSelector + +func Select(labels v1.Labels, sel ...Selector) ([]v1.Label, error) { + return selectors.SelectLabels(labels, sel...) +} + +func Get(labels v1.Labels, sel ...Selector) []v1.Label { + return selectors.GetLabels(labels, sel...) +} + +//////////////////////////////////////////////////////////////////////////////// + +type name string + +func (n name) MatchLabel(l *v1.Label) bool { + return string(n) == l.Name +} + +func Name(n string) *selectors.LabelSelectorImpl { + return &selectors.LabelSelectorImpl{name(n)} +} + +//////////////////////////////////////////////////////////////////////////////// + +type version string + +func (n version) MatchLabel(l *v1.Label) bool { + return string(n) == l.Version +} + +func Version(n string) *selectors.LabelSelectorImpl { + return &selectors.LabelSelectorImpl{version(n)} +} + +/////////////////////////////////////////////////////////////////////////////// + +type signed bool + +func (n signed) MatchLabel(l *v1.Label) bool { + return bool(n) == l.Signing +} + +func Signed(b ...bool) *selectors.LabelSelectorImpl { + return &selectors.LabelSelectorImpl{signed(utils.OptionalDefaultedBool(true, b...))} +} + +/////////////////////////////////////////////////////////////////////////////// + +type mergealgo string + +func (n mergealgo) MatchLabel(l *v1.Label) bool { + a := string(n) + if l.Merge == nil { + return a == "" + } + return a == l.Merge.Algorithm +} + +func MergeAlgo(algo string) *selectors.LabelSelectorImpl { + return &selectors.LabelSelectorImpl{mergealgo(algo)} +} + +//////////////////////////////////////////////////////////////////////////////// + +func AsStructure(value interface{}) (interface{}, error) { + var err error + + data, ok := value.([]byte) + if !ok { + data, err = json.Marshal(value) + if err != nil { + return nil, err + } + } + + var v interface{} + err = runtime.DefaultYAMLEncoding.Unmarshal(data, &v) + if err != nil { + return nil, err + } + return v, nil +} + +// Value matches a label by a label value. +// This selector should typically be combined with Name. +func Value(value interface{}) *selectors.LabelErrorSelectorImpl { + data, err := AsStructure(value) + return selectors.NewLabelErrorSelectorImpl(selectors.LabelSelectorFunc(func(l *v1.Label) bool { + var value interface{} + err := json.Unmarshal(l.Value, &value) + if err != nil { + return false + } + return reflect.DeepEqual(value, data) + }), err) +} + +//////////////////////////////////////////////////////////////////////////////// + +func YQParse(data []byte) (*yqlib.CandidateNode, error) { + decoder := yqlib.NewYamlDecoder(yqlib.YamlPreferences{}) + err := decoder.Init(bytes.NewReader(data)) + if err != nil { + return nil, err + } + return decoder.Decode() +} + +type yqeval struct { + expr *yqlib.ExpressionNode + value interface{} +} + +func (v *yqeval) MatchLabel(l *v1.Label) bool { + if v.expr == nil { + return false + } + in, err := YQParse(l.Value) + if err != nil { + return false + } + t := yqlib.NewDataTreeNavigator() + docs := list.New() + docs.PushBack(in) + context, err := t.GetMatchingNodes(yqlib.Context{MatchingNodes: docs}, v.expr) + if err != nil { + return false + } + if context.MatchingNodes.Len() != 1 { + return false + } + data, err := context.MatchingNodes.Front().Value.(*yqlib.CandidateNode).MarshalJSON() + if err != nil { + return false + } + var out interface{} + err = json.Unmarshal(data, &out) + if err != nil { + return false + } + return reflect.DeepEqual(v.value, out) +} + +// YQExpression matches a part of a label values described by a YQ expression. +// If value is a []byte, it is interpreted as JSON data, otherwise the value +// marshalled as JSON. +func YQExpression(expr string, value interface{}) *selectors.LabelErrorSelectorImpl { + var data interface{} + + node, err := yqlib.ExpressionParser.ParseExpression(expr) + if err == nil { + data, err = AsStructure(value) + } + return selectors.NewLabelErrorSelectorImpl(&yqeval{node, data}, errors.Wrapf(err, "YQExpression selector")) +} diff --git a/pkg/contexts/ocm/selectors/labelsel/label_test.go b/api/ocm/selectors/labelsel/label_test.go similarity index 92% rename from pkg/contexts/ocm/selectors/labelsel/label_test.go rename to api/ocm/selectors/labelsel/label_test.go index 1e0dc22dc..feb34c5a0 100644 --- a/pkg/contexts/ocm/selectors/labelsel/label_test.go +++ b/api/ocm/selectors/labelsel/label_test.go @@ -6,12 +6,13 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/labelsel" - "sigs.k8s.io/yaml" "github.com/mikefarah/yq/v4/pkg/yqlib" + "sigs.k8s.io/yaml" + + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/selectors" + "ocm.software/ocm/api/ocm/selectors/labelsel" ) func Parse(data []byte) (*yqlib.CandidateNode, error) { diff --git a/api/ocm/selectors/labelsel/operators.go b/api/ocm/selectors/labelsel/operators.go new file mode 100644 index 000000000..357d43ab4 --- /dev/null +++ b/api/ocm/selectors/labelsel/operators.go @@ -0,0 +1,56 @@ +package labelsel + +import ( + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/selectors" +) + +var ( + _ selectors.ErrorProvider = (or)(nil) + _ selectors.ErrorProvider = (*not)(nil) +) + +//////////////////////////////////////////////////////////////////////////////// + +func And(sel ...Selector) *selectors.LabelErrPropSelectorImpl { + return selectors.Label(sel...) +} + +//////////////////////////////////////////////////////////////////////////////// + +type or []Selector + +func (a or) MatchLabel(l *v1.Label) bool { + for _, o := range a { + if o.MatchLabel(l) { + return true + } + } + return false +} + +func (a or) GetError() error { + return selectors.ValidateSubSelectors("or", []Selector(a)...) +} + +func Or(operands ...Selector) Selector { + return or(operands) +} + +//////////////////////////////////////////////////////////////////////////////// + +type not struct { + sel Selector +} + +func (a *not) MatchLabel(l *v1.Label) bool { + return !a.sel.MatchLabel(l) +} + +func (a *not) GetError() error { + return selectors.ValidateSubSelectors("not", a.sel) +} + +func Not(operand Selector) Selector { + return ¬{operand} +} diff --git a/pkg/contexts/ocm/selectors/labelsel/suite_test.go b/api/ocm/selectors/labelsel/suite_test.go similarity index 100% rename from pkg/contexts/ocm/selectors/labelsel/suite_test.go rename to api/ocm/selectors/labelsel/suite_test.go diff --git a/api/ocm/selectors/refsel/element.go b/api/ocm/selectors/refsel/element.go new file mode 100644 index 000000000..cf77bc7bf --- /dev/null +++ b/api/ocm/selectors/refsel/element.go @@ -0,0 +1,35 @@ +package refsel + +import ( + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/selectors" + "ocm.software/ocm/api/ocm/selectors/labelsel" +) + +// Identity selectors + +func IdentityByKeyPairs(extras ...string) Selector { + return selectors.IdentityByKeyPairs(extras...) +} + +func Identity(id v1.Identity) Selector { + return selectors.Identity(id) +} + +func Name(n string) Selector { + return selectors.Name(n) +} + +func Version(n string) Selector { + return selectors.Version(n) +} + +// Label selectors + +func Label(sel ...selectors.LabelSelector) Selector { + return selectors.Label(sel...) +} + +func LabelName(n string) Selector { + return labelsel.Name(n) +} diff --git a/api/ocm/selectors/refsel/interface.go b/api/ocm/selectors/refsel/interface.go new file mode 100644 index 000000000..8b07319ca --- /dev/null +++ b/api/ocm/selectors/refsel/interface.go @@ -0,0 +1,59 @@ +package refsel + +import ( + "regexp" + + "github.com/gobwas/glob" + + "ocm.software/ocm/api/ocm/selectors" + "ocm.software/ocm/api/ocm/selectors/accessors" +) + +type ( + Selector = selectors.ReferenceSelector + SelectorFunc = selectors.ReferenceSelectorFunc +) + +//////////////////////////////////////////////////////////////////////////////// + +type Component string + +func (c Component) MatchReference(list accessors.ElementListAccessor, ref accessors.ReferenceAccessor) bool { + return string(c) == ref.GetComponentName() +} + +//////////////////////////////////////////////////////////////////////////////// + +type compGlob struct { + glob.Glob +} + +func (c *compGlob) MatchReference(list accessors.ElementListAccessor, ref accessors.ReferenceAccessor) bool { + if c.Glob == nil { + return false + } + return c.Glob.Match(ref.GetComponentName()) +} + +func ComponentGlob(g string) Selector { + c, err := glob.Compile(g, '/') + return selectors.NewReferenceErrorSelectorImpl(&compGlob{c}, err) +} + +//////////////////////////////////////////////////////////////////////////////// + +type compRegEx struct { + *regexp.Regexp +} + +func (c *compRegEx) MatchReference(list accessors.ElementListAccessor, ref accessors.ReferenceAccessor) bool { + if c.Regexp == nil { + return false + } + return c.Regexp.MatchString(ref.GetComponentName()) +} + +func ComponentRegex(g string) selectors.ReferenceSelector { + c, err := regexp.Compile(g) + return selectors.NewReferenceErrorSelectorImpl(&compRegEx{c}, err) +} diff --git a/api/ocm/selectors/refsel/operators.go b/api/ocm/selectors/refsel/operators.go new file mode 100644 index 000000000..fe8364e73 --- /dev/null +++ b/api/ocm/selectors/refsel/operators.go @@ -0,0 +1,72 @@ +package refsel + +import ( + "ocm.software/ocm/api/ocm/selectors" + "ocm.software/ocm/api/ocm/selectors/accessors" +) + +var ( + _ selectors.ErrorProvider = (and)(nil) + _ selectors.ErrorProvider = (or)(nil) + _ selectors.ErrorProvider = (*not)(nil) +) + +//////////////////////////////////////////////////////////////////////////////// + +type and []Selector + +func (a and) MatchReference(list accessors.ElementListAccessor, ref accessors.ReferenceAccessor) bool { + for _, o := range a { + if !o.MatchReference(list, ref) { + return false + } + } + return true +} + +func (a and) GetError() error { + return selectors.ValidateSubSelectors("and", []Selector(a)...) +} + +func And(operands ...Selector) Selector { + return and(operands) +} + +//////////////////////////////////////////////////////////////////////////////// + +type or []Selector + +func (a or) MatchReference(list accessors.ElementListAccessor, ref accessors.ReferenceAccessor) bool { + for _, o := range a { + if o.MatchReference(list, ref) { + return true + } + } + return false +} + +func (a or) GetError() error { + return selectors.ValidateSubSelectors("or", []Selector(a)...) +} + +func Or(operands ...Selector) Selector { + return or(operands) +} + +//////////////////////////////////////////////////////////////////////////////// + +type not struct { + Selector +} + +func (a *not) MatchReference(list accessors.ElementListAccessor, ref accessors.ReferenceAccessor) bool { + return !a.Selector.MatchReference(list, ref) +} + +func (a *not) GetError() error { + return selectors.ValidateSubSelectors("not", a.Selector) +} + +func Not(operand Selector) Selector { + return ¬{operand} +} diff --git a/api/ocm/selectors/rscsel/artifact.go b/api/ocm/selectors/rscsel/artifact.go new file mode 100644 index 000000000..5995e19f6 --- /dev/null +++ b/api/ocm/selectors/rscsel/artifact.go @@ -0,0 +1,15 @@ +package rscsel + +import ( + "ocm.software/ocm/api/ocm/selectors" +) + +// Artifact selectors + +func ArtifactType(n string) Selector { + return selectors.ArtifactType(n) +} + +func AccessKind(n string) Selector { + return selectors.AccessKind(n) +} diff --git a/api/ocm/selectors/rscsel/element.go b/api/ocm/selectors/rscsel/element.go new file mode 100644 index 000000000..28b239147 --- /dev/null +++ b/api/ocm/selectors/rscsel/element.go @@ -0,0 +1,39 @@ +package rscsel + +import ( + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/selectors" + "ocm.software/ocm/api/ocm/selectors/labelsel" +) + +// Identity selectors + +func IdentityByKeyPairs(extras ...string) Selector { + return selectors.IdentityByKeyPairs(extras...) +} + +func Identity(id v1.Identity) Selector { + return selectors.Identity(id) +} + +func Name(n string) Selector { + return selectors.Name(n) +} + +func Version(n string) Selector { + return selectors.Version(n) +} + +func VersionConstraint(expr string) Selector { + return selectors.VersionConstraint(expr) +} + +// Label selectors + +func Label(sel ...selectors.LabelSelector) Selector { + return selectors.Label(sel...) +} + +func LabelName(n string) Selector { + return labelsel.Name(n) +} diff --git a/api/ocm/selectors/rscsel/interface.go b/api/ocm/selectors/rscsel/interface.go new file mode 100644 index 000000000..58647d923 --- /dev/null +++ b/api/ocm/selectors/rscsel/interface.go @@ -0,0 +1,40 @@ +package rscsel + +import ( + "runtime" + + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extraid" + "ocm.software/ocm/api/ocm/selectors" + "ocm.software/ocm/api/ocm/selectors/accessors" +) + +type ( + Selector = selectors.ResourceSelector + SelectorFunc = selectors.ResourceSelectorFunc +) + +//////////////////////////////////////////////////////////////////////////////// + +type Relation v1.ResourceRelation + +func (r Relation) MatchResource(list accessors.ElementListAccessor, res accessors.ResourceAccessor) bool { + return v1.ResourceRelation(r) == res.GetRelation() +} + +var ( + Local = Relation(v1.LocalRelation) + External = Relation(v1.ExternalRelation) +) + +//////////////////////////////////////////////////////////////////////////////// + +func Executable(name string) Selector { + return SelectorFunc(func(list accessors.ElementListAccessor, a accessors.ResourceAccessor) bool { + extra := a.GetMeta().GetExtraIdentity() + return a.GetMeta().GetName() == name && a.GetType() == resourcetypes.EXECUTABLE && extra != nil && + extra[extraid.ExecutableOperatingSystem] == runtime.GOOS && + extra[extraid.ExecutableArchitecture] == runtime.GOARCH + }) +} diff --git a/api/ocm/selectors/rscsel/operators.go b/api/ocm/selectors/rscsel/operators.go new file mode 100644 index 000000000..919c42a7b --- /dev/null +++ b/api/ocm/selectors/rscsel/operators.go @@ -0,0 +1,72 @@ +package rscsel + +import ( + "ocm.software/ocm/api/ocm/selectors" + "ocm.software/ocm/api/ocm/selectors/accessors" +) + +var ( + _ selectors.ErrorProvider = (and)(nil) + _ selectors.ErrorProvider = (or)(nil) + _ selectors.ErrorProvider = (*not)(nil) +) + +//////////////////////////////////////////////////////////////////////////////// + +type and []Selector + +func (a and) MatchResource(list accessors.ElementListAccessor, e accessors.ResourceAccessor) bool { + for _, o := range a { + if !o.MatchResource(list, e) { + return false + } + } + return true +} + +func (a and) GetError() error { + return selectors.ValidateSubSelectors("and", []Selector(a)...) +} + +func And(operands ...Selector) Selector { + return and(operands) +} + +//////////////////////////////////////////////////////////////////////////////// + +type or []Selector + +func (a or) MatchResource(list accessors.ElementListAccessor, e accessors.ResourceAccessor) bool { + for _, o := range a { + if o.MatchResource(list, e) { + return true + } + } + return false +} + +func (a or) GetError() error { + return selectors.ValidateSubSelectors("or", []Selector(a)...) +} + +func Or(operands ...Selector) Selector { + return or(operands) +} + +//////////////////////////////////////////////////////////////////////////////// + +type not struct { + Selector +} + +func (a *not) MatchResource(list accessors.ElementListAccessor, e accessors.ResourceAccessor) bool { + return !a.Selector.MatchResource(list, e) +} + +func (a *not) GetError() error { + return selectors.ValidateSubSelectors("not", a.Selector) +} + +func Not(operand Selector) Selector { + return ¬{operand} +} diff --git a/pkg/contexts/ocm/selectors/select.go b/api/ocm/selectors/select.go similarity index 96% rename from pkg/contexts/ocm/selectors/select.go rename to api/ocm/selectors/select.go index 352530d1e..e125017d2 100644 --- a/pkg/contexts/ocm/selectors/select.go +++ b/api/ocm/selectors/select.go @@ -4,8 +4,8 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/goutils/generics" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/accessors" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/selectors/accessors" ) // ErrorProvider is an optional interface a Selector can offer diff --git a/api/ocm/selectors/srcsel/artifact.go b/api/ocm/selectors/srcsel/artifact.go new file mode 100644 index 000000000..353fcb100 --- /dev/null +++ b/api/ocm/selectors/srcsel/artifact.go @@ -0,0 +1,15 @@ +package srcsel + +import ( + "ocm.software/ocm/api/ocm/selectors" +) + +// Artifact selectors + +func ArtifactType(n string) Selector { + return selectors.ArtifactType(n) +} + +func AccessKind(n string) Selector { + return selectors.AccessKind(n) +} diff --git a/api/ocm/selectors/srcsel/element.go b/api/ocm/selectors/srcsel/element.go new file mode 100644 index 000000000..ff45ea1dd --- /dev/null +++ b/api/ocm/selectors/srcsel/element.go @@ -0,0 +1,39 @@ +package srcsel + +import ( + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/selectors" + "ocm.software/ocm/api/ocm/selectors/labelsel" +) + +// Identity selectors + +func IdentityByKeyPairs(extras ...string) Selector { + return selectors.IdentityByKeyPairs(extras...) +} + +func Identity(id v1.Identity) Selector { + return selectors.Identity(id) +} + +func Name(n string) Selector { + return selectors.Name(n) +} + +func Version(n string) Selector { + return selectors.Version(n) +} + +func VersionConstraint(expr string) Selector { + return selectors.VersionConstraint(expr) +} + +// Label selectors + +func Label(sel ...selectors.LabelSelector) Selector { + return selectors.Label(sel...) +} + +func LabelName(n string) Selector { + return labelsel.Name(n) +} diff --git a/api/ocm/selectors/srcsel/interface.go b/api/ocm/selectors/srcsel/interface.go new file mode 100644 index 000000000..612948482 --- /dev/null +++ b/api/ocm/selectors/srcsel/interface.go @@ -0,0 +1,10 @@ +package srcsel + +import ( + "ocm.software/ocm/api/ocm/selectors" +) + +type ( + Selector = selectors.SourceSelector + SelectorFunc = selectors.SourceSelectorFunc +) diff --git a/api/ocm/selectors/srcsel/operators.go b/api/ocm/selectors/srcsel/operators.go new file mode 100644 index 000000000..4686c49ca --- /dev/null +++ b/api/ocm/selectors/srcsel/operators.go @@ -0,0 +1,72 @@ +package srcsel + +import ( + "ocm.software/ocm/api/ocm/selectors" + "ocm.software/ocm/api/ocm/selectors/accessors" +) + +var ( + _ selectors.ErrorProvider = (and)(nil) + _ selectors.ErrorProvider = (or)(nil) + _ selectors.ErrorProvider = (*not)(nil) +) + +//////////////////////////////////////////////////////////////////////////////// + +type and []Selector + +func (a and) MatchSource(list accessors.ElementListAccessor, e accessors.SourceAccessor) bool { + for _, o := range a { + if !o.MatchSource(list, e) { + return false + } + } + return true +} + +func (a and) GetError() error { + return selectors.ValidateSubSelectors("and", []Selector(a)...) +} + +func And(operands ...Selector) Selector { + return and(operands) +} + +//////////////////////////////////////////////////////////////////////////////// + +type or []Selector + +func (a or) MatchSource(list accessors.ElementListAccessor, e accessors.SourceAccessor) bool { + for _, o := range a { + if o.MatchSource(list, e) { + return true + } + } + return false +} + +func (a or) GetError() error { + return selectors.ValidateSubSelectors("or", []Selector(a)...) +} + +func Or(operands ...Selector) Selector { + return or(operands) +} + +//////////////////////////////////////////////////////////////////////////////// + +type not struct { + Selector +} + +func (a *not) MatchReference(list accessors.ElementListAccessor, e accessors.SourceAccessor) bool { + return !a.Selector.MatchSource(list, e) +} + +func (a *not) GetError() error { + return selectors.ValidateSubSelectors("not", a.Selector) +} + +func Not(operand Selector) Selector { + return ¬{operand} +} diff --git a/api/ocm/session.go b/api/ocm/session.go new file mode 100644 index 000000000..2392780b7 --- /dev/null +++ b/api/ocm/session.go @@ -0,0 +1,283 @@ +package ocm + +import ( + "fmt" + "reflect" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/ocm/internal" + common "ocm.software/ocm/api/utils/misc" +) + +type ComponentContainer interface { + LookupComponent(name string) (ComponentAccess, error) +} +type ComponentVersionContainer interface { + LookupVersion(version string) (ComponentVersionAccess, error) +} + +type EvaluationResult struct { + Ref RefSpec + Repository Repository + Component ComponentAccess + Version ComponentVersionAccess +} + +type Session interface { + datacontext.Session + + Finalize(Finalizer) + LookupRepository(Context, RepositorySpec) (Repository, error) + LookupComponent(ComponentContainer, string) (ComponentAccess, error) + LookupComponentVersion(r ComponentVersionResolver, comp, vers string) (ComponentVersionAccess, error) + GetComponentVersion(ComponentVersionContainer, string) (ComponentVersionAccess, error) + EvaluateRef(ctx Context, ref string) (*EvaluationResult, error) + EvaluateComponentRef(ctx Context, ref string) (*EvaluationResult, error) + EvaluateVersionRef(ctx Context, ref string) (*EvaluationResult, error) + DetermineRepository(ctx Context, ref string) (Repository, UniformRepositorySpec, error) + DetermineRepositoryBySpec(ctx Context, spec *UniformRepositorySpec) (Repository, error) +} + +type session struct { + datacontext.Session + base datacontext.SessionBase + repositories *internal.RepositoryCache + components map[datacontext.ObjectKey]ComponentAccess + versions map[datacontext.ObjectKey]ComponentVersionAccess +} + +var _ Session = (*session)(nil) + +var key = reflect.TypeOf(session{}) + +func NewSession(s datacontext.Session) Session { + return datacontext.GetOrCreateSubSession(s, key, newSession).(Session) +} + +func newSession(s datacontext.SessionBase) datacontext.Session { + return &session{ + Session: s.Session(), + base: s, + repositories: internal.NewRepositoryCache(), + components: map[datacontext.ObjectKey]ComponentAccess{}, + versions: map[datacontext.ObjectKey]ComponentVersionAccess{}, + } +} + +type Finalizer interface { + Finalize() error +} + +type finalizer struct { + finalizer Finalizer +} + +func (f *finalizer) Close() error { + return f.finalizer.Finalize() +} + +func (s *session) Finalize(f Finalizer) { + s.Session.AddCloser(&finalizer{f}) +} + +func (s *session) Close() error { + return s.Session.Close() + // TODO: cleanup cache +} + +func (s *session) LookupRepository(ctx Context, spec RepositorySpec) (Repository, error) { + s.base.Lock() + defer s.base.Unlock() + if s.base.IsClosed() { + return nil, errors.ErrClosed("session") + } + + repo, cached, err := s.repositories.LookupRepository(ctx, spec) + if err != nil { + return nil, err + } + + // The repo's closer function should only be added once with add closer. Otherwise, it would be attempted to close + // an already closed object. Thus, we only want to add the repo's closer function, if it was not already cached + // (and thus, consequently already added to the sessions close). + // Session has to take over responsibility for open repositories for the Repository Cache because the objects + // opened during a session have to be closed in the reverse order they were opened (e.g. components opened based + // on a previously opened repository have to be closed first). + if !cached { + s.base.AddCloser(repo) + } + + return repo, nil +} + +func (s *session) LookupComponent(c ComponentContainer, name string) (ComponentAccess, error) { + key := datacontext.ObjectKey{ + Object: c, + Name: name, + } + s.base.Lock() + defer s.base.Unlock() + if s.base.IsClosed() { + return nil, errors.ErrClosed("session") + } + if ns := s.components[key]; ns != nil { + return ns, nil + } + ns, err := c.LookupComponent(name) + if err != nil { + return nil, err + } + s.components[key] = ns + s.base.AddCloser(ns) + return ns, err +} + +func (s *session) LookupComponentVersion(r ComponentVersionResolver, comp, vers string) (ComponentVersionAccess, error) { + if repo, ok := r.(Repository); ok { + component, err := s.LookupComponent(repo, comp) + if err != nil { + return nil, err + } + return s.GetComponentVersion(component, vers) + } + + key := datacontext.ObjectKey{ + Object: r, + Name: common.NewNameVersion(comp, vers).String(), + } + s.base.Lock() + defer s.base.Unlock() + if s.base.IsClosed() { + return nil, errors.ErrClosed("session") + } + if obj := s.versions[key]; obj != nil { + return obj, nil + } + + obj, err := r.LookupComponentVersion(comp, vers) + if err != nil { + return nil, err + } + + s.versions[key] = obj + s.base.AddCloser(obj) + return obj, err +} + +func (s *session) GetComponentVersion(c ComponentVersionContainer, version string) (ComponentVersionAccess, error) { + if c == nil { + return nil, fmt.Errorf("no container given") + } + key := datacontext.ObjectKey{ + Object: c, + Name: version, + } + s.base.Lock() + defer s.base.Unlock() + if s.base.IsClosed() { + return nil, errors.ErrClosed("session") + } + if obj := s.versions[key]; obj != nil { + return obj, nil + } + obj, err := c.LookupVersion(version) + if err != nil { + return nil, err + } + s.versions[key] = obj + s.base.AddCloser(obj) + return obj, err +} + +func (s *session) EvaluateVersionRef(ctx Context, ref string) (*EvaluationResult, error) { + evaluated, err := s.EvaluateComponentRef(ctx, ref) + if err != nil { + return nil, err + } + versions, err := evaluated.Component.ListVersions() + if err != nil { + return evaluated, errors.Wrapf(err, "%s[%s]: listing versions", ref, evaluated.Ref.Component) + } + if len(versions) != 1 { + return evaluated, errors.Wrapf(err, "%s {%s]: found %d components", ref, evaluated.Ref.Component, len(versions)) + } + evaluated.Version, err = s.GetComponentVersion(evaluated.Component, versions[0]) + if err != nil { + return evaluated, errors.Wrapf(err, "%s {%s:%s]: listing components", ref, evaluated.Ref.Component, versions[0]) + } + evaluated.Ref.Version = &versions[0] + return evaluated, nil +} + +func (s *session) EvaluateComponentRef(ctx Context, ref string) (*EvaluationResult, error) { + evaluated, err := s.EvaluateRef(ctx, ref) + if err != nil { + return evaluated, err + } + if evaluated.Component == nil { + lister := evaluated.Repository.ComponentLister() + if lister == nil { + return evaluated, errors.Newf("%s: no component specified", ref) + } + if n, err := lister.NumComponents(""); n != 1 { + if err != nil { + return evaluated, errors.Wrapf(err, "%s: listing components", ref) + } + // return evaluated, errors.Newf("%s: found %d components", ref, n) + return evaluated, nil // return repo ref + } + list, err := lister.GetComponents("", true) + if err != nil { + return evaluated, errors.Wrapf(err, "%s: listing components", ref) + } + evaluated.Ref.Component = list[0] + evaluated.Component, err = s.LookupComponent(evaluated.Repository, list[0]) + if err != nil { + return evaluated, errors.Wrapf(err, "%s: listing components", ref) + } + } + return evaluated, nil +} + +func (s *session) EvaluateRef(ctx Context, ref string) (*EvaluationResult, error) { + var err error + result := &EvaluationResult{} + result.Ref, err = ParseRef(ref) + if err != nil { + return nil, err + } + + result.Repository, err = s.DetermineRepositoryBySpec(ctx, &result.Ref.UniformRepositorySpec) + if err != nil { + return result, err + } + if result.Ref.Component != "" { + result.Component, err = s.LookupComponent(result.Repository, result.Ref.Component) + if err != nil { + return nil, err + } + if result.Ref.IsVersion() { + result.Version, err = s.GetComponentVersion(result.Component, *result.Ref.Version) + } + } + return result, err +} + +func (s *session) DetermineRepository(ctx Context, ref string) (Repository, UniformRepositorySpec, error) { + spec, err := ParseRepo(ref) + if err != nil { + return nil, spec, err + } + r, err := s.DetermineRepositoryBySpec(ctx, &spec) + return r, spec, err +} + +func (s *session) DetermineRepositoryBySpec(ctx Context, spec *UniformRepositorySpec) (Repository, error) { + rspec, err := ctx.MapUniformRepositorySpec(spec) + if err != nil { + return nil, err + } + return s.LookupRepository(ctx, rspec) +} diff --git a/pkg/contexts/ocm/session_test.go b/api/ocm/session_test.go similarity index 82% rename from pkg/contexts/ocm/session_test.go rename to api/ocm/session_test.go index 15c09d85b..660ee3862 100644 --- a/pkg/contexts/ocm/session_test.go +++ b/api/ocm/session_test.go @@ -7,8 +7,8 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - ocmreg "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/utils" + ocmreg "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" + "ocm.software/ocm/api/utils" ) var TEST_KEY = "test" diff --git a/pkg/contexts/ocm/suite_test.go b/api/ocm/suite_test.go similarity index 100% rename from pkg/contexts/ocm/suite_test.go rename to api/ocm/suite_test.go diff --git a/api/ocm/testhelper/references.go b/api/ocm/testhelper/references.go new file mode 100644 index 000000000..415a74409 --- /dev/null +++ b/api/ocm/testhelper/references.go @@ -0,0 +1,35 @@ +package testhelper + +import ( + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/compdesc/normalizations/jsonv1" + "ocm.software/ocm/api/tech/signing/hasher/sha256" +) + +func CompDigestSpec(d string) *metav1.DigestSpec { + return &metav1.DigestSpec{ + HashAlgorithm: sha256.Algorithm, + NormalisationAlgorithm: jsonv1.Algorithm, + Value: d, + } +} + +func CheckCompRef(cv ocm.ComponentVersionAccess, name string, d *metav1.DigestSpec, offsets ...int) { + o := 1 + for _, a := range offsets { + o += a + } + for _, ref := range cv.GetDescriptor().References { + if ref.Name == name { + ExpectWithOffset(o, ref.Digest).To(Equal(d)) + return + } + } + Fail(fmt.Sprintf("ref %s not found", name), o) +} diff --git a/api/ocm/testhelper/refmgmt.go b/api/ocm/testhelper/refmgmt.go new file mode 100644 index 000000000..d3a224b73 --- /dev/null +++ b/api/ocm/testhelper/refmgmt.go @@ -0,0 +1,12 @@ +package testhelper + +import ( + "github.com/mandelsoft/logging" + + ocmlog "ocm.software/ocm/api/utils/logging" + "ocm.software/ocm/api/utils/refmgmt" +) + +func EnableRefMgmtLog() { + ocmlog.Context().AddRule(logging.NewConditionRule(logging.TraceLevel, refmgmt.ALLOC_REALM)) +} diff --git a/api/ocm/testhelper/resources.go b/api/ocm/testhelper/resources.go new file mode 100644 index 000000000..0d608ad17 --- /dev/null +++ b/api/ocm/testhelper/resources.go @@ -0,0 +1,50 @@ +package testhelper + +import ( + "github.com/mandelsoft/goutils/testutils" + + "ocm.software/ocm/api/helper/builder" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/digester/digesters/blob" + "ocm.software/ocm/api/tech/signing/hasher/sha256" + "ocm.software/ocm/api/utils/mime" +) + +func TextResourceDigestSpec(d string) *metav1.DigestSpec { + return &metav1.DigestSpec{ + HashAlgorithm: sha256.Algorithm, + NormalisationAlgorithm: blob.GenericBlobDigestV1, + Value: d, + } +} + +var Digests = testutils.Substitutions{ + "D_TESTDATA": D_TESTDATA, + "D_OTHERDATA": D_OTHERDATA, +} + +const S_TESTDATA = "testdata" + +const D_TESTDATA = "810ff2fb242a5dee4220f2cb0e6a519891fb67f2f828a6cab4ef8894633b1f50" + +var DS_TESTDATA = TextResourceDigestSpec(D_TESTDATA) + +func TestDataResource(env *builder.Builder, funcs ...func()) { + env.Resource("testdata", "", "PlainText", metav1.LocalRelation, func() { + env.BlobStringData(mime.MIME_TEXT, S_TESTDATA) + env.Configure(funcs...) + }) +} + +const S_OTHERDATA = "otherdata" + +const D_OTHERDATA = "54b8007913ec5a907ca69001d59518acfd106f7b02f892eabf9cae3f8b2414b4" + +var DS_OTHERDATA = TextResourceDigestSpec(D_OTHERDATA) + +func OtherDataResource(env *builder.Builder, funcs ...func()) { + env.Resource("otherdata", "", "PlainText", metav1.LocalRelation, func() { + env.BlobStringData(mime.MIME_TEXT, S_OTHERDATA) + env.Configure(funcs...) + }) +} diff --git a/api/ocm/tools/signing/convenience.go b/api/ocm/tools/signing/convenience.go new file mode 100644 index 000000000..af0abd727 --- /dev/null +++ b/api/ocm/tools/signing/convenience.go @@ -0,0 +1,57 @@ +package signing + +import ( + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" + "ocm.software/ocm/api/tech/signing/handlers/rsa" +) + +func SignComponentVersion(cv ocm.ComponentVersionAccess, name string, optlist ...Option) (*metav1.DigestSpec, error) { + var opts Options + + opts.Eval( + SignatureName(name), + Update(), + Recursive(), + VerifyDigests(), + ) + opts.Eval(optlist...) + + if opts.VerifySignature { + return nil, errors.Newf("impossible verification option set for signing") + } + if opts.Signer == nil { + opts.Signer = signingattr.Get(cv.GetContext()).GetSigner(rsa.Algorithm) + } + err := opts.Complete(cv.GetContext()) + if err != nil { + return nil, errors.Wrapf(err, "inconsistent options for signing") + } + return Apply(nil, nil, cv, &opts) +} + +func VerifyComponentVersion(cv ocm.ComponentVersionAccess, name string, optlist ...Option) (*metav1.DigestSpec, error) { + var opts Options + if len(cv.GetDescriptor().Signatures) == 1 && name == "" { + name = cv.GetDescriptor().Signatures[0].Name + } + + opts.Eval( + VerifyDigests(), + VerifySignature(name), + Recursive(), + ) + opts.Eval(optlist...) + + if opts.Signer != nil { + return nil, errors.Newf("impossible signer option set for verification") + } + err := opts.Complete(cv.GetContext()) + if err != nil { + return nil, errors.Wrapf(err, "inconsistent options for verification") + } + return Apply(nil, nil, cv, &opts) +} diff --git a/pkg/contexts/ocm/signing/deprecated.go b/api/ocm/tools/signing/deprecated.go similarity index 100% rename from pkg/contexts/ocm/signing/deprecated.go rename to api/ocm/tools/signing/deprecated.go diff --git a/pkg/contexts/ocm/signing/digestctx.go b/api/ocm/tools/signing/digestctx.go similarity index 93% rename from pkg/contexts/ocm/signing/digestctx.go rename to api/ocm/tools/signing/digestctx.go index 5fa01a20f..7fd1ffc91 100644 --- a/pkg/contexts/ocm/signing/digestctx.go +++ b/api/ocm/tools/signing/digestctx.go @@ -5,13 +5,13 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "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/signing" - "github.com/open-component-model/ocm/pkg/signing/signutils" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/signutils" + "ocm.software/ocm/api/utils" + common "ocm.software/ocm/api/utils/misc" ) type RootContextInfo struct { diff --git a/pkg/contexts/ocm/signing/handle.go b/api/ocm/tools/signing/handle.go similarity index 97% rename from pkg/contexts/ocm/signing/handle.go rename to api/ocm/tools/signing/handle.go index f7b4723e0..8952558dc 100644 --- a/pkg/contexts/ocm/signing/handle.go +++ b/api/ocm/tools/signing/handle.go @@ -13,14 +13,14 @@ import ( "github.com/mandelsoft/goutils/generics" "github.com/mandelsoft/logging" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/none" - "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/signing" - "github.com/open-component-model/ocm/pkg/signing/signutils" - "github.com/open-component-model/ocm/pkg/signing/tsa" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/none" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/signutils" + "ocm.software/ocm/api/tech/signing/tsa" + common "ocm.software/ocm/api/utils/misc" ) var REALM = logging.NewRealm("signing") diff --git a/api/ocm/tools/signing/handler_test.go b/api/ocm/tools/signing/handler_test.go new file mode 100644 index 000000000..bfe41265c --- /dev/null +++ b/api/ocm/tools/signing/handler_test.go @@ -0,0 +1,66 @@ +package signing_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/ocm" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/repositories/composition" + "ocm.software/ocm/api/ocm/tools/signing" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + rsa_pss "ocm.software/ocm/api/tech/signing/handlers/rsa-pss" + "ocm.software/ocm/api/tech/signing/signutils" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/mime" +) + +var _ = Describe("Simple signing handlers", func() { + ctx := ocm.DefaultContext() + + var cv ocm.ComponentVersionAccess + var pub signutils.GenericPublicKey + var priv signutils.GenericPrivateKey + + BeforeEach(func() { + priv, pub = Must2(rsa.CreateKeyPair()) + }) + + Context("", func() { + BeforeEach(func() { + cv = composition.NewComponentVersion(ctx, COMPONENTA, VERSION) + MustBeSuccessful(cv.SetResourceBlob(ocm.NewResourceMeta("blob", resourcetypes.PLAIN_TEXT, v1.LocalRelation), blobaccess.ForString(mime.MIME_TEXT, "test data"), "", nil)) + }) + + DescribeTable("rsa handlers", func(kind string) { + Must(signing.SignComponentVersion(cv, "signature", signing.PrivateKey("signature", priv))) + Must(signing.VerifyComponentVersion(cv, "signature", signing.PublicKey("signature", pub))) + }, + Entry("rsa", rsa.Algorithm), + Entry("rsapss", rsa_pss.Algorithm), + ) + }) + + Context("non-unique resources", func() { + BeforeEach(func() { + cv = composition.NewComponentVersion(ctx, COMPONENTA, VERSION) + + meta := ocm.NewResourceMeta("blob", resourcetypes.PLAIN_TEXT, v1.LocalRelation) + meta.Version = "v1" + meta.ExtraIdentity = map[string]string{} + MustBeSuccessful(cv.SetResourceBlob(meta, blobaccess.ForString(mime.MIME_TEXT, "test data"), "", nil)) + meta.Version = "v2" + MustBeSuccessful(cv.SetResourceBlob(meta, blobaccess.ForString(mime.MIME_TEXT, "other test data"), "", nil, ocm.TargetIndex(-1))) + }) + + It("signs without modification", func() { + Must(signing.SignComponentVersion(cv, "signature", signing.PrivateKey("signature", priv))) + cd := cv.GetDescriptor() + Expect(len(cd.Resources)).To(Equal(2)) + Expect(len(cd.Resources[0].ExtraIdentity)).To(Equal(0)) + Expect(len(cd.Resources[1].ExtraIdentity)).To(Equal(0)) + }) + }) +}) diff --git a/api/ocm/tools/signing/options.go b/api/ocm/tools/signing/options.go new file mode 100644 index 000000000..ba735785e --- /dev/null +++ b/api/ocm/tools/signing/options.go @@ -0,0 +1,753 @@ +package signing + +import ( + "crypto/x509/pkix" + "fmt" + "strings" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/generics" + + "ocm.software/ocm/api/datacontext/attrs/rootcertsattr" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/hasher/sha256" + "ocm.software/ocm/api/tech/signing/signutils" + "ocm.software/ocm/api/utils" + common "ocm.software/ocm/api/utils/misc" +) + +type Option interface { + ApplySigningOption(o *Options) +} + +//////////////////////////////////////////////////////////////////////////////// + +type printer struct { + printer common.Printer +} + +// Printer provides an option configuring a printer for a signing/verification +// operation. +func Printer(p common.Printer) Option { + return &printer{p} +} + +func (o *printer) ApplySigningOption(opts *Options) { + opts.Printer = o.printer +} + +//////////////////////////////////////////////////////////////////////////////// + +const ( + DIGESTMODE_LOCAL = "local" // (default) store nested digests locally in component descriptor + DIGESTMODE_TOP = "top" // store aggregated nested digests in signed component version +) + +type digestmode struct { + mode string +} + +// DigestMode provides an option configuring the digest mode for a signing/verification +// operation. Possible values are +// - DIGESTMODE_LOCAL(default) all digest information is store along with a component version +// - DIGESTMODE_TOP (experimental) all digest information is gathered for referenced component versions in the initially signed component version. +func DigestMode(name string) Option { + return &digestmode{name} +} + +func (o *digestmode) ApplySigningOption(opts *Options) { + opts.DigestMode = o.mode +} + +//////////////////////////////////////////////////////////////////////////////// + +type recursive struct { + flag bool +} + +// Recursive provides an option configuring recursion for a signing/verification +// operation. If enabled the operation will be done for all component versions +// in the reference graph. +func Recursive(flags ...bool) Option { + return &recursive{utils.GetOptionFlag(flags...)} +} + +func (o *recursive) ApplySigningOption(opts *Options) { + opts.Recursively = o.flag +} + +//////////////////////////////////////////////////////////////////////////////// + +type update struct { + flag bool +} + +// Update provides an option configuring the update mode for a signing/verification +// operation. Only if enabled, state changes will be persisted. +func Update(flags ...bool) Option { + return &update{utils.GetOptionFlag(flags...)} +} + +func (o *update) ApplySigningOption(opts *Options) { + opts.Update = o.flag +} + +//////////////////////////////////////////////////////////////////////////////// + +type verify struct { + flag bool +} + +// VerifyDigests provides an option requesting signature verification for a +// signing/verification operation. +func VerifyDigests(flags ...bool) Option { + return &verify{utils.GetOptionFlag(flags...)} +} + +func (o *verify) ApplySigningOption(opts *Options) { + opts.Verify = o.flag +} + +//////////////////////////////////////////////////////////////////////////////// + +type signer struct { + algo string + signer signing.Signer + name string +} + +// Sign provides an option requesting signing for a dedicated name and signer for a +// signing operation. +func Sign(h signing.Signer, name string) Option { + return &signer{"", h, name} +} + +// Signer provides an option requesting to use a dedicated signer for a +// signing/verification operation. +func Signer(h signing.Signer) Option { + return &signer{"", h, ""} +} + +// SignByAlgo provides an option requesting signing with a signing algorithm +// for a signing operation. The effective signer is taken from +// the signer registry provided by the OCM context. +func SignByAlgo(algo string, name string) Option { + return &signer{algo, nil, name} +} + +// SignerByAlgo provides an option requesting to use a dedicated signer by +// algorithm for a signing operation. The effective signer is taken from +// the signer registry provided by the OCM context. +func SignerByAlgo(algo string) Option { + return &signer{algo, nil, ""} +} + +func (o *signer) ApplySigningOption(opts *Options) { + n := strings.TrimSpace(o.name) + if n != "" { + opts.SignatureNames = append([]string{n}, opts.SignatureNames...) + } + opts.SignAlgo = o.algo + opts.Signer = o.signer +} + +//////////////////////////////////////////////////////////////////////////////// + +type hasher struct { + algo string + hasher signing.Hasher +} + +// Hash provides an option requesting hashing with a dedicated hasher for a +// signing/hash operation. +func Hash(h signing.Hasher) Option { + return &hasher{"", h} +} + +// HashByAlgo provides an option requesting to use a dedicated hasher by name +// for a signing/hash operation. The effective hasher is taken from +// the hasher registry provided by the OCM context. +func HashByAlgo(algo string) Option { + return &hasher{algo, nil} +} + +func (o *hasher) ApplySigningOption(opts *Options) { + opts.HashAlgo = o.algo + opts.Hasher = o.hasher +} + +//////////////////////////////////////////////////////////////////////////////// + +type verifier struct { + name string +} + +// VerifySignature provides an option requesting verification for dedicated +// signature names for a signing/verification operation. If no name is specified +// the names are taken from the component version. +func VerifySignature(names ...string) Option { + name := "" + for _, n := range names { + n = strings.TrimSpace(n) + if n != "" { + name = n + break + } + } + return &verifier{name} +} + +func (o *verifier) ApplySigningOption(opts *Options) { + opts.VerifySignature = true + if o.name != "" { + opts.SignatureNames = append(opts.SignatureNames, o.name) + } +} + +//////////////////////////////////////////////////////////////////////////////// + +type resolver struct { + resolver []ocm.ComponentVersionResolver +} + +// Resolver provides an option requesting to use a dedicated component version +// resolver for a signing/verification operation. It is used to resolve +// references in component versions. +func Resolver(h ...ocm.ComponentVersionResolver) Option { + return &resolver{h} +} + +func (o *resolver) ApplySigningOption(opts *Options) { + opts.Resolver = ocm.NewCompoundResolver(append([]ocm.ComponentVersionResolver{opts.Resolver}, o.resolver...)...) +} + +//////////////////////////////////////////////////////////////////////////////// + +type skip struct { + skip map[string]bool +} + +// SkipAccessTypes provides an option to declare dedicated resource types +// which should be excluded from digesting. This is a legacy options, +// required only for the handling of older component version not yet +// completely configured with resource digests. The content of resources with +// the given types will be marked as not signature relevant. +func SkipAccessTypes(names ...string) Option { + m := map[string]bool{} + for _, n := range names { + m[n] = true + } + return &skip{m} +} + +func (o *skip) ApplySigningOption(opts *Options) { + if len(o.skip) > 0 { + if opts.SkipAccessTypes == nil { + opts.SkipAccessTypes = map[string]bool{} + } + for k, v := range o.skip { + opts.SkipAccessTypes[k] = v + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +type registry struct { + registry signing.Registry +} + +// Registry provides an option requesting to use a dedicated signing registry +// for a signing/verification operation. It is used to lookup +// signers, verifiers, hashers and signing public/private keys by name. +func Registry(h signing.Registry) Option { + return ®istry{h} +} + +func (o *registry) ApplySigningOption(opts *Options) { + opts.Registry = o.registry +} + +//////////////////////////////////////////////////////////////////////////////// + +type signame struct { + name string + reset bool +} + +// SignatureName provides an option requesting to use dedicated signature names +// for a signing/verification operation. +func SignatureName(name string, reset ...bool) Option { + return &signame{name, utils.Optional(reset...)} +} + +func (o *signame) ApplySigningOption(opts *Options) { + if o.reset { + opts.SignatureNames = nil + } + if o.name != "" { + opts.SignatureNames = append(opts.SignatureNames, o.name) + } +} + +//////////////////////////////////////////////////////////////////////////////// + +type issuer struct { + issuer pkix.Name + name string + err error +} + +// Issuer provides an option requesting to use a dedicated issuer name +// for a signing operation. +func Issuer(is string) Option { + dn, err := signutils.ParseDN(is) + if err != nil { + return &issuer{err: err} + } + return &issuer{issuer: *dn} +} + +func IssuerFor(name string, is string) Option { + dn, err := signutils.ParseDN(is) + if err != nil { + return &issuer{err: err} + } + return PKIXIssuerFor(name, *dn) +} + +// PKIXIssuer provides an option requesting to use a dedicated issuer name +// for a signing operation. +func PKIXIssuer(is pkix.Name) Option { + return &issuer{issuer: is} +} + +func PKIXIssuerFor(name string, is pkix.Name) Option { + return &issuer{issuer: is, name: name} +} + +func (o *issuer) ApplySigningOption(opts *Options) { + if o.name != "" { + if opts.Keys == nil { + opts.Keys = signing.NewKeyRegistry() + } + opts.Keys.RegisterIssuer(o.name, generics.Pointer(o.issuer)) + } else { + opts.Issuer = generics.Pointer(o.issuer) + } +} + +//////////////////////////////////////////////////////////////////////////////// + +type rootcerts struct { + pool signutils.GenericCertificatePool +} + +// RootCertificates provides an option requesting to dedicated root certificates +// for a signing/verification operation using certificates. +func RootCertificates(pool signutils.GenericCertificatePool) Option { + return &rootcerts{pool} +} + +func (o *rootcerts) ApplySigningOption(opts *Options) { + opts.RootCerts = o.pool +} + +//////////////////////////////////////////////////////////////////////////////// + +type privkey struct { + name string + key interface{} +} + +// PrivateKey provides an option requesting to use a dedicated private key +// for a dedicated signature name for a signing operation. +func PrivateKey(name string, key interface{}) Option { + return &privkey{name, key} +} + +func (o *privkey) ApplySigningOption(opts *Options) { + if o.key == nil { + return + } + if opts.Keys == nil { + opts.Keys = signing.NewKeyRegistry() + } + opts.Keys.RegisterPrivateKey(o.name, o.key) +} + +//////////////////////////////////////////////////////////////////////////////// + +type pubkey struct { + name string + key interface{} +} + +// PublicKey provides an option requesting to use a dedicated public key +// for a dedicated signature name for a verification operation. +func PublicKey(name string, key interface{}) Option { + return &pubkey{name, key} +} + +func (o *pubkey) ApplySigningOption(opts *Options) { + if o.key == nil { + return + } + if opts.Keys == nil { + opts.Keys = signing.NewKeyRegistry() + } + opts.Keys.RegisterPublicKey(o.name, o.key) +} + +//////////////////////////////////////////////////////////////////////////////// + +type tsaOpt struct { + url string + use *bool +} + +// UseTSA enables the usage of a timestamp server authority. +func UseTSA(flag ...bool) Option { + return &tsaOpt{use: utils.BoolP(utils.GetOptionFlag(flag...))} +} + +// TSAUrl selects the TSA server URL to use, if TSA mode is enabled. +func TSAUrl(url string) Option { + return &tsaOpt{url: url} +} + +func (o *tsaOpt) ApplySigningOption(opts *Options) { + if o.url != "" { + opts.TSAUrl = o.url + } + if o.use != nil { + opts.UseTSA = *o.use + } +} + +//////////////////////////////////////////////////////////////////////////////// + +type Options struct { + Printer common.Printer + Update bool + Recursively bool + DigestMode string + Verify bool + SignAlgo string + Signer signing.Signer + Issuer *pkix.Name + VerifySignature bool + RootCerts signutils.GenericCertificatePool + HashAlgo string + Hasher signing.Hasher + Keys signing.KeyRegistry + Registry signing.Registry + Resolver ocm.ComponentVersionResolver + SkipAccessTypes map[string]bool + SignatureNames []string + NormalizationAlgo string + Keyless bool + TSAUrl string + UseTSA bool + + effectiveRegistry signing.Registry +} + +var _ Option = (*Options)(nil) + +func NewOptions(list ...Option) *Options { + return (&Options{}).Eval(list...) +} + +func (opts *Options) Eval(list ...Option) *Options { + for _, o := range list { + o.ApplySigningOption(opts) + } + return opts +} + +func (o *Options) ApplySigningOption(opts *Options) { + if o.Printer != nil { + opts.Printer = o.Printer + } + if o.Keys != nil { + opts.Keys = o.Keys + } + if o.Signer != nil { + opts.Signer = o.Signer + } + if o.DigestMode != "" { + opts.DigestMode = o.DigestMode + } + if o.VerifySignature { + opts.VerifySignature = o.VerifySignature + } + if o.Hasher != nil { + opts.Hasher = o.Hasher + } + if o.Registry != nil { + opts.Registry = o.Registry + } + if o.Resolver != nil { + opts.Resolver = o.Resolver + } + if len(o.SignatureNames) != 0 { + opts.SignatureNames = o.SignatureNames + } + if o.SkipAccessTypes != nil { + if opts.SkipAccessTypes == nil { + opts.SkipAccessTypes = map[string]bool{} + } + for k, v := range o.SkipAccessTypes { + opts.SkipAccessTypes[k] = v + } + } + if o.Issuer != nil { + opts.Issuer = o.Issuer + } + opts.Recursively = o.Recursively + opts.Update = o.Update + opts.Verify = o.Verify + opts.Keyless = o.Keyless + if o.NormalizationAlgo != "" { + opts.NormalizationAlgo = o.NormalizationAlgo + } + if o.TSAUrl != "" { + opts.TSAUrl = o.TSAUrl + } + if o.UseTSA { + opts.UseTSA = o.UseTSA + } +} + +// Complete takes either nil, an ocm.ContextProvider or a signing.Registry. +// To be compatible with an older version the type has been changed to interface +// to support multiple variants. +func (o *Options) Complete(ctx interface{}) error { + var reg signing.Registry + + if ctx == nil { + ctx = ocm.DefaultContext() + } + + var ocmctx ocm.Context + + switch t := ctx.(type) { + case ocm.ContextProvider: + ocmctx = t.OCMContext() + reg = signingattr.Get(ocmctx) + case signing.Registry: + reg = t + ocmctx = ocm.DefaultContext() + default: + return fmt.Errorf("context argument (%T) is invalid", ctx) + } + + o.Printer = common.AssurePrinter(o.Printer) + + if o.Registry == nil { + o.Registry = reg + } + + o.effectiveRegistry = o.Registry + if o.Keys != nil && (o.Keys.HasKeys() || o.Keys.HasIssuers()) { + o.effectiveRegistry = signing.RegistryWithPreferredKeys(o.Registry, o.Keys) + } + + certs := rootcertsattr.Get(ocmctx) + if o.RootCerts == nil && certs.HasRootCertificates() { + o.RootCerts = certs.GetRootCertPool(true) + } + + if o.RootCerts != nil { + // check root certificates + pool, err := signutils.GetCertPool(o.RootCerts, false) + if err != nil { + return err + } + o.RootCerts = pool + } + + if o.SkipAccessTypes == nil { + o.SkipAccessTypes = map[string]bool{} + } + + if o.Signer == nil && o.SignAlgo != "" { + o.Signer = o.Registry.GetSigner(o.SignAlgo) + if o.Signer == nil { + return errors.ErrUnknown(compdesc.KIND_SIGN_ALGORITHM, o.SignAlgo) + } + } + if o.Signer != nil { + if len(o.SignatureNames) == 0 { + return errors.Newf("signature name required for signing") + } + priv, err := o.PrivateKey() + if err != nil { + return err + } + if priv == nil && !o.Keyless { + return errors.ErrNotFound(compdesc.KIND_PRIVATE_KEY, o.SignatureNames[0]) + } + if o.DigestMode == "" { + o.DigestMode = DIGESTMODE_LOCAL + } + } + if !o.Keyless { + if o.Signer != nil && !o.VerifySignature { + if pub := o.PublicKey(o.SignatureName()); pub != nil { + o.VerifySignature = true + if err := o.checkCert(pub, o.IssuerFor(o.SignatureName())); err != nil { + return fmt.Errorf("public key not valid: %w", err) + } + } + } else if o.VerifySignature { + for _, n := range o.SignatureNames { + pub := o.PublicKey(n) + // don't check for public key here, anymore, + // because the key might be provided via certificate together with + // the signature. An early failure is therefore not possible anymore. + if pub != nil { + if err := o.checkCert(pub, o.IssuerFor(n)); err != nil { + return fmt.Errorf("public key not valid: %w", err) + } + } + } + } + } + if o.NormalizationAlgo == "" { + o.NormalizationAlgo = compdesc.JsonNormalisationV1 + } + + if o.Hasher == nil && o.HashAlgo != "" { + o.Hasher = o.Registry.GetHasher(o.HashAlgo) + if o.Hasher == nil { + return errors.ErrUnknown(compdesc.KIND_HASH_ALGORITHM, o.HashAlgo) + } + } + if o.Hasher == nil { + o.Hasher = o.Registry.GetHasher(sha256.Algorithm) + } + return nil +} + +func (o *Options) checkCert(data interface{}, name *pkix.Name) error { + cert, pool, err := signutils.GetCertificate(data, false) + if err != nil { + return nil + } + err = signing.VerifyCertDN(pool, o.RootCerts, name, cert) + if err != nil { + if name != nil { + return errors.Wrapf(err, "issuer [%s]", name) + } + return err + } + return nil +} + +func (o *Options) DoUpdate() bool { + return o.Update || o.DoSign() +} + +func (o *Options) DoSign() bool { + return o.Signer != nil && len(o.SignatureNames) > 0 +} + +func (o *Options) StoreLocally() bool { + return o.DigestMode == DIGESTMODE_LOCAL +} + +func (o *Options) DoVerify() bool { + return o.VerifySignature +} + +func (o *Options) SignatureName() string { + if len(o.SignatureNames) > 0 { + return o.SignatureNames[0] + } + return "" +} + +func (o *Options) GetIssuer() *pkix.Name { + if o.Issuer != nil { + return o.Issuer + } + if o.effectiveRegistry != nil { + return o.effectiveRegistry.GetIssuer(o.SignatureName()) + } + return nil +} + +func (o *Options) IssuerFor(name string) *pkix.Name { + if o.Issuer != nil && name == o.SignatureName() { + return o.Issuer + } + if o.effectiveRegistry != nil { + return o.effectiveRegistry.GetIssuer(name) + } + return nil +} + +func (o *Options) SignatureConfigured(name string) bool { + for _, n := range o.SignatureNames { + if n == name { + return true + } + } + return false +} + +func (o *Options) PublicKey(sig string) signutils.GenericPublicKey { + return o.effectiveRegistry.GetPublicKey(sig) +} + +func (o *Options) PrivateKey() (signutils.GenericPrivateKey, error) { + return signing.ResolvePrivateKey(o.effectiveRegistry, o.SignatureName()) +} + +func (o *Options) EffectiveTSAUrl() string { + if o.UseTSA { + if o.TSAUrl != "" { + return o.TSAUrl + } + return o.effectiveRegistry.TSAUrl() + } + return "" +} + +func (o *Options) Dup() *Options { + opts := *o + return &opts +} + +func (o *Options) Nested() *Options { + opts := o.Dup() + opts.VerifySignature = false // TODO: may be we want a mode to verify signature if present + if !opts.Recursively { + opts.Update = opts.DoUpdate() && opts.DigestMode == DIGESTMODE_LOCAL + opts.Signer = nil + } + opts.Printer = opts.Printer.AddGap(" ") + return opts +} + +func (o *Options) StopRecursion() *Options { + opts := *o + opts.Recursively = false + opts.Signer = nil + opts.Update = false + return &opts +} + +func (o *Options) WithDigestMode(mode string) *Options { + if mode == "" || o.DigestMode == mode { + return o + } + opts := *o + opts.DigestMode = mode + return &opts +} diff --git a/api/ocm/tools/signing/options_test.go b/api/ocm/tools/signing/options_test.go new file mode 100644 index 000000000..45a19bdd1 --- /dev/null +++ b/api/ocm/tools/signing/options_test.go @@ -0,0 +1,98 @@ +package signing_test + +import ( + "crypto/x509" + "crypto/x509/pkix" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/ocm/tools/signing" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/api/tech/signing/signutils" +) + +const NAME = "mandelsoft" + +var _ = Describe("options", func() { + defer GinkgoRecover() + + capriv, capub, err := rsa.Handler{}.CreateKeyPair() + Expect(err).To(Succeed()) + + spec := &signutils.Specification{ + RootCAs: nil, + IsCA: true, + PublicKey: capub, + CAPrivateKey: capriv, + CAChain: nil, + Subject: pkix.Name{ + CommonName: "ca-authority", + }, + Usages: signutils.Usages{x509.ExtKeyUsageCodeSigning}, + Validity: 10 * time.Hour, + NotBefore: nil, + } + + ca, _, err := signutils.CreateCertificate(spec) + Expect(err).To(Succeed()) + + priv, pub, err := rsa.Handler{}.CreateKeyPair() + Expect(err).To(Succeed()) + + spec.Subject = pkix.Name{ + CommonName: NAME, + StreetAddress: []string{"some street 21"}, + } + spec.RootCAs = ca + spec.CAChain = ca + spec.PublicKey = pub + spec.IsCA = false + + cert, _, err := signutils.CreateCertificate(spec) + Expect(err).To(Succeed()) + + pool := x509.NewCertPool() + pool.AddCert(ca) + + It("verifies options for verification", func() { + opts := NewOptions( + RootCertificates(pool), + VerifySignature(NAME), + PrivateKey(NAME, priv), + PublicKey(NAME, cert), + ) + Expect(opts.Complete(ocm.DefaultContext())).To(Succeed()) + }) + + It("fails for options for verification without root cert", func() { + opts := NewOptions( + VerifySignature(NAME), + PrivateKey(NAME, priv), + PublicKey(NAME, cert), + ) + Expect(opts.Complete(ocm.DefaultContext())).To(HaveOccurred()) + }) + + It("succeeds for options for signing with verification with root cert", func() { + opts := NewOptions( + RootCertificates(pool), + Sign(signing.DefaultRegistry().GetSigner(rsa.Algorithm), NAME), + PrivateKey(NAME, priv), + PublicKey(NAME, cert), + ) + Expect(opts.Complete(ocm.DefaultContext())).To(Succeed()) + }) + + It("fails for options for signing with verification without root cert", func() { + opts := NewOptions( + Sign(signing.DefaultRegistry().GetSigner(rsa.Algorithm), NAME), + PrivateKey(NAME, priv), + PublicKey(NAME, cert), + ) + Expect(opts.Complete(ocm.DefaultContext())).To(HaveOccurred()) + }) +}) diff --git a/api/ocm/tools/signing/signing_test.go b/api/ocm/tools/signing/signing_test.go new file mode 100644 index 000000000..1fcb2a901 --- /dev/null +++ b/api/ocm/tools/signing/signing_test.go @@ -0,0 +1,1430 @@ +package signing_test + +import ( + "crypto/x509/pkix" + "fmt" + "time" + + . "github.com/mandelsoft/goutils/finalizer" + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + . "ocm.software/ocm/api/oci/testhelper" + . "ocm.software/ocm/api/ocm/testhelper" + . "ocm.software/ocm/api/ocm/tools/signing" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/none" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/attrs/compositionmodeattr" + "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" + "ocm.software/ocm/api/ocm/extensions/repositories/composition" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/tools/signing/signingtest" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/api/tech/signing/hasher/sha256" + "ocm.software/ocm/api/tech/signing/hasher/sha512" + "ocm.software/ocm/api/tech/signing/signutils" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/mime" + common "ocm.software/ocm/api/utils/misc" +) + +var DefaultContext = ocm.New() + +const ( + ARCH = "/tmp/ctf" + PROVIDER = "mandelsoft" + VERSION = "v1" + COMPONENTA = "github.com/mandelsoft/test" + COMPONENTB = "github.com/mandelsoft/ref" + COMPONENTC = "github.com/mandelsoft/ref2" + COMPONENTD = "github.com/mandelsoft/top" + OUT = "/tmp/res" + OCIPATH = "/tmp/oci" + OCIHOST = "alias" +) + +const ( + SIGNATURE = "test" + SIGNATURE2 = "second" + SIGN_ALGO = rsa.Algorithm +) + +var _ = Describe("access method", func() { + var env *Builder + + BeforeEach(func() { + env = NewBuilder(signingtest.ModifiableTestData()) + env.RSAKeyPair(SIGNATURE, SIGNATURE2) + }) + + AfterEach(func() { + env.Cleanup() + }) + + /* TODO: add complex example from component cli + Context("compatibility", func() { + It("verifies older hash types (sha256[digest type] instead of SHA-256(crypto type))", func() { + session := datacontext.NewSession() + defer session.Close() + + env.ReadRSAKeyPair(SIGNATURE, "/testdata/compat") + cv, err := comparch.Open(env.OCMContext(), accessobj.ACC_READONLY, "/testdata/compat/component-archive", 0, env) + Expect(err).To(Succeed()) + session.AddCloser(cv) + opts := NewOptions( + VerifySignature(SIGNATURE), + VerifyDigests(), + ) + Expect(opts.Complete(signingattr.Get(DefaultContext))).To(Succeed()) + + dig, err := Apply(nil, nil, cv, opts) + Expect(err).To(Succeed()) + Expect(dig.Value).To(Equal("b06e4c1a68274b876661f9fbf1f100526d289745f6ee847bfef702007b5b14cf")) + Expect(dig.HashAlgorithm).To(Equal(sha256.Algorithm)) + }) + It("resigns with older hash types", func() { + session := datacontext.NewSession() + defer session.Close() + + env.ReadRSAKeyPair(SIGNATURE, "/testdata/compat") + cv, err := comparch.Open(env.OCMContext(), accessobj.ACC_READONLY, "/testdata/compat/component-archive", 0, env) + Expect(err).To(Succeed()) + session.AddCloser(cv) + opts := NewOptions( + Sign(signing.DefaultHandlerRegistry().GetSigner(SIGN_ALGO), SIGNATURE), + VerifySignature(SIGNATURE), + VerifyDigests(), + ) + Expect(opts.Complete(signingattr.Get(DefaultContext))).To(Succeed()) + + dig, err := Apply(nil, nil, cv, opts) + Expect(err).To(Succeed()) + Expect(dig.Value).To(Equal("b06e4c1a68274b876661f9fbf1f100526d289745f6ee847bfef702007b5b14cf")) + Expect(dig.HashAlgorithm).To(Equal(sha256.Algorithm)) + }) + }) + */ + + Context("special cases", func() { + DescribeTable("handles none access", func(mode string) { + env.ModificationOptions(ocm.SkipDigest()) + env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { + env.Component(COMPONENTA, func() { + env.Version(VERSION, func() { + env.Provider(PROVIDER) + TestDataResource(env) + env.Resource("value", "", resourcetypes.OCI_IMAGE, metav1.LocalRelation, func() { + env.Access( + none.New(), + ) + }) + }) + }) + }) + + session := datacontext.NewSession() + defer session.Close() + + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) + archcloser := session.AddCloser(src) + resolver := ocm.NewCompoundResolver(src) + + cv := Must(resolver.LookupComponentVersion(COMPONENTA, VERSION)) + closer := session.AddCloser(cv) + + digest := "123d48879559d16965a54eba9a3e845709770f4f0be984ec8db2f507aa78f338" + + pr, buf := common.NewBufferedPrinter() + // key taken from signing attr + dig := Must(SignComponentVersion(cv, SIGNATURE, SignerByAlgo(SIGN_ALGO), Resolver(resolver), DigestMode(mode), Printer(pr))) + Expect(closer.Close()).To(Succeed()) + Expect(archcloser.Close()).To(Succeed()) + Expect(dig.Value).To(StringEqualWithContext(digest)) + + src = Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) + session.AddCloser(src) + cv = Must(src.LookupComponentVersion(COMPONENTA, VERSION)) + session.AddCloser(cv) + Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(digest)) + + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v1]... + resource 0: "name"="testdata": digest SHA-256:${D_TESTDATA}[genericBlobDigest/v1] +`, Digests)) + + CheckResourceDigests(cv.GetDescriptor(), map[string]*metav1.DigestSpec{ + "testdata": DS_TESTDATA, + }) + //////// + + dig = Must(VerifyComponentVersion(cv, SIGNATURE, Resolver(resolver), Printer(pr))) + Expect(dig.Value).To(Equal(digest)) + }, + Entry(DIGESTMODE_TOP, DIGESTMODE_TOP), + Entry(DIGESTMODE_LOCAL, DIGESTMODE_LOCAL), + ) + }) + + Context("valid", func() { + digestA := "01de99400030e8336020059a435cea4e7fe8f21aad4faf619da882134b85569d" + digestB := "5f416ec59629d6af91287e2ba13c6360339b6a0acf624af2abd2a810ce4aefce" + + localDigests := Substitutions{ + "D_COMPA": digestA, + "D_COMPB": digestB, + } + BeforeEach(func() { + FakeOCIRepo(env, OCIPATH, OCIHOST) + + env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { + OCIManifest1(env) + OCIManifest2(env) + }) + + env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { + env.Component(COMPONENTA, func() { + env.Version(VERSION, func() { + env.Provider(PROVIDER) + TestDataResource(env) + env.Resource("value", "", resourcetypes.OCI_IMAGE, metav1.LocalRelation, func() { + env.Access( + ociartifact.New(oci.StandardOCIRef(OCIHOST+".alias", OCINAMESPACE, OCIVERSION)), + ) + env.Label("transportByValue", true) + }) + env.Resource("ref", "", resourcetypes.OCI_IMAGE, metav1.LocalRelation, func() { + env.Access( + ociartifact.New(oci.StandardOCIRef(OCIHOST+".alias", OCINAMESPACE2, OCIVERSION)), + ) + }) + }) + }) + env.Component(COMPONENTB, func() { + env.Version(VERSION, func() { + env.Provider(PROVIDER) + OtherDataResource(env) + env.Reference("ref", COMPONENTA, VERSION) + }) + }) + }) + }) + + DescribeTable("sign flat version", func(mode string) { + session := datacontext.NewSession() + defer session.Close() + + src, err := ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env) + Expect(err).To(Succeed()) + archcloser := session.AddCloser(src) + resolver := ocm.NewCompoundResolver(src) + + cv, err := resolver.LookupComponentVersion(COMPONENTA, VERSION) + Expect(err).To(Succeed()) + closer := session.AddCloser(cv) + + opts := NewOptions( + Sign(signingattr.Get(env.OCMContext()).GetSigner(SIGN_ALGO), SIGNATURE), + Resolver(resolver), + Update(), VerifyDigests(), + ) + Expect(opts.Complete(env)).To(Succeed()) + + pr, buf := common.NewBufferedPrinter() + dig, err := Apply(pr, nil, cv, opts) + Expect(err).To(Succeed()) + Expect(closer.Close()).To(Succeed()) + Expect(archcloser.Close()).To(Succeed()) + Expect(dig.Value).To(StringEqualWithContext(digestA)) + + src, err = ctf.Open(env, accessobj.ACC_READONLY, ARCH, 0, env) + Expect(err).To(Succeed()) + session.AddCloser(src) + cv, err = src.LookupComponentVersion(COMPONENTA, VERSION) + Expect(err).To(Succeed()) + session.AddCloser(cv) + Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(digestA)) + + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v1]... + resource 0: "name"="testdata": digest SHA-256:${D_TESTDATA}[genericBlobDigest/v1] + resource 1: "name"="value": digest SHA-256:${D_OCIMANIFEST1}[ociArtifactDigest/v1] + resource 2: "name"="ref": digest SHA-256:${D_OCIMANIFEST2}[ociArtifactDigest/v1] +`, Digests, OCIDigests)) + //////// + + opts = NewOptions( + DigestMode(mode), + VerifySignature(SIGNATURE), + Resolver(resolver), + Update(), VerifyDigests(), + ) + Expect(opts.Complete(env)).To(Succeed()) + + dig, err = Apply(nil, nil, cv, opts) + Expect(err).To(Succeed()) + Expect(dig.Value).To(Equal(digestA)) + + CheckResourceDigests(cv.GetDescriptor(), map[string]*metav1.DigestSpec{ + "testdata": DS_TESTDATA, + "value": DS_OCIMANIFEST1, + "ref": DS_OCIMANIFEST2, + }) + + cv.GetDescriptor().Resources[0].Digest.Value = "010ff2fb242a5dee4220f2cb0e6a519891fb67f2f828a6cab4ef8894633b1f50" // some wrong value + _, err = Apply(nil, nil, cv, opts) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(StringEqualWithContext("github.com/mandelsoft/test:v1: calculated resource digest (SHA-256:" + D_TESTDATA + "[genericBlobDigest/v1]) mismatches existing digest (SHA-256:010ff2fb242a5dee4220f2cb0e6a519891fb67f2f828a6cab4ef8894633b1f50[genericBlobDigest/v1]) for testdata:v1 (Local blob sha256:810ff2fb242a5dee4220f2cb0e6a519891fb67f2f828a6cab4ef8894633b1f50[])")) + // Reset to original to avoid write back in readonly mode + cv.GetDescriptor().Resources[0].Digest.Value = D_TESTDATA + + cv.GetDescriptor().Signatures[0].Digest.Value = "0ae7ab0c1578d1292922b2a3884833c380a57df2cc7dfab7213ee051b092edc3" + _, err = Apply(nil, nil, cv, opts) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(StringEqualTrimmedWithContext("github.com/mandelsoft/test:v1: signature digest (0ae7ab0c1578d1292922b2a3884833c380a57df2cc7dfab7213ee051b092edc3) does not match found digest (" + digestA + ")")) + // Reset to original to avoid write back in readonly mode + cv.GetDescriptor().Signatures[0].Digest.Value = digestA + }, + Entry(DIGESTMODE_TOP, DIGESTMODE_TOP), + Entry(DIGESTMODE_LOCAL, DIGESTMODE_LOCAL), + ) + + DescribeTable("sign flat version with generic verification", func(mode string) { + session := datacontext.NewSession() + defer session.Close() + + src, err := ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env) + Expect(err).To(Succeed()) + archcloser := session.AddCloser(src) + resolver := ocm.NewCompoundResolver(src) + + cv, err := resolver.LookupComponentVersion(COMPONENTA, VERSION) + Expect(err).To(Succeed()) + closer := session.AddCloser(cv) + + opts := NewOptions( + DigestMode(mode), + Sign(signingattr.Get(env.OCMContext()).GetSigner(SIGN_ALGO), SIGNATURE), + Resolver(resolver), + Update(), VerifyDigests(), + ) + Expect(opts.Complete(env)).To(Succeed()) + dig, err := Apply(nil, nil, cv, opts) + Expect(err).To(Succeed()) + closer.Close() + archcloser.Close() + Expect(dig.Value).To(StringEqualWithContext(digestA)) + + src, err = ctf.Open(env, accessobj.ACC_READONLY, ARCH, 0, env) + Expect(err).To(Succeed()) + session.AddCloser(src) + cv, err = src.LookupComponentVersion(COMPONENTA, VERSION) + Expect(err).To(Succeed()) + session.AddCloser(cv) + Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(digestA)) + + //////// + + opts = NewOptions( + VerifySignature(), + Resolver(resolver), + Update(), VerifyDigests(), + ) + Expect(opts.Complete(env)).To(Succeed()) + + dig, err = Apply(nil, nil, cv, opts) + Expect(err).To(Succeed()) + Expect(dig.Value).To(Equal(digestA)) + }, + Entry(DIGESTMODE_TOP, DIGESTMODE_TOP), + Entry(DIGESTMODE_LOCAL, DIGESTMODE_LOCAL), + ) + + DescribeTable("sign deep version", func(mode string) { + session := datacontext.NewSession() + defer session.Close() + + src, err := ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env) + Expect(err).To(Succeed()) + archcloser := session.AddCloser(src) + resolver := ocm.NewCompoundResolver(src) + + cv, err := resolver.LookupComponentVersion(COMPONENTB, VERSION) + Expect(err).To(Succeed()) + closer := session.AddCloser(cv) + + opts := NewOptions( + DigestMode(mode), + Sign(signingattr.Get(env.OCMContext()).GetSigner(SIGN_ALGO), SIGNATURE), + Resolver(resolver), + Update(), VerifyDigests(), + ) + Expect(opts.Complete(env)).To(Succeed()) + + pr, buf := common.NewBufferedPrinter() + dig, err := Apply(pr, nil, cv, opts) + Expect(err).To(Succeed()) + closer.Close() + archcloser.Close() + Expect(dig.Value).To(StringEqualWithContext(digestB)) + + src, err = ctf.Open(env, accessobj.ACC_READONLY, ARCH, 0, env) + Expect(err).To(Succeed()) + session.AddCloser(src) + cv, err = src.LookupComponentVersion(COMPONENTB, VERSION) + Expect(err).To(Succeed()) + session.AddCloser(cv) + Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(digestB)) + + if mode == DIGESTMODE_TOP { + Expect(cv.GetDescriptor().NestedDigests.String()).To(StringEqualTrimmedWithContext(` +github.com/mandelsoft/test:v1: SHA-256:${D_COMPA}[jsonNormalisation/v1] + testdata:v1[]: SHA-256:${D_TESTDATA}[genericBlobDigest/v1] + value:v1[]: SHA-256:${D_OCIMANIFEST1}[ociArtifactDigest/v1] + ref:v1[]: SHA-256:${D_OCIMANIFEST2}[ociArtifactDigest/v1] +`, localDigests, Digests, OCIDigests)) + } else { + Expect(cv.GetDescriptor().NestedDigests).To(BeNil()) + } + + cva, err := src.LookupComponentVersion(COMPONENTA, VERSION) + Expect(err).To(Succeed()) + session.AddCloser(cva) + Expect(len(cva.GetDescriptor().Signatures)).To(Equal(0)) + + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +applying to version "github.com/mandelsoft/ref:v1"[github.com/mandelsoft/ref:v1]... + no digest found for "github.com/mandelsoft/test:v1" + applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/ref:v1]... + resource 0: "name"="testdata": digest SHA-256:${D_TESTDATA}[genericBlobDigest/v1] + resource 1: "name"="value": digest SHA-256:${D_OCIMANIFEST1}[ociArtifactDigest/v1] + resource 2: "name"="ref": digest SHA-256:${D_OCIMANIFEST2}[ociArtifactDigest/v1] + reference 0: github.com/mandelsoft/test:v1: digest SHA-256:${D_COMPA}[jsonNormalisation/v1] + resource 0: "name"="otherdata": digest SHA-256:${D_OTHERDATA}[genericBlobDigest/v1] +`, localDigests, Digests, OCIDigests)) + //////// + + opts = NewOptions( + VerifySignature(SIGNATURE), + Resolver(src), + VerifyDigests(), + ) + Expect(opts.Complete(env)).To(Succeed()) + + dig, err = Apply(nil, nil, cv, opts) + Expect(err).To(Succeed()) + Expect(dig.Value).To(Equal(digestB)) + }, + Entry(DIGESTMODE_TOP, DIGESTMODE_TOP), + Entry(DIGESTMODE_LOCAL, DIGESTMODE_LOCAL), + ) + + DescribeTable("fails generic verification", func(mode string) { + session := datacontext.NewSession() + defer session.Close() + + src, err := ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env) + Expect(err).To(Succeed()) + session.AddCloser(src) + resolver := ocm.NewCompoundResolver(src) + + cv, err := resolver.LookupComponentVersion(COMPONENTA, VERSION) + Expect(err).To(Succeed()) + session.AddCloser(cv) + + opts := NewOptions( + DigestMode(mode), + VerifySignature(), + Resolver(resolver), + Update(), VerifyDigests(), + ) + Expect(opts.Complete(env)).To(Succeed()) + + _, err = Apply(nil, nil, cv, opts) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(StringEqualWithContext("github.com/mandelsoft/test:v1: failed to determine signature info: no signature found")) + }, + Entry(DIGESTMODE_TOP, DIGESTMODE_TOP), + Entry(DIGESTMODE_LOCAL, DIGESTMODE_LOCAL), + ) + }) + + Context("invalid", func() { + BeforeEach(func() { + env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { + env.Component(COMPONENTB, func() { + env.Version(VERSION, func() { + env.Provider(PROVIDER) + OtherDataResource(env) + env.Reference("ref", COMPONENTA, VERSION) + }) + }) + }) + }) + + DescribeTable("fails signing version with unknown ref", func(mode string) { + session := datacontext.NewSession() + defer session.Close() + + src, err := ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env) + Expect(err).To(Succeed()) + session.AddCloser(src) + + opts := NewOptions( + DigestMode(mode), + Sign(signingattr.Get(env.OCMContext()).GetSigner(SIGN_ALGO), SIGNATURE), + Resolver(src), + Update(), VerifyDigests(), + ) + Expect(opts.Complete(env)).To(Succeed()) + + cv, err := src.LookupComponentVersion(COMPONENTB, VERSION) + Expect(err).To(Succeed()) + session.AddCloser(cv) + + _, err = Apply(nil, nil, cv, opts) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(StringEqualWithContext("github.com/mandelsoft/ref:v1: failed resolving component reference ref[github.com/mandelsoft/test:v1]: component version \"github.com/mandelsoft/test:v1\" not found: oci artifact \"v1\" not found in component-descriptors/github.com/mandelsoft/test")) + }, + Entry(DIGESTMODE_TOP, DIGESTMODE_TOP), + Entry(DIGESTMODE_LOCAL, DIGESTMODE_LOCAL), + ) + }) + + Context("legacy rhombus", func() { + D_DATAA := "8a835d52867572bdaf7da7fb35ee59ad45c3db2dacdeeca62178edd5d07ef08c" + D_DATAA512 := "47e63fa783ec370d83b84ce3de37a3de3fdd5cdc64d4fb21a530dce00fa1011e8dbc85f7509694a5875bf82e710ce00ac6bcd8e716741a7fc4c51a181b741920" + D_DATAB := "5f103fcedc97b81bfc1841447d164781ed0f6244ce20b26d7a8a7d5880156c33" + D_DATAB512 := "a9469fc2e9787c8496cf1526508ae86d4e855715ef6b8f7031bdc55759683762f1c330b94a4516dff23e32f19fb170cbcb53015f1ffc0d77624ee5c9a288a030" + D_DATAC := "90e06e32c46338db42d78d49fee035063d4b10e83cfbf0d1831e14527245da12" + D_DATAD := "5a5c3f681c2af10d682926a635a1dc9dfe7087d4fa3daf329bf0acad540911a9" + + DS_DATAA := TextResourceDigestSpec(D_DATAA) + DS_DATAB := TextResourceDigestSpec(D_DATAB) + DS_DATAC := TextResourceDigestSpec(D_DATAC) + DS_DATAD := TextResourceDigestSpec(D_DATAD) + + D_COMPA := "bdb62ce8299f10e230b91bc9a9bfdbf2d33147f205fcf736d802c7e1cec7b5e8" + D_COMPA512 := "7aa760f27b494814e56c44413afd7bc9d932df28918d63bea222be4bd2b6abd921225cca2140d6eb549418a75b8db2a32be1852012d77474657505f0ea57b34d" + // D_COMPA_HASHED := "0bf5d019bab058a392b6bcb2ae50c93a02f623da0a439b1bbbfd4b1f795fbd3aafe271e3b757fad06e9118f74b18c2b83c7443f86e0c04c4539196bad79c6380" + D_COMPB := "d1def1b60cc8b241451b0e3fccb705a9d99db188b72ec4548519017921700857" + D_COMPB512 := "08366761127c791e550d2082e34e68c8836739c68f018f969a46a17a6c13b529390303335ee0ae3cd938af9e0f31665427a1b45360622d864a5dbe053917a75d" + // D_COMPBR := "e47deeca35bc34116770a50a88954a0b028eb4e236d089b84e419c6d7ce15d97" + D_COMPC := "b376a7b440c0b1e506e54a790966119a8e229cf9226980b84c628d77ef06fc58" + D_COMPD := "64674d3e2843d36c603f44477e4cd66ee85fe1a91227bbcd271202429024ed61" + + localDigests := Substitutions{ + "D_DATAA": D_DATAA, + "D_DATAB": D_DATAB, + "D_DATAB512": D_DATAB512, + "D_DATAC": D_DATAC, + "D_DATAD": D_DATAD, + + "D_COMPA": D_COMPA, + "D_COMPB": D_COMPB, + // "D_COMPBR": D_COMPB, + "D_COMPC": D_COMPC, + "D_COMPD": D_COMPD, + "D_COMPB512": D_COMPB512, + } + + _, _, _, _ = DS_DATAA, DS_DATAB, DS_DATAC, DS_DATAD + + setup := func(opts ...ocm.ModificationOption) { + env.ModificationOptions(opts...) + env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { + env.Component(COMPONENTA, func() { + env.Version(VERSION, func() { + env.Provider(PROVIDER) + env.Resource("data_a", "", "PlainText", metav1.LocalRelation, func() { + env.BlobStringData(mime.MIME_TEXT, "testdata A") + }) + }) + }) + env.Component(COMPONENTB, func() { + env.Version(VERSION, func() { + env.Provider(PROVIDER) + env.Resource("data_b", "", "PlainText", metav1.LocalRelation, func() { + env.BlobStringData(mime.MIME_TEXT, "testdata B") + }) + env.Reference("ref", COMPONENTA, VERSION) + }) + }) + env.Component(COMPONENTC, func() { + env.Version(VERSION, func() { + env.Provider(PROVIDER) + env.Resource("data_c", "", "PlainText", metav1.LocalRelation, func() { + env.BlobStringData(mime.MIME_TEXT, "testdata C") + }) + env.Reference("ref", COMPONENTA, VERSION) + }) + }) + env.Component(COMPONENTD, func() { + env.Version(VERSION, func() { + env.Provider(PROVIDER) + env.Resource("data_d", "", "PlainText", metav1.LocalRelation, func() { + env.BlobStringData(mime.MIME_TEXT, "testdata D") + }) + env.Reference("refb", COMPONENTB, VERSION) + env.Reference("refc", COMPONENTC, VERSION) + }) + }) + }) + } + + DescribeTable("hashes unsigned", func(mode bool, c EntryCheck, mopts ...ocm.ModificationOption) { + var finalizer Finalizer + defer Defer(finalizer.Finalize) + + { + compositionmodeattr.Set(env.OCMContext(), mode) + setup(mopts...) + arch := finalizer.Nested() + src, err := ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env) + Expect(err).To(Succeed()) + arch.Close(src) + + resolver := ocm.NewCompoundResolver(src) + + log := HashComponent(resolver, COMPONENTD, D_COMPD, DigestMode(c.Mode())) + + Expect(log).To(StringEqualTrimmedWithContext(` +applying to version "github.com/mandelsoft/top:v1"[github.com/mandelsoft/top:v1]... + no digest found for "github.com/mandelsoft/ref:v1" + applying to version "github.com/mandelsoft/ref:v1"[github.com/mandelsoft/top:v1]... + no digest found for "github.com/mandelsoft/test:v1" + applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/top:v1]... + resource 0: "name"="data_a": digest SHA-256:${D_DATAA}[genericBlobDigest/v1] + reference 0: github.com/mandelsoft/test:v1: digest SHA-256:${D_COMPA}[jsonNormalisation/v1] + resource 0: "name"="data_b": digest SHA-256:${D_DATAB}[genericBlobDigest/v1] + reference 0: github.com/mandelsoft/ref:v1: digest SHA-256:${D_COMPB}[jsonNormalisation/v1] + no digest found for "github.com/mandelsoft/ref2:v1" + applying to version "github.com/mandelsoft/ref2:v1"[github.com/mandelsoft/top:v1]... + reference 0: github.com/mandelsoft/test:v1: digest SHA-256:${D_COMPA}[jsonNormalisation/v1] + resource 0: "name"="data_c": digest SHA-256:${D_DATAC}[genericBlobDigest/v1] + reference 1: github.com/mandelsoft/ref2:v1: digest SHA-256:${D_COMPC}[jsonNormalisation/v1] + resource 0: "name"="data_d": digest SHA-256:${D_DATAD}[genericBlobDigest/v1] +`, localDigests)) + MustBeSuccessful(arch.Finalize()) + } + + src, err := ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env) + Expect(err).To(Succeed()) + finalizer.Close(src) + cv, err := src.LookupComponentVersion(COMPONENTD, VERSION) + Expect(err).To(Succeed()) + finalizer.Close(cv) + Expect(len(cv.GetDescriptor().Signatures)).To(Equal(0)) + + c.Check1CheckD(cv, localDigests) + + Expect(cv.GetDescriptor().Resources[0].Digest).NotTo(BeNil()) + Expect(cv.GetDescriptor().Resources[0].Digest.String()).To(Equal("SHA-256:" + D_DATAD + "[genericBlobDigest/v1]")) + + cva, err := src.LookupComponentVersion(COMPONENTA, VERSION) + Expect(err).To(Succeed()) + sub := finalizer.Nested() + sub.Close(cva) + Expect(len(cva.GetDescriptor().Signatures)).To(Equal(0)) + + c.Check1CheckA(cva, DS_DATAA, mopts...) + + //////// + + VerifyHashes(src, COMPONENTD, D_COMPD) + + c.Check1Corrupt(cva, sub, cv) + + opts := NewOptions( + Resolver(src), + VerifyDigests(), + ) + Expect(opts.Complete(env)).To(Succeed()) + _, err = Apply(nil, nil, cv, opts) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("github.com/mandelsoft/top:v1: failed applying to component reference refb[github.com/mandelsoft/ref:v1]: github.com/mandelsoft/top:v1->github.com/mandelsoft/ref:v1: failed applying to component reference ref[github.com/mandelsoft/test:v1]: github.com/mandelsoft/top:v1->github.com/mandelsoft/ref:v1->github.com/mandelsoft/test:v1: calculated resource digest (SHA-256:" + D_DATAA + "[genericBlobDigest/v1]) mismatches existing digest (SHA-256:" + wrongDigest + "[genericBlobDigest/v1]) for data_a:v1 (Local blob sha256:" + D_DATAA + "[])")) + }, + Entry(DIGESTMODE_TOP, false, &EntryTop{}), + Entry(DIGESTMODE_LOCAL, false, &EntryLocal{}), + + Entry("legacy "+DIGESTMODE_TOP, false, &EntryTop{}, ocm.SkipDigest()), + Entry("legacy "+DIGESTMODE_LOCAL, false, &EntryLocal{}, ocm.SkipDigest()), + + Entry(DIGESTMODE_TOP+" with composition mode", true, &EntryTop{}), + Entry(DIGESTMODE_LOCAL+" with composition mode", true, &EntryLocal{}), + + Entry("legacy "+DIGESTMODE_TOP+" with composition mode", true, &EntryTop{}, ocm.SkipDigest()), + Entry("legacy "+DIGESTMODE_LOCAL+" with composition mode", true, &EntryLocal{}, ocm.SkipDigest()), + ) + + DescribeTable("signs unsigned", func(c EntryCheck, mopts ...ocm.ModificationOption) { + var finalizer Finalizer + defer Defer(finalizer.Finalize) + + { + setup(mopts...) + arch := finalizer.Nested() + src, err := ctf.Open(env, accessobj.ACC_WRITABLE, ARCH, 0, env) + Expect(err).To(Succeed()) + arch.Close(src) + + resolver := ocm.NewCompoundResolver(src) + + log := SignComponent(resolver, SIGNATURE, COMPONENTD, D_COMPD, DigestMode(c.Mode())) + + Expect(log).To(StringEqualTrimmedWithContext(` +applying to version "github.com/mandelsoft/top:v1"[github.com/mandelsoft/top:v1]... + no digest found for "github.com/mandelsoft/ref:v1" + applying to version "github.com/mandelsoft/ref:v1"[github.com/mandelsoft/top:v1]... + no digest found for "github.com/mandelsoft/test:v1" + applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/top:v1]... + resource 0: "name"="data_a": digest SHA-256:${D_DATAA}[genericBlobDigest/v1] + reference 0: github.com/mandelsoft/test:v1: digest SHA-256:${D_COMPA}[jsonNormalisation/v1] + resource 0: "name"="data_b": digest SHA-256:${D_DATAB}[genericBlobDigest/v1] + reference 0: github.com/mandelsoft/ref:v1: digest SHA-256:${D_COMPB}[jsonNormalisation/v1] + no digest found for "github.com/mandelsoft/ref2:v1" + applying to version "github.com/mandelsoft/ref2:v1"[github.com/mandelsoft/top:v1]... + reference 0: github.com/mandelsoft/test:v1: digest SHA-256:${D_COMPA}[jsonNormalisation/v1] + resource 0: "name"="data_c": digest SHA-256:${D_DATAC}[genericBlobDigest/v1] + reference 1: github.com/mandelsoft/ref2:v1: digest SHA-256:${D_COMPC}[jsonNormalisation/v1] + resource 0: "name"="data_d": digest SHA-256:${D_DATAD}[genericBlobDigest/v1] +`, localDigests)) + MustBeSuccessful(arch.Finalize()) + } + src, err := ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env) + Expect(err).To(Succeed()) + finalizer.Close(src) + cv, err := src.LookupComponentVersion(COMPONENTD, VERSION) + Expect(err).To(Succeed()) + finalizer.Close(cv) + Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(D_COMPD)) + + c.Check1CheckD(cv, localDigests) + c.Check2Ref(cv, "refb", D_COMPB) + c.Check2Ref(cv, "refc", D_COMPC) + + cva, err := src.LookupComponentVersion(COMPONENTA, VERSION) + Expect(err).To(Succeed()) + finalizer.Close(cva) + Expect(len(cva.GetDescriptor().Signatures)).To(Equal(0)) + + c.Check1CheckA(cva, DS_DATAA, mopts...) + //////// + + cvb := Must(src.LookupComponentVersion(COMPONENTB, VERSION)) + finalizer.Close(cvb) + Expect(len(cvb.GetDescriptor().Signatures)).To(Equal(0)) + c.Check2Ref(cvb, "ref", D_COMPA) + + cvc := Must(src.LookupComponentVersion(COMPONENTB, VERSION)) + finalizer.Close(cvc) + Expect(len(cvb.GetDescriptor().Signatures)).To(Equal(0)) + c.Check2Ref(cvb, "ref", D_COMPA) + + VerifyComponent(src, SIGNATURE, COMPONENTD, D_COMPD) + }, + Entry(DIGESTMODE_TOP, &EntryTop{}), + Entry(DIGESTMODE_LOCAL, &EntryLocal{}), + + Entry("legacy "+DIGESTMODE_TOP, &EntryTop{}, ocm.SkipDigest()), + Entry("legacy "+DIGESTMODE_LOCAL, &EntryLocal{}, ocm.SkipDigest()), + ) + + // D_COMPD_LEGACY := "342d30317bee13ec30d815122f23b19d9ee54a15ff8be1ec550c8072d5a6dba6" + // D_COMPB_HASHED := "af8a8324b7848fc5887c63e632402df99c729669889b4d2ae7efceb9f1c2341b81d8a18b82e994564d854422a544c3dffc7d64d8389c90ab7fad19a50bb75e31" + D_COMPB_HASHED := "6ef2fa650b73302f2f23543adf4588e18ec419c5604eab43dcbb7d4ef12a7e6ad0f5d872d34a9839d428861e22770973e0ca7316891f8b246cb0942d4fede3fc" + // DigestDFor512 := "64674d3e2843d36c603f44477e4cd66ee85fe1a91227bbcd271202429024ed61" + // DigestBFor512 := "af8a8324b7848fc5887c63e632402df99c729669889b4d2ae7efceb9f1c2341b81d8a18b82e994564d854422a544c3dffc7d64d8389c90ab7fad19a50bb75e31" + + DescribeTable("signs and rehashes presigned in top mode", (func(subst Substitutions, mopts ...ocm.ModificationOption) { + var finalizer Finalizer + defer Defer(finalizer.Finalize) + + setup(mopts...) + { + arch := finalizer.Nested() + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) + arch.Close(src) + + resolver := ocm.NewCompoundResolver(src) + + log := SignComponent(resolver, SIGNATURE, COMPONENTB, subst["D_COMPB_X"], DigestMode(DIGESTMODE_TOP), HashByAlgo(sha512.Algorithm)) + Expect(log).To(StringEqualTrimmedWithContext(` +applying to version "github.com/mandelsoft/ref:v1"[github.com/mandelsoft/ref:v1]... + no digest found for "github.com/mandelsoft/test:v1" + applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/ref:v1]... + resource 0: "name"="data_a": digest SHA-512:${D_DATAA_X}[genericBlobDigest/v1] + reference 0: github.com/mandelsoft/test:v1: digest SHA-512:${D_COMPA_X}[jsonNormalisation/v1] + resource 0: "name"="data_b": digest ${HASH}:${D_DATAB_X}[genericBlobDigest/v1] + +`, MergeSubst(localDigests, subst))) + VerifyComponent(resolver, SIGNATURE, COMPONENTB, subst["D_COMPB_X"]) + log = SignComponent(resolver, SIGNATURE, COMPONENTD, subst["D_COMPD_X"], DigestMode(DIGESTMODE_TOP)) + + Expect(log).To(StringEqualTrimmedWithContext(` +applying to version "github.com/mandelsoft/top:v1"[github.com/mandelsoft/top:v1]... + no digest found for "github.com/mandelsoft/ref:v1" + applying to version "github.com/mandelsoft/ref:v1"[github.com/mandelsoft/top:v1]... + no digest found for "github.com/mandelsoft/test:v1" + applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/top:v1]... + resource 0: "name"="data_a": digest SHA-256:${D_DATAA}[genericBlobDigest/v1] + reference 0: github.com/mandelsoft/test:v1: digest SHA-256:${D_COMPA}[jsonNormalisation/v1] + resource 0: "name"="data_b": digest SHA-256:${D_DATAB}[genericBlobDigest/v1] + reference 0: github.com/mandelsoft/ref:v1: digest SHA-256:${D_COMPB}[jsonNormalisation/v1] + no digest found for "github.com/mandelsoft/ref2:v1" + applying to version "github.com/mandelsoft/ref2:v1"[github.com/mandelsoft/top:v1]... + reference 0: github.com/mandelsoft/test:v1: digest SHA-256:${D_COMPA}[jsonNormalisation/v1] + resource 0: "name"="data_c": digest SHA-256:${D_DATAC}[genericBlobDigest/v1] + reference 1: github.com/mandelsoft/ref2:v1: digest SHA-256:${D_COMPC}[jsonNormalisation/v1] + resource 0: "name"="data_d": digest SHA-256:${D_DATAD}[genericBlobDigest/v1] +`, MergeSubst(localDigests, subst))) + Defer(arch.Finalize) + } + + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) + finalizer.Close(src) + cv := Must(src.LookupComponentVersion(COMPONENTD, VERSION)) + finalizer.Close(cv) + Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(subst["D_COMPD_X"])) + + Expect(cv.GetDescriptor().NestedDigests).NotTo(BeNil()) + Expect(cv.GetDescriptor().NestedDigests.String()).To(StringEqualTrimmedWithContext(` +github.com/mandelsoft/ref:v1: SHA-256:${D_COMPB}[jsonNormalisation/v1] + data_b:v1[]: SHA-256:${D_DATAB}[genericBlobDigest/v1] +github.com/mandelsoft/ref2:v1: SHA-256:${D_COMPC}[jsonNormalisation/v1] + data_c:v1[]: SHA-256:${D_DATAC}[genericBlobDigest/v1] +github.com/mandelsoft/test:v1: SHA-256:${D_COMPA}[jsonNormalisation/v1] + data_a:v1[]: SHA-256:${D_DATAA}[genericBlobDigest/v1] +`, MergeSubst(localDigests, subst))) + + cva, err := src.LookupComponentVersion(COMPONENTA, VERSION) + Expect(err).To(Succeed()) + finalizer.Close(cva) + Expect(len(cva.GetDescriptor().Signatures)).To(Equal(0)) + + //////// + + VerifyComponent(src, SIGNATURE, COMPONENTD, subst["D_COMPD_X"]) + }), + Entry("legacy", Substitutions{ + "HASH": "SHA-512", + "D_DATAA_X": D_DATAA512, + "D_DATAB_X": D_DATAB512, + "D_DATAB": D_DATAB, + "D_COMPA_X": D_COMPA512, + "D_COMPB_X": D_COMPB512, + "D_COMPD_X": D_COMPD, + "D_COMPB": D_COMPB, + }, ocm.SkipDigest()), + Entry("hashed", Substitutions{ + "HASH": "SHA-256", + "D_DATAA_X": D_DATAA512, + "D_DATAB_X": D_DATAB, + "D_COMPA_X": D_COMPA512, + "D_COMPB_X": D_COMPB_HASHED, + "D_COMPD_X": D_COMPD, + }), + ) + + DescribeTable("verifies after sub level signing", func(subst Substitutions, mopts ...ocm.ModificationOption) { + var finalizer Finalizer + defer Defer(finalizer.Finalize) + + setup(mopts...) + + digestD := D_COMPD + { + arch := finalizer.Nested() + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) + arch.Close(src) + + resolver := ocm.NewCompoundResolver(src) + + fmt.Printf("SIGN D\n") + log := SignComponent(resolver, SIGNATURE, COMPONENTD, digestD, DigestMode(DIGESTMODE_TOP)) + + Expect(log).To(StringEqualTrimmedWithContext(` +applying to version "github.com/mandelsoft/top:v1"[github.com/mandelsoft/top:v1]... + no digest found for "github.com/mandelsoft/ref:v1" + applying to version "github.com/mandelsoft/ref:v1"[github.com/mandelsoft/top:v1]... + no digest found for "github.com/mandelsoft/test:v1" + applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/top:v1]... + resource 0: "name"="data_a": digest SHA-256:${D_DATAA}[genericBlobDigest/v1] + reference 0: github.com/mandelsoft/test:v1: digest SHA-256:${D_COMPA}[jsonNormalisation/v1] + resource 0: "name"="data_b": digest SHA-256:${D_DATAB}[genericBlobDigest/v1] + reference 0: github.com/mandelsoft/ref:v1: digest SHA-256:${D_COMPB}[jsonNormalisation/v1] + no digest found for "github.com/mandelsoft/ref2:v1" + applying to version "github.com/mandelsoft/ref2:v1"[github.com/mandelsoft/top:v1]... + reference 0: github.com/mandelsoft/test:v1: digest SHA-256:${D_COMPA}[jsonNormalisation/v1] + resource 0: "name"="data_c": digest SHA-256:${D_DATAC}[genericBlobDigest/v1] + reference 1: github.com/mandelsoft/ref2:v1: digest SHA-256:${D_COMPC}[jsonNormalisation/v1] + resource 0: "name"="data_d": digest SHA-256:${D_DATAD}[genericBlobDigest/v1] +`, MergeSubst(localDigests, subst))) + + fmt.Printf("SIGN B\n") + SignComponent(resolver, SIGNATURE, COMPONENTB, subst["D_COMPB_X"], HashByAlgo(sha512.Algorithm), DigestMode(DIGESTMODE_TOP)) + fmt.Printf("VERIFY B\n") + VerifyComponent(resolver, SIGNATURE, COMPONENTB, subst["D_COMPB_X"]) + + Defer(arch.Finalize) + } + + src, err := ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env) + Expect(err).To(Succeed()) + finalizer.Close(src) + cv, err := src.LookupComponentVersion(COMPONENTD, VERSION) + Expect(err).To(Succeed()) + finalizer.Close(cv) + Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(digestD)) + + Expect(cv.GetDescriptor().NestedDigests.String()).To(StringEqualTrimmedWithContext(` +github.com/mandelsoft/ref:v1: SHA-256:${D_COMPB}[jsonNormalisation/v1] + data_b:v1[]: SHA-256:${D_DATAB}[genericBlobDigest/v1] +github.com/mandelsoft/ref2:v1: SHA-256:${D_COMPC}[jsonNormalisation/v1] + data_c:v1[]: SHA-256:${D_DATAC}[genericBlobDigest/v1] +github.com/mandelsoft/test:v1: SHA-256:${D_COMPA}[jsonNormalisation/v1] + data_a:v1[]: SHA-256:${D_DATAA}[genericBlobDigest/v1] +`, MergeSubst(localDigests, subst))) + + cva, err := src.LookupComponentVersion(COMPONENTA, VERSION) + Expect(err).To(Succeed()) + finalizer.Close(cva) + Expect(len(cva.GetDescriptor().Signatures)).To(Equal(0)) + + //////// + fmt.Printf("VERIFY D\n") + VerifyComponent(src, SIGNATURE, COMPONENTD, digestD) + }, + Entry("legacy", Substitutions{ + "D_COMPB_X": D_COMPB512, + }, ocm.SkipDigest()), + Entry("hashed", Substitutions{ + "D_COMPB_X": D_COMPB_HASHED, + }), + ) + + DescribeTable("fixes digest mode", func(subst Substitutions, mopts ...ocm.ModificationOption) { + setup(mopts...) + + var finalizer Finalizer + defer Check(finalizer.Finalize) + + { // sign with mode local + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) + finalizer.Close(src) + + resolver := ocm.NewCompoundResolver(src) + + fmt.Printf("SIGN B\n") + _ = SignComponent(resolver, SIGNATURE, COMPONENTB, D_COMPB, DigestMode(DIGESTMODE_LOCAL)) + VerifyComponent(src, SIGNATURE, COMPONENTB, D_COMPB) + Check(finalizer.Finalize) + } + { // check mode + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) + finalizer.Close(src) + cv := Must(src.LookupComponentVersion(COMPONENTB, VERSION)) + finalizer.Close(cv) + Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(D_COMPB)) + Expect(cv.GetDescriptor().NestedDigests).To(BeNil()) + Expect(cv.GetDescriptor().References[0].Digest).NotTo(BeNil()) + Expect(GetDigestMode(cv.GetDescriptor())).To(Equal(DIGESTMODE_LOCAL)) + Check(finalizer.Finalize) + } + { // try resign with mode top + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) + finalizer.Close(src) + + resolver := ocm.NewCompoundResolver(src) + + fmt.Printf("RESIGN B\n") + _ = SignComponent(resolver, SIGNATURE, COMPONENTB, D_COMPB, DigestMode(DIGESTMODE_TOP), SignatureName(SIGNATURE2, true)) + VerifyComponent(src, SIGNATURE, COMPONENTB, D_COMPB, SignatureName(SIGNATURE2, true)) + Check(finalizer.Finalize) + } + { // check mode + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) + finalizer.Close(src) + cv := Must(src.LookupComponentVersion(COMPONENTB, VERSION)) + finalizer.Close(cv) + cd := cv.GetDescriptor() + Expect(len(cd.Signatures)).To(Equal(2)) + Expect(cd.Signatures[0].Digest.Value).To(Equal(D_COMPB)) + Expect(cd.Signatures[0].Name).To(Equal(SIGNATURE)) + Expect(cd.Signatures[1].Digest.Value).To(Equal(D_COMPB)) + Expect(cd.Signatures[1].Name).To(Equal(SIGNATURE2)) + Expect(cv.GetDescriptor().NestedDigests).To(BeNil()) + Expect(cv.GetDescriptor().References[0].Digest).NotTo(BeNil()) + Expect(GetDigestMode(cd)).To(Equal(DIGESTMODE_LOCAL)) + Check(finalizer.Finalize) + } + }, + Entry("legacy", Substitutions{}, ocm.SkipDigest()), + Entry("hashed", Substitutions{}), + ) + + DescribeTable("fixes digest mode in recursive signing", func(subst Substitutions, mopts ...ocm.ModificationOption) { + var finalizer Finalizer + defer Check(finalizer.Finalize) + + setup(mopts...) + + { // sign with mode local + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) + finalizer.Close(src) + + resolver := ocm.NewCompoundResolver(src) + + fmt.Printf("SIGN B\n") + _ = SignComponent(resolver, SIGNATURE, COMPONENTB, D_COMPB, DigestMode(DIGESTMODE_TOP), SignatureName(SIGNATURE2, true)) + VerifyComponent(src, SIGNATURE, COMPONENTB, D_COMPB, SignatureName(SIGNATURE2, true)) + Check(finalizer.Finalize) + } + { // check mode + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) + finalizer.Close(src) + cv := Must(src.LookupComponentVersion(COMPONENTB, VERSION)) + finalizer.Close(cv) + Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(D_COMPB)) + Expect(cv.GetDescriptor().NestedDigests).NotTo(BeNil()) + Expect(cv.GetDescriptor().References[0].Digest).To(BeNil()) + Expect(GetDigestMode(cv.GetDescriptor())).To(Equal(DIGESTMODE_TOP)) + Check(finalizer.Finalize) + } + { // resign recursively from top + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) + finalizer.Close(src) + + resolver := ocm.NewCompoundResolver(src) + + fmt.Printf("SIGN D\n") + _ = SignComponent(resolver, SIGNATURE, COMPONENTD, D_COMPD, Recursive(), DigestMode(DIGESTMODE_LOCAL)) + VerifyComponent(src, SIGNATURE, COMPONENTD, D_COMPD) + Check(finalizer.Finalize) + } + { // check mode + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) + finalizer.Close(src) + + cv := Must(src.LookupComponentVersion(COMPONENTB, VERSION)) + finalizer.Close(cv) + cd := cv.GetDescriptor() + Expect(len(cd.Signatures)).To(Equal(2)) + Expect(cd.Signatures[0].Digest.Value).To(Equal(D_COMPB)) + Expect(cd.Signatures[0].Name).To(Equal(SIGNATURE2)) + Expect(cd.Signatures[1].Digest.Value).To(Equal(D_COMPB)) + Expect(cd.Signatures[1].Name).To(Equal(SIGNATURE)) + Expect(cd.NestedDigests).NotTo(BeNil()) + Expect(cd.References[0].Digest).To(BeNil()) + Expect(GetDigestMode(cd)).To(Equal(DIGESTMODE_TOP)) + + cv = Must(src.LookupComponentVersion(COMPONENTD, VERSION)) + finalizer.Close(cv) + cd = cv.GetDescriptor() + Expect(len(cd.Signatures)).To(Equal(1)) + Expect(cd.Signatures[0].Digest.Value).To(Equal(D_COMPD)) + Expect(cd.Signatures[0].Name).To(Equal(SIGNATURE)) + Expect(cv.GetDescriptor().NestedDigests).To(BeNil()) + Expect(cv.GetDescriptor().References[0].Digest).NotTo(BeNil()) + Expect(GetDigestMode(cd)).To(Equal(DIGESTMODE_LOCAL)) + + Check(finalizer.Finalize) + } + }, + Entry("legacy", Substitutions{}, ocm.SkipDigest()), + Entry("hashed", Substitutions{}), + ) + }) + + Context("ref hashes", func() { + BeforeEach(func() { + env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { + env.ComponentVersion(COMPONENTA, VERSION, func() { + env.Provider(PROVIDER) + }) + env.ComponentVersion(COMPONENTB, VERSION, func() { + env.Provider(PROVIDER) + env.Reference("refa", COMPONENTA, VERSION) + }) + env.ComponentVersion(COMPONENTC, VERSION, func() { + env.Provider(PROVIDER) + env.Reference("refb", COMPONENTB, VERSION) + }) + }) + }) + + It("handles top level signature", func() { + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) + defer Close(src, "ctf") + + resolver := ocm.NewCompoundResolver(src) + + cv := Must(resolver.LookupComponentVersion(COMPONENTC, VERSION)) + defer cv.Close() + + opts := NewOptions( + Sign(signing.DefaultHandlerRegistry().GetSigner(SIGN_ALGO), SIGNATURE), + Resolver(resolver), + VerifyDigests(), + ) + MustBeSuccessful(opts.Complete(env)) + + digestC := "1e81ac0fe69614e6fd73ab7a1c809dd31fcbcb810f0036be7a296d226e4bd64b" + pr, buf := common.NewBufferedPrinter() + dig := Must(Apply(pr, nil, cv, opts)) + Expect(dig.Value).To(StringEqualWithContext(digestC)) + + Expect(cv.GetDescriptor().References[0].Digest.HashAlgorithm).To(Equal(sha256.Algorithm)) + + cvb := Must(resolver.LookupComponentVersion(COMPONENTB, VERSION)) + defer Close(cvb) + Expect(cvb.GetDescriptor().References[0].Digest).NotTo(BeNil()) + _ = buf + }) + }) + + Context("keyless verification", func() { + ca, capriv := Must2(rsa.CreateRootCertificate(signutils.CommonName("ca-authority"), 10*time.Hour)) + intercert, interpem, interpriv := Must3(rsa.CreateSigningCertificate(signutils.CommonName("acme.org"), ca, ca, capriv, 5*time.Hour, true)) + certIssuer := &pkix.Name{ + CommonName: PROVIDER, + Country: []string{"DE", "US"}, + Locality: []string{"Walldorf d"}, + StreetAddress: []string{"x y"}, + PostalCode: []string{"69169"}, + Province: []string{"BW"}, + } + cert, pemBytes, priv := Must3(rsa.CreateSigningCertificate(certIssuer, interpem, ca, interpriv, time.Hour)) + + certs := Must(signutils.GetCertificateChain(pemBytes, false)) + Expect(len(certs)).To(Equal(3)) + + ctx := ocm.DefaultContext() + + var cv ocm.ComponentVersionAccess + + BeforeEach(func() { + cv = composition.NewComponentVersion(ctx, COMPONENTA, VERSION) + }) + + It("is consistent", func() { + MustBeSuccessful(signutils.VerifyCertificate(intercert, interpem, ca, nil)) + MustBeSuccessful(signutils.VerifyCertificate(cert, pemBytes, ca, nil)) + }) + + It("signs with certificate and default issuer", func() { + digest := "9cf14695c864411cad03071a8766e6769bb00373bdd8c65887e4644cc285dc78" + res := ocm.NewDedicatedResolver(cv) + + buf := SignComponent(res, PROVIDER, COMPONENTA, digest, PrivateKey(PROVIDER, priv), PublicKey(PROVIDER, pemBytes), RootCertificates(ca)) + Expect(buf).To(StringEqualTrimmedWithContext(` +applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v1]... +`)) + + i := cv.GetDescriptor().GetSignatureIndex(PROVIDER) + Expect(i).To(BeNumerically(">=", 0)) + sig := cv.GetDescriptor().Signatures[i].Signature + Expect(sig.MediaType).To(Equal(signutils.MediaTypePEM)) + _, algo, chain := Must3(signutils.GetSignatureFromPem([]byte(sig.Value))) + Expect(algo).To(Equal(rsa.Algorithm)) + Expect(len(chain)).To(Equal(3)) + + VerifyComponent(res, PROVIDER, COMPONENTA, digest, RootCertificates(ca)) + }) + + It("signs with certificate and explicit CN issuer", func() { + digest := "9cf14695c864411cad03071a8766e6769bb00373bdd8c65887e4644cc285dc78" + res := ocm.NewDedicatedResolver(cv) + + buf := SignComponent(res, SIGNATURE, COMPONENTA, digest, PrivateKey(SIGNATURE, priv), PublicKey(SIGNATURE, pemBytes), RootCertificates(ca), Issuer(PROVIDER)) + Expect(buf).To(StringEqualTrimmedWithContext(` +applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v1]... +`)) + + i := cv.GetDescriptor().GetSignatureIndex(SIGNATURE) + Expect(i).To(BeNumerically(">=", 0)) + sig := cv.GetDescriptor().Signatures[i].Signature + Expect(sig.MediaType).To(Equal(signutils.MediaTypePEM)) + _, algo, chain := Must3(signutils.GetSignatureFromPem([]byte(sig.Value))) + Expect(algo).To(Equal(rsa.Algorithm)) + Expect(len(chain)).To(Equal(3)) + + VerifyComponent(res, SIGNATURE, COMPONENTA, digest, RootCertificates(ca), Issuer(PROVIDER)) + + FailVerifyComponent(res, SIGNATURE, COMPONENTA, digest, + `github.com/mandelsoft/test:v1: public key from signature: public key certificate: issuer mismatch in public key certificate: common name "mandelsoft" is invalid`, + RootCertificates(ca)) + }) + + It("signs with certificate and issuer", func() { + digest := "9cf14695c864411cad03071a8766e6769bb00373bdd8c65887e4644cc285dc78" + res := ocm.NewDedicatedResolver(cv) + issuer := &pkix.Name{ + CommonName: PROVIDER, + Country: []string{"DE"}, + } + + buf := SignComponent(res, SIGNATURE, COMPONENTA, digest, PrivateKey(SIGNATURE, priv), PublicKey(SIGNATURE, pemBytes), RootCertificates(ca), PKIXIssuer(*issuer)) + Expect(buf).To(StringEqualTrimmedWithContext(` +applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v1]... +`)) + + i := cv.GetDescriptor().GetSignatureIndex(SIGNATURE) + Expect(i).To(BeNumerically(">=", 0)) + sig := cv.GetDescriptor().Signatures[i].Signature + Expect(sig.MediaType).To(Equal(signutils.MediaTypePEM)) + _, algo, chain := Must3(signutils.GetSignatureFromPem([]byte(sig.Value))) + Expect(algo).To(Equal(rsa.Algorithm)) + Expect(len(chain)).To(Equal(3)) + dn := Must(signutils.ParseDN(sig.Issuer)) + Expect(dn).To(Equal(certIssuer)) + + VerifyComponent(res, SIGNATURE, COMPONENTA, digest, RootCertificates(ca), PKIXIssuer(*issuer)) + + issuer.Country = []string{"XX"} + FailVerifyComponent(res, SIGNATURE, COMPONENTA, digest, + `github.com/mandelsoft/test:v1: public key from signature: public key certificate: issuer mismatch in public key certificate: country "XX" not found`, + RootCertificates(ca), PKIXIssuer(*issuer)) + }) + + It("signs with certificate, issuer and tsa", func() { + digest := "9cf14695c864411cad03071a8766e6769bb00373bdd8c65887e4644cc285dc78" + res := ocm.NewDedicatedResolver(cv) + issuer := &pkix.Name{ + CommonName: "mandelsoft", + Country: []string{"DE"}, + } + + buf := SignComponent(res, SIGNATURE, COMPONENTA, digest, PrivateKey(SIGNATURE, priv), PublicKey(SIGNATURE, pemBytes), RootCertificates(ca), PKIXIssuer(*issuer), UseTSA()) + Expect(buf).To(StringEqualTrimmedWithContext(` +applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v1]... +`)) + + i := cv.GetDescriptor().GetSignatureIndex(SIGNATURE) + Expect(i).To(BeNumerically(">=", 0)) + sig := cv.GetDescriptor().Signatures[i] + Expect(sig.Signature.MediaType).To(Equal(signutils.MediaTypePEM)) + _, algo, chain := Must3(signutils.GetSignatureFromPem([]byte(sig.Signature.Value))) + Expect(algo).To(Equal(rsa.Algorithm)) + Expect(len(chain)).To(Equal(3)) + dn := Must(signutils.ParseDN(sig.Signature.Issuer)) + Expect(dn).To(Equal(certIssuer)) + + Expect(sig.Timestamp).NotTo(BeNil()) + Expect(sig.Timestamp.Value).NotTo(Equal("")) + Expect(sig.Timestamp.Time).NotTo(BeNil()) + Expect(time.Now().Sub(sig.Timestamp.Time.Time()).Minutes()).To(BeNumerically("<", 2)) + VerifyComponent(res, SIGNATURE, COMPONENTA, digest, RootCertificates(ca), PKIXIssuer(*issuer)) + + issuer.Country = []string{"XX"} + FailVerifyComponent(res, SIGNATURE, COMPONENTA, digest, + `github.com/mandelsoft/test:v1: public key from signature: public key certificate: issuer mismatch in public key certificate: country "XX" not found`, + RootCertificates(ca), PKIXIssuer(*issuer)) + }) + }) +}) + +func HashComponent(resolver ocm.ComponentVersionResolver, name string, digest string, other ...Option) string { + cv, err := resolver.LookupComponentVersion(name, VERSION) + Expect(err).To(Succeed()) + defer cv.Close() + + opts := NewOptions( + Resolver(resolver), + Update(), VerifyDigests(), + ) + opts.Eval(other...) + ExpectWithOffset(1, opts.Complete(signingattr.Get(DefaultContext))).To(Succeed()) + + pr, buf := common.NewBufferedPrinter() + dig, err := Apply(pr, nil, cv, opts) + ExpectWithOffset(1, err).To(Succeed()) + ExpectWithOffset(1, dig.Value).To(StringEqualWithContext(digest)) + return buf.String() +} + +func VerifyHashes(resolver ocm.ComponentVersionResolver, name string, digest string) { + cv, err := resolver.LookupComponentVersion(name, VERSION) + ExpectWithOffset(1, err).To(Succeed()) + defer cv.Close() + + opts := NewOptions( + Resolver(resolver), + VerifyDigests(), + ) + ExpectWithOffset(1, opts.Complete(signingattr.Get(DefaultContext))).To(Succeed()) + dig, err := Apply(nil, nil, cv, opts) + ExpectWithOffset(1, err).To(Succeed()) + ExpectWithOffset(1, dig.Value).To(Equal(digest)) +} + +func SignComponent(resolver ocm.ComponentVersionResolver, signame, name string, digest string, other ...Option) string { + cv, err := resolver.LookupComponentVersion(name, VERSION) + Expect(err).To(Succeed()) + defer cv.Close() + + opts := NewOptions( + Sign(signingattr.Get(cv.GetContext()).GetSigner(SIGN_ALGO), signame), + Resolver(resolver), + VerifyDigests(), + ) + opts.Eval(other...) + ExpectWithOffset(1, opts.Complete(cv.GetContext())).To(Succeed()) + + pr, buf := common.NewBufferedPrinter() + dig, err := Apply(pr, nil, cv, opts) + ExpectWithOffset(1, err).To(Succeed()) + ExpectWithOffset(1, dig.Value).To(StringEqualWithContext(digest)) + return buf.String() +} + +func VerifyComponent(resolver ocm.ComponentVersionResolver, signame, name string, digest string, other ...Option) { + cv, err := resolver.LookupComponentVersion(name, VERSION) + ExpectWithOffset(1, err).To(Succeed()) + defer cv.Close() + + opts := NewOptions( + VerifySignature(signame), + Resolver(resolver), + VerifyDigests(), + ) + opts.Eval(other...) + ExpectWithOffset(1, opts.Complete(cv.GetContext())).To(Succeed()) + dig, err := Apply(nil, nil, cv, opts) + ExpectWithOffset(1, err).To(Succeed()) + ExpectWithOffset(1, dig.Value).To(Equal(digest)) +} + +func FailVerifyComponent(resolver ocm.ComponentVersionResolver, signame, name string, digest string, msg string, other ...Option) { + cv, err := resolver.LookupComponentVersion(name, VERSION) + ExpectWithOffset(1, err).To(Succeed()) + defer cv.Close() + + opts := NewOptions( + VerifySignature(signame), + Resolver(resolver), + VerifyDigests(), + ) + opts.Eval(other...) + ExpectWithOffset(1, opts.Complete(cv.GetContext())).To(Succeed()) + _, err = Apply(nil, nil, cv, opts) + ExpectWithOffset(1, err).To(MatchError(msg)) +} + +func Check(f func() error) { + ExpectWithOffset(1, f()).To(Succeed()) +} + +func CheckResourceDigests(cd *compdesc.ComponentDescriptor, digests map[string]*metav1.DigestSpec, offsets ...int) { + o := 4 + for _, a := range offsets { + o += a + } + for i, r := range cd.Resources { + By(fmt.Sprintf("resource %d", i), func() { + if none.IsNone(r.Access.GetKind()) { + ExpectWithOffset(o, r.Digest).To(BeNil()) + } else { + ExpectWithOffset(o, r.Digest).NotTo(BeNil()) + if digests != nil { + ExpectWithOffset(o, r.Digest).To(Equal(digests[r.Name])) + } + } + }) + } +} + +//////////////////////////////////////////////////////////////////////////////// + +const wrongDigest = "0a835d52867572bdaf7da7fb35ee59ad45c3db2dacdeeca62178edd5d07ef08c" // any wrong value + +type EntryCheck interface { + Mode() string + Check1CheckD(cvd ocm.ComponentVersionAccess, d Substitutions) + Check1CheckA(cva ocm.ComponentVersionAccess, d *metav1.DigestSpec, mopts ...ocm.ModificationOption) + Check1Corrupt(cva ocm.ComponentVersionAccess, f *Finalizer, cvd ocm.ComponentVersionAccess) + + Check2Ref(cv ocm.ComponentVersionAccess, name string, d string) +} + +type EntryLocal struct{} + +func (*EntryLocal) Mode() string { + return DIGESTMODE_LOCAL +} + +func (*EntryLocal) Check1CheckD(cvd ocm.ComponentVersionAccess, _ Substitutions) { + ExpectWithOffset(1, cvd.GetDescriptor().NestedDigests).To(BeNil()) +} + +func (*EntryLocal) Check1CheckA(cva ocm.ComponentVersionAccess, d *metav1.DigestSpec, _ ...ocm.ModificationOption) { + CheckResourceDigests(cva.GetDescriptor(), map[string]*metav1.DigestSpec{ + "data_a": d, + }, 1) +} + +func (*EntryLocal) Check1Corrupt(cva ocm.ComponentVersionAccess, f *Finalizer, _ ocm.ComponentVersionAccess) { + cva.GetDescriptor().Resources[0].Digest.Value = wrongDigest + MustBeSuccessful(cva.Update()) + Check(f.Finalize) +} + +func (*EntryLocal) Check2Ref(cv ocm.ComponentVersionAccess, name string, d string) { + CheckCompRef(cv, name, CompDigestSpec(d), 1) +} + +////////// + +type EntryTop struct { + found int +} + +func (*EntryTop) Mode() string { + return DIGESTMODE_TOP +} + +func (*EntryTop) Check1CheckD(cvd ocm.ComponentVersionAccess, digests Substitutions) { + ExpectWithOffset(1, cvd.GetDescriptor().NestedDigests).NotTo(BeNil()) + ExpectWithOffset(1, cvd.GetDescriptor().NestedDigests.String()).To(StringEqualTrimmedWithContext(` +github.com/mandelsoft/ref:v1: SHA-256:${D_COMPB}[jsonNormalisation/v1] + data_b:v1[]: SHA-256:${D_DATAB}[genericBlobDigest/v1] +github.com/mandelsoft/ref2:v1: SHA-256:${D_COMPC}[jsonNormalisation/v1] + data_c:v1[]: SHA-256:${D_DATAC}[genericBlobDigest/v1] +github.com/mandelsoft/test:v1: SHA-256:${D_COMPA}[jsonNormalisation/v1] + data_a:v1[]: SHA-256:${D_DATAA}[genericBlobDigest/v1] +`, digests)) +} + +func (*EntryTop) Check1CheckA(cva ocm.ComponentVersionAccess, d *metav1.DigestSpec, mopts ...ocm.ModificationOption) { + if ocm.NewModificationOptions(mopts...).IsSkipDigest() { + ExpectWithOffset(1, cva.GetDescriptor().Resources[0].Digest).To(BeNil()) + } else { + CheckResourceDigests(cva.GetDescriptor(), map[string]*metav1.DigestSpec{ + "data_a": d, + }, 1) + } +} + +func (e *EntryTop) Check1Corrupt(_ ocm.ComponentVersionAccess, _ *Finalizer, cvd ocm.ComponentVersionAccess) { + e.found = -1 + for i, n := range cvd.GetDescriptor().NestedDigests { + if n.Name == COMPONENTA { + n.Resources[0].Digest.Value = wrongDigest + e.found = i + } + } + ExpectWithOffset(1, e.found).NotTo(Equal(-1)) +} + +func (*EntryTop) Check2Ref(cv ocm.ComponentVersionAccess, name string, d string) { + CheckCompRef(cv, name, nil, 1) +} diff --git a/api/ocm/tools/signing/signingtest/testdata.go b/api/ocm/tools/signing/signingtest/testdata.go new file mode 100644 index 000000000..849435cdb --- /dev/null +++ b/api/ocm/tools/signing/signingtest/testdata.go @@ -0,0 +1,13 @@ +package signingtest + +import ( + "ocm.software/ocm/api/helper/env" +) + +func TestData(dest ...string) env.Option { + return env.ProjectTestDataForCaller(dest...) +} + +func ModifiableTestData(dest ...string) env.Option { + return env.ModifiableProjectTestDataForCaller(dest...) +} diff --git a/pkg/contexts/ocm/signing/signingtest/testdata/compat/component-archive/blobs/sha256.c893900c1106f13d55648a425f502dd6518e40eaa3ac7d24023d4c73d55dc12e b/api/ocm/tools/signing/signingtest/testdata/compat/component-archive/blobs/sha256.c893900c1106f13d55648a425f502dd6518e40eaa3ac7d24023d4c73d55dc12e similarity index 100% rename from pkg/contexts/ocm/signing/signingtest/testdata/compat/component-archive/blobs/sha256.c893900c1106f13d55648a425f502dd6518e40eaa3ac7d24023d4c73d55dc12e rename to api/ocm/tools/signing/signingtest/testdata/compat/component-archive/blobs/sha256.c893900c1106f13d55648a425f502dd6518e40eaa3ac7d24023d4c73d55dc12e diff --git a/pkg/contexts/ocm/signing/signingtest/testdata/compat/component-archive/component-descriptor.yaml b/api/ocm/tools/signing/signingtest/testdata/compat/component-archive/component-descriptor.yaml similarity index 100% rename from pkg/contexts/ocm/signing/signingtest/testdata/compat/component-archive/component-descriptor.yaml rename to api/ocm/tools/signing/signingtest/testdata/compat/component-archive/component-descriptor.yaml diff --git a/pkg/contexts/ocm/signing/signingtest/testdata/compat/rsa.priv b/api/ocm/tools/signing/signingtest/testdata/compat/rsa.priv similarity index 100% rename from pkg/contexts/ocm/signing/signingtest/testdata/compat/rsa.priv rename to api/ocm/tools/signing/signingtest/testdata/compat/rsa.priv diff --git a/pkg/contexts/ocm/signing/signingtest/testdata/compat/rsa.pub b/api/ocm/tools/signing/signingtest/testdata/compat/rsa.pub similarity index 100% rename from pkg/contexts/ocm/signing/signingtest/testdata/compat/rsa.pub rename to api/ocm/tools/signing/signingtest/testdata/compat/rsa.pub diff --git a/pkg/contexts/ocm/signing/suite_test.go b/api/ocm/tools/signing/suite_test.go similarity index 100% rename from pkg/contexts/ocm/signing/suite_test.go rename to api/ocm/tools/signing/suite_test.go diff --git a/pkg/contexts/ocm/signing/transport_test.go b/api/ocm/tools/signing/transport_test.go similarity index 89% rename from pkg/contexts/ocm/signing/transport_test.go rename to api/ocm/tools/signing/transport_test.go index 7d3e76a04..2133b3890 100644 --- a/pkg/contexts/ocm/signing/transport_test.go +++ b/api/ocm/tools/signing/transport_test.go @@ -3,29 +3,29 @@ package signing_test import ( "fmt" - // . "github.com/open-component-model/ocm/pkg/contexts/ocm/signing" + // . "ocm.software/ocm/api/ocm/tools/signing" . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/oci/testhelper" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/testhelper" - . "github.com/open-component-model/ocm/pkg/env/builder" + . "ocm.software/ocm/api/helper/builder" + . "ocm.software/ocm/api/oci/testhelper" + . "ocm.software/ocm/api/ocm/testhelper" "github.com/mandelsoft/goutils/finalizer" - "github.com/open-component-model/ocm/pkg/common" - "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/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" - "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/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/signing" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/tools/signing" + "ocm.software/ocm/api/ocm/tools/transfer" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + common "ocm.software/ocm/api/utils/misc" ) const ( diff --git a/api/ocm/tools/toi/drivers/default/driver.go b/api/ocm/tools/toi/drivers/default/driver.go new file mode 100644 index 000000000..eae590e8a --- /dev/null +++ b/api/ocm/tools/toi/drivers/default/driver.go @@ -0,0 +1,10 @@ +package _default + +import ( + "ocm.software/ocm/api/ocm/tools/toi/drivers/docker" + "ocm.software/ocm/api/ocm/tools/toi/install" +) + +var New = func() install.Driver { + return &docker.Driver{} +} diff --git a/pkg/toi/drivers/docker/client.go b/api/ocm/tools/toi/drivers/docker/client.go similarity index 100% rename from pkg/toi/drivers/docker/client.go rename to api/ocm/tools/toi/drivers/docker/client.go diff --git a/api/ocm/tools/toi/drivers/docker/driver.go b/api/ocm/tools/toi/drivers/docker/driver.go new file mode 100644 index 000000000..75a15c25f --- /dev/null +++ b/api/ocm/tools/toi/drivers/docker/driver.go @@ -0,0 +1,566 @@ +package docker + +import ( + "archive/tar" + "context" + "fmt" + "io" + "os" + unix_path "path" + "strconv" + "strings" + "sync" + + "github.com/distribution/reference" + "github.com/docker/cli/cli/command" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/image" + registrytypes "github.com/docker/docker/api/types/registry" + "github.com/docker/docker/client" + "github.com/docker/docker/pkg/jsonmessage" + "github.com/docker/docker/pkg/stdcopy" + "github.com/docker/docker/registry" + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/set" + "github.com/mitchellh/copystructure" + + "ocm.software/ocm/api/ocm/tools/toi" + "ocm.software/ocm/api/ocm/tools/toi/install" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +const ( + OptionQuiet = "DOCKER_DRIVER_QUIET" + OptionCleanup = "CLEANUP_CONTAINERS" + OptionPullPolicy = "PULL_POLICY" + PullPolicyAlways = "Always" + PullPolicyNever = "Never" + PullPolicyIfNotPresent = "IfNotPresent" + OptionNetworkMode = "NETWORK_MODE" + OptionUsernsMode = "USERNS_MODE" + + trueAsString = "true" + falseAsString = "false" +) + +var Options = set.New[string]( + OptionQuiet, + OptionCleanup, + OptionPullPolicy, + OptionNetworkMode, + OptionUsernsMode, +) + +// Driver is capable of running Docker invocation images using Docker itself. +type Driver struct { + config map[string]string + // If true, this will not actually run Docker + Simulate bool + dockerCli command.Cli + dockerConfigurationOptions []ConfigurationOption + containerOut io.Writer + containerErr io.Writer + containerHostCfg container.HostConfig + containerCfg container.Config +} + +var _ install.Driver = (*Driver)(nil) + +func New() install.Driver { + return &Driver{} +} + +// GetContainerConfig returns a copy of the container configuration +// used by the driver during container exec. +func (d *Driver) GetContainerConfig() (container.Config, error) { + cpy, err := copystructure.Copy(d.containerCfg) + if err != nil { + return container.Config{}, err + } + + cfg, ok := cpy.(container.Config) + if !ok { + return container.Config{}, errors.New("unable to process container config") + } + + return cfg, nil +} + +// GetContainerHostConfig returns a copy of the container host configuration +// used by the driver during container exec. +func (d *Driver) GetContainerHostConfig() (container.HostConfig, error) { + cpy, err := copystructure.Copy(d.containerHostCfg) + if err != nil { + return container.HostConfig{}, err + } + + cfg, ok := cpy.(container.HostConfig) + if !ok { + return container.HostConfig{}, errors.New("unable to process container host config") + } + + return cfg, nil +} + +// SetConfig sets Docker driver configuration. +func (d *Driver) SetConfig(settings map[string]string) error { + // Set default and provide feedback on acceptable input values. + value, ok := settings[OptionCleanup] + if !ok { + settings[OptionCleanup] = trueAsString + } else if value != trueAsString && value != falseAsString { + return fmt.Errorf("config variable %s has unexpected value %q. Supported values are 'true', 'false', or unset", OptionCleanup, value) + } + + value, ok = settings[OptionPullPolicy] + if ok { + if value != PullPolicyAlways && value != PullPolicyIfNotPresent && value != PullPolicyNever { + return fmt.Errorf("config variable %s has unexpected value %q. Supported values are '%s', '%s', '%s' , or unset", OptionPullPolicy, value, PullPolicyAlways, PullPolicyIfNotPresent, PullPolicyNever) + } + } + + value, ok = settings[OptionNetworkMode] + if ok { + m := container.NetworkMode(value) + d.dockerConfigurationOptions = append(d.dockerConfigurationOptions, NetworkModeOpt(value)) + if _, ok := settings[OptionUsernsMode]; m.IsHost() && !ok { + settings[OptionUsernsMode] = "host" + } + } + + value, ok = settings[OptionUsernsMode] + if ok { + d.dockerConfigurationOptions = append(d.dockerConfigurationOptions, UsernsModeOpt(value)) + } + + d.config = settings + return nil +} + +// SetDockerCli makes the driver use an already initialized cli. +func (d *Driver) SetDockerCli(dockerCli command.Cli) { + d.dockerCli = dockerCli +} + +// SetContainerOut sets the container output stream. +func (d *Driver) SetContainerOut(w io.Writer) { + d.containerOut = w +} + +// SetContainerErr sets the container error stream. +func (d *Driver) SetContainerErr(w io.Writer) { + d.containerErr = w +} + +func pullImage(ctx context.Context, cli command.Cli, imageName string) error { + ref, err := reference.ParseNormalizedNamed(imageName) + if err != nil { + return fmt.Errorf("unable to parse normalized name: %w", err) + } + + // Resolve the Repository name from fqn to RepositoryInfo + repoInfo, err := registry.ParseRepositoryInfo(ref) + if err != nil { + return fmt.Errorf("unable to parse repository info: %w", err) + } + + authConfig := command.ResolveAuthConfig(cli.ConfigFile(), repoInfo.Index) + + encodedAuth, err := registrytypes.EncodeAuthConfig(authConfig) + if err != nil { + return fmt.Errorf("unable encode auth: %w", err) + } + + options := image.PullOptions{ + RegistryAuth: encodedAuth, + } + + responseBody, err := cli.Client().ImagePull(ctx, imageName, options) + if err != nil { + return fmt.Errorf("unable to pull image: %w", err) + } + + defer responseBody.Close() + + // passing isTerm = false here because of https://github.com/Nvveen/Gotty/pull/1 + err = jsonmessage.DisplayJSONMessagesStream(responseBody, cli.Out(), cli.Out().FD(), false, nil) + if err != nil { + return fmt.Errorf("unable to display json message: %w", err) + } + + return nil +} + +func (d *Driver) initializeDockerCli() (command.Cli, error) { + if d.config == nil { + d.config = map[string]string{} + } + + if d.dockerCli != nil { + return d.dockerCli, nil + } + + cli, err := GetDockerClient() + if err != nil { + return nil, err + } + + if d.config[OptionQuiet] == "1" { + err = cli.Apply(command.WithCombinedStreams(io.Discard)) + if err != nil { + return nil, err + } + } + + d.dockerCli = cli + return cli, nil +} + +func (d *Driver) Exec(op *install.Operation) (*install.OperationResult, error) { + ctx := context.Background() + + cli, err := d.initializeDockerCli() + if err != nil { + return nil, err + } + + if d.Simulate { + return nil, nil + } + if d.config[OptionPullPolicy] == PullPolicyAlways { + if err := pullImage(ctx, cli, op.Image.Ref); err != nil { + return nil, err + } + } + + ii, err := d.inspectImage(ctx, op.Image.Ref) + if err != nil { + return nil, err + } + + err = d.validateImageDigest(op.Image, ii.RepoDigests) + if err != nil { + return nil, errors.Wrap(err, "image digest validation failed") + } + + if err := d.setConfigurationOptions(op); err != nil { + return nil, err + } + + resp, err := cli.Client().ContainerCreate(ctx, &d.containerCfg, &d.containerHostCfg, nil, nil, "") + if err != nil { + return nil, fmt.Errorf("cannot create container: %w", err) + } + + if d.config[OptionCleanup] == trueAsString { + defer cli.Client().ContainerRemove(ctx, resp.ID, container.RemoveOptions{}) + } + + containerUID := getContainerUserID(ii.Config.User) + tarContent, done, err := generateTar(op.Files, containerUID) + if err != nil { + return nil, fmt.Errorf("error staging files: %w", err) + } + options := container.CopyToContainerOptions{ + AllowOverwriteDirWithFile: false, + } + // This copies the tar to the root of the container. The tar has been assembled using the + // path from the given file, starting at the /. + err = cli.Client().CopyToContainer(ctx, resp.ID, "/", tarContent, options) + if err != nil { + return nil, fmt.Errorf("error copying to / in container: %w", err) + } + + if err = done(); err != nil { + return nil, fmt.Errorf("unable to send data: %w", err) + } + tarContent.Close() + + attach, err := cli.Client().ContainerAttach(ctx, resp.ID, container.AttachOptions{ + Stream: true, + Stdout: true, + Stderr: true, + Logs: true, + }) + if err != nil { + return nil, fmt.Errorf("unable to retrieve logs: %w", err) + } + var ( + stdout io.Writer = os.Stdout + stderr io.Writer = os.Stderr + ) + if d.containerOut != nil { + stdout = d.containerOut + } else if op.Out != nil { + stdout = op.Out + } + if d.containerErr != nil { + stderr = d.containerErr + } else if op.Err != nil { + stderr = op.Err + } + go func() { + defer attach.Close() + for { + _, err = stdcopy.StdCopy(stdout, stderr, attach.Reader) + if err != nil { + break + } + } + }() + + if err = cli.Client().ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil { + return nil, fmt.Errorf("cannot start container: %w", err) + } + statusc, errc := cli.Client().ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning) + select { + case err := <-errc: + if err != nil { + opResult, fetchErr := d.fetchOutputs(ctx, resp.ID, op) + return opResult, containerError("error in container", err, fetchErr) + } + case s := <-statusc: + if s.StatusCode == 0 { + return d.fetchOutputs(ctx, resp.ID, op) + } + if s.Error != nil { + opResult, fetchErr := d.fetchOutputs(ctx, resp.ID, op) + return opResult, containerError(fmt.Sprintf("container exit code: %d", s.StatusCode), err, fetchErr) + } + opResult, fetchErr := d.fetchOutputs(ctx, resp.ID, op) + return opResult, containerError(fmt.Sprintf("container exit code: %d", s.StatusCode), err, fetchErr) + } + opResult, fetchErr := d.fetchOutputs(ctx, resp.ID, op) + if fetchErr != nil { + return opResult, fmt.Errorf("fetching outputs failed: %w", fetchErr) + } + return opResult, err +} + +// getContainerUserID determines the user id that the container will execute as +// based on the image's configured user. Defaults to 0 (root) if a user id is not set. +func getContainerUserID(user string) int { + if user != "" { + // Only look at the user, strip off a group if one was specified with USER uid:gid + if uid, err := strconv.Atoi(strings.Split(user, ":")[0]); err == nil { + return uid + } + } + return 0 +} + +// ApplyConfigurationOptions applies the configuration options set on the driver by the user. +func (d *Driver) ApplyConfigurationOptions() error { + for _, opt := range d.dockerConfigurationOptions { + if err := opt(&d.containerCfg, &d.containerHostCfg); err != nil { + return fmt.Errorf("unable to apply docker configuration: %w", err) + } + } + + return nil +} + +// setConfigurationOptions initializes the container and host configuration options on the driver, +// combining the default configuration with any overrides set by the user. +func (d *Driver) setConfigurationOptions(op *install.Operation) error { + var env []string + for k, v := range op.Environment { + env = append(env, fmt.Sprintf("%s=%v", k, v)) + } + + d.containerCfg = container.Config{ + Image: op.Image.Ref, + Env: env, + Cmd: []string{op.Action, op.ComponentVersion}, + AttachStderr: true, + AttachStdout: true, + } + + d.containerHostCfg = container.HostConfig{} + + if err := d.ApplyConfigurationOptions(); err != nil { + return fmt.Errorf("failed to apply configuration: %w", err) + } + + return nil +} + +func containerError(containerMessage string, containerErr, fetchErr error) error { + if fetchErr != nil { + return errors.NewEf(containerErr, "%s: %v. fetching outputs failed", containerMessage, fetchErr) + } + return errors.NewEf(containerErr, "%s", containerMessage) +} + +// fetchOutputs takes a context and a container ID; it copies the PathOutputs directory from that container. +// The goal is to collect all the files in the directory (recursively) and put them in a flat map of path to contents. +// This map will be inside the OperationResult. When fetchOutputs returns an error, it may also return partial results. +func (d *Driver) fetchOutputs(ctx context.Context, container string, op *install.Operation) (*install.OperationResult, error) { + opResult := &install.OperationResult{ + Outputs: map[string][]byte{}, + } + // The PathOutputs directory probably only exists if outputs are created. In the + // case there are no outputs defined on the operation, there probably are none to copy + // and we should return early. + if len(op.Outputs) == 0 { + return opResult, nil + } + ioReader, _, err := d.dockerCli.Client().CopyFromContainer(ctx, container, install.PathOutputs) + if err != nil { + return nil, fmt.Errorf("error copying outputs from container: %w", err) + } + tarReader := tar.NewReader(ioReader) + header, err := tarReader.Next() + // io.EOF pops us out of loop on successful run. + for err == nil { + // skip directories because we're gathering file contents + if header.FileInfo().IsDir() { + header, err = tarReader.Next() + continue + } + + var contents []byte + // CopyFromContainer strips prefix above outputs directory. + name := strings.TrimPrefix(header.Name, "outputs/") + outputName, shouldCapture := op.Outputs[name] + if shouldCapture { + contents, err = io.ReadAll(tarReader) + if err != nil { + return opResult, fmt.Errorf("error while reading %q from outputs tar: %w", header.Name, err) + } + opResult.Outputs[outputName] = contents + } + + header, err = tarReader.Next() + } + + if !errors.Is(err, io.EOF) { + return opResult, err + } + + return opResult, nil +} + +// generateTar creates a tarfile containing the specified files, with the owner +// set to the uid that the container runs as so that it is guaranteed to have +// read access to the files we copy into the container. +func generateTar(files map[string]blobaccess.BlobAccess, uid int) (io.ReadCloser, func() error, error) { + r, w := io.Pipe() + tw := tar.NewWriter(w) + for path := range files { + if unix_path.IsAbs(path) { + return nil, nil, fmt.Errorf("destination path %s should be a relative unix path", path) + } + } + var err error + wg := sync.WaitGroup{} + wg.Add(1) + toi.Log.Info("waiting for successful data transfer") + go func() { + defer wg.Done() + defer w.Close() + + have := map[string]bool{} + for path, content := range files { + path = unix_path.Join(install.PathInputs, path) + toi.Log.Info("transferring", "path", path) + // Write a header for the parent directories so that newly created intermediate directories are accessible by the user + dir := path + for dir != "/" { + dir = unix_path.Dir(dir) + if !have[dir] { + dirHdr := &tar.Header{ + Typeflag: tar.TypeDir, + Name: dir, + Mode: 0o700, + Uid: uid, + Size: 0, + } + tw.WriteHeader(dirHdr) + have[dir] = true + } + } + + // Grant access to just the owner (container user), so that files can be read by the container + fildHdr := &tar.Header{ + Typeflag: tar.TypeReg, + Name: path, + Mode: 0o600, + Size: content.Size(), + Uid: uid, + } + tw.WriteHeader(fildHdr) + reader, e := content.Reader() + if e != nil { + toi.Log.LogError(err, "cannot transfer", "path", path) + err = e + return + } + io.Copy(tw, reader) + } + }() + return r, func() error { wg.Wait(); return err }, nil +} + +// ConfigurationOption is an option used to customize docker driver container and host config. +type ConfigurationOption func(*container.Config, *container.HostConfig) error + +// inspectImage inspects the operation image and returns an object of types.ImageInspect, +// pulling the image if not found locally. +func (d *Driver) inspectImage(ctx context.Context, image string) (types.ImageInspect, error) { + ii, _, err := d.dockerCli.Client().ImageInspectWithRaw(ctx, image) + switch { + case client.IsErrNotFound(err): + fmt.Fprintf(d.dockerCli.Err(), "Unable to find image '%s' locally\n", image) + if d.config[OptionPullPolicy] == PullPolicyNever { + return ii, errors.Wrapf(err, "image %s not found", image) + } + if err := pullImage(ctx, d.dockerCli, image); err != nil { + return ii, err + } + if ii, _, err = d.dockerCli.Client().ImageInspectWithRaw(ctx, image); err != nil { + return ii, errors.Wrapf(err, "cannot inspect image %s", image) + } + case err != nil: + return ii, errors.Wrapf(err, "cannot inspect image %s", image) + } + + return ii, nil +} + +// validateImageDigest validates the operation image digest, if exists, against +// the supplied repoDigests. +func (d *Driver) validateImageDigest(image toi.Image, repoDigests []string) error { + if image.Digest == "" { + return nil + } + + if len(repoDigests) == 0 { + return fmt.Errorf("image %s has no repo digests", image) + } + + for _, repoDigest := range repoDigests { + // RepoDigests are of the form 'imageName@sha256:' or imageName: + // We only care about the ones in digest form + ref, err := reference.ParseNormalizedNamed(repoDigest) + if err != nil { + return fmt.Errorf("unable to parse repo digest %s", repoDigest) + } + + digestRef, ok := ref.(reference.Digested) + if !ok { + continue + } + + digest := digestRef.Digest().String() + + // image.Digest is the digest of the original invocation image defined in the bundle. + // It persists even when the bundle's invocation image has been relocated. + if digest == image.Digest { + return nil + } + } + + return fmt.Errorf("content digest mismatch: image %s was defined with the digest %s, but no matching repoDigest was found upon inspecting the image", image.Ref, image.Digest) +} diff --git a/pkg/toi/drivers/docker/opts.go b/api/ocm/tools/toi/drivers/docker/opts.go similarity index 100% rename from pkg/toi/drivers/docker/opts.go rename to api/ocm/tools/toi/drivers/docker/opts.go diff --git a/api/ocm/tools/toi/drivers/filesystem/driver.go b/api/ocm/tools/toi/drivers/filesystem/driver.go new file mode 100644 index 000000000..ae3aaffdd --- /dev/null +++ b/api/ocm/tools/toi/drivers/filesystem/driver.go @@ -0,0 +1,92 @@ +package filesystem + +import ( + "io" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/finalizer" + "github.com/mandelsoft/vfs/pkg/osfs" + "github.com/mandelsoft/vfs/pkg/vfs" + "sigs.k8s.io/yaml" + + "ocm.software/ocm/api/ocm/tools/toi/install" +) + +const OptionTargetPath = "TARGET_PATH" + +// Driver is capable of running Docker invocation images using Docker itself. +type Driver struct { + config map[string]string + Simulate bool + TargetPath string + Filesystem vfs.FileSystem +} + +var _ install.Driver = (*Driver)(nil) + +func New(fs vfs.FileSystem) install.Driver { + if fs == nil { + fs = osfs.New() + } + return &Driver{Filesystem: fs} +} + +// SetConfig sets Docker driver configuration. +func (d *Driver) SetConfig(settings map[string]string) error { + if settings != nil { + d.TargetPath = settings[OptionTargetPath] + } + + if d.TargetPath == "" { + d.TargetPath = "toi" + } + d.config = settings + return nil +} + +func (d *Driver) Exec(op *install.Operation) (*install.OperationResult, error) { + if d.Simulate { + return nil, nil + } + + err := d.Filesystem.MkdirAll(d.TargetPath, 0o700) + if err != nil { + return nil, errors.Wrapf(err, "creating target path") + } + + var finalize finalizer.Finalizer + defer finalize.Finalize() + + for k, v := range op.Files { + n := vfs.Join(d.Filesystem, d.TargetPath, install.Inputs, k) + err := d.Filesystem.MkdirAll(vfs.Dir(d.Filesystem, n), 0o700) + if err != nil { + return nil, errors.Wrapf(err, "creating directory for file %q", k) + } + r, err := v.Reader() + if err != nil { + return nil, errors.Wrapf(err, "reading data for %q", k) + } + finalize.Close(r) + file, err := d.Filesystem.OpenFile(n, vfs.O_TRUNC|vfs.O_CREATE|vfs.O_WRONLY, 0o600) + if err != nil { + return nil, errors.Wrapf(err, "writing file %q", n) + } + finalize.Close(file) + _, err = io.Copy(file, r) + if err != nil { + return nil, errors.Wrapf(err, "writing %q", n) + } + finalize.Finalize() + } + props := map[string]string{} + props["image"] = op.Image.String() + props["componentVersion"] = op.ComponentVersion + props["action"] = op.Action + data, err := yaml.Marshal(props) + if err != nil { + return nil, errors.Wrapf(err, "writing operation properties") + } + vfs.WriteFile(d.Filesystem, vfs.Join(d.Filesystem, d.TargetPath, "properties"), data, 0o600) + return &install.OperationResult{}, nil +} diff --git a/api/ocm/tools/toi/drivers/mock/driver.go b/api/ocm/tools/toi/drivers/mock/driver.go new file mode 100644 index 000000000..034bee5cb --- /dev/null +++ b/api/ocm/tools/toi/drivers/mock/driver.go @@ -0,0 +1,27 @@ +package mock + +import ( + "ocm.software/ocm/api/ocm/tools/toi/install" + "ocm.software/ocm/api/utils" +) + +type Driver struct { + handler func(*install.Operation) (*install.OperationResult, error) +} + +var _ install.Driver = (*Driver)(nil) + +func New(handler ...func(*install.Operation) (*install.OperationResult, error)) install.Driver { + return &Driver{utils.Optional(handler...)} +} + +func (d *Driver) SetConfig(props map[string]string) error { + return nil +} + +func (d *Driver) Exec(op *install.Operation) (*install.OperationResult, error) { + if d.handler != nil { + return d.handler(op) + } + return &install.OperationResult{}, nil +} diff --git a/pkg/toi/install/action.go b/api/ocm/tools/toi/install/action.go similarity index 91% rename from pkg/toi/install/action.go rename to api/ocm/tools/toi/install/action.go index f1f1952b4..e901b97e2 100644 --- a/pkg/toi/install/action.go +++ b/api/ocm/tools/toi/install/action.go @@ -13,26 +13,26 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" "github.com/xeipuuv/gojsonschema" - "github.com/open-component-model/ocm/pkg/blobaccess" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/common/accessobj" - globalconfig "github.com/open-component-model/ocm/pkg/contexts/config/config" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/logforward" - logcfg "github.com/open-component-model/ocm/pkg/contexts/datacontext/config/logging" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/spiff" - "github.com/open-component-model/ocm/pkg/toi" - utils2 "github.com/open-component-model/ocm/pkg/utils" + globalconfig "ocm.software/ocm/api/config/extensions/config" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext/attrs/logforward" + logcfg "ocm.software/ocm/api/datacontext/config/logging" + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + utils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/ocm/tools/toi" + "ocm.software/ocm/api/ocm/tools/transfer" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + utils2 "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/mime" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/api/utils/spiff" ) func ValidateByScheme(src []byte, schemedata []byte) error { diff --git a/api/ocm/tools/toi/install/action_test.go b/api/ocm/tools/toi/install/action_test.go new file mode 100644 index 000000000..417e3148f --- /dev/null +++ b/api/ocm/tools/toi/install/action_test.go @@ -0,0 +1,294 @@ +package install_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + . "ocm.software/ocm/api/helper/env" + + "github.com/mandelsoft/vfs/pkg/memoryfs" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/identity/hostpath" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/tools/toi" + "ocm.software/ocm/api/ocm/tools/toi/drivers/mock" + "ocm.software/ocm/api/ocm/tools/toi/install" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/mime" + common "ocm.software/ocm/api/utils/misc" +) + +const ( + COMPONENT = "acme.org/test" + VERSION = "0.1.0" +) + +type Driver struct { + install.Driver + Found *install.Operation +} + +func NewDriver() *Driver { + driver := &Driver{} + driver.Driver = mock.New(func(op *install.Operation) (*install.OperationResult, error) { + driver.Found = op + return &install.OperationResult{}, nil + }) + return driver +} + +var _ = Describe("Transfer handler", func() { + var env *Builder + var driver *Driver + + cid1 := credentials.NewConsumerIdentity("test", hostpath.ID_HOSTNAME, "test.de") + creds1 := credentials.NewCredentials(common.Properties{"user": "test", "password": "pw"}) + + BeforeEach(func() { + env = NewBuilder(FileSystem(memoryfs.New(), "")) + + env.OCMCommonTransport("ctf", accessio.FormatDirectory, func() { + env.ComponentVersion(COMPONENT, VERSION, func() { + env.Provider("acme.org") + env.Resource("package", VERSION, toi.TypeTOIPackage, v1.LocalRelation, func() { + env.BlobData(mime.MIME_YAML, []byte("")) + }) + }) + }) + + driver = NewDriver() + }) + + It("gets credentials", func() { + env.CredentialsContext().SetCredentialsForConsumer(cid1, creds1) + + c := Must(credentials.CredentialsForConsumer(env.OCMContext().CredentialsContext(), cid1)) + Expect(c.Properties()).To(Equal(creds1.Properties())) + }) + + It("executes with credential substitution", func() { + env.CredentialsContext().SetCredentialsForConsumer(cid1, creds1) + + p, _ := common.NewBufferedPrinter() + + mapping := ` +testparam: (( merge )) +creds: (( hasCredentials("mycred") ? [getCredentials("mycred")] :[] )) +` + spec := &toi.PackageSpecification{ + CredentialsRequest: toi.CredentialsRequest{ + Credentials: map[string]toi.CredentialsRequestSpec{ + "mycred": { + ConsumerId: cid1, + Description: "test", + Optional: false, + }, + }, + }, + Executors: []toi.Executor{ + { + Actions: []string{"install"}, + Image: &toi.Image{ + Ref: "a/b:v1", + }, + ParameterMapping: []byte(mapping), + }, + }, + } + + credspec := &toi.Credentials{ + Credentials: map[string]toi.CredentialSpec{ + "mycred": { + ConsumerId: cid1, + }, + }, + } + + params := ` +testparam: value +` + + repo := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, "/ctf", 0, env)) + defer Close(repo) + cv := Must(repo.LookupComponentVersion(COMPONENT, VERSION)) + defer Close(cv) + + Must(install.ExecuteAction(p, driver, "install", spec, credspec, []byte(params), env, cv, nil)) + + effparams := Must(driver.Found.Files[install.InputParameters].Get()) + Expect(string(effparams)).To(StringEqualTrimmedWithContext(` +creds: +- password: pw + user: test +testparam: value +`)) + }) + + It("executes with credential property substitution", func() { + env.CredentialsContext().SetCredentialsForConsumer(cid1, creds1) + + p, _ := common.NewBufferedPrinter() + + mapping := ` +testparam: (( merge )) +creds: (( hasCredentials("mycred") ? getCredentials("mycred", "user") :"" )) +` + spec := &toi.PackageSpecification{ + CredentialsRequest: toi.CredentialsRequest{ + Credentials: map[string]toi.CredentialsRequestSpec{ + "mycred": { + ConsumerId: cid1, + Description: "test", + Optional: false, + }, + }, + }, + Executors: []toi.Executor{ + { + Actions: []string{"install"}, + Image: &toi.Image{ + Ref: "a/b:v1", + }, + ParameterMapping: []byte(mapping), + }, + }, + } + + credspec := &toi.Credentials{ + Credentials: map[string]toi.CredentialSpec{ + "mycred": { + ConsumerId: cid1, + }, + }, + } + + params := ` +testparam: value +` + + repo := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, "/ctf", 0, env)) + defer Close(repo) + cv := Must(repo.LookupComponentVersion(COMPONENT, VERSION)) + defer Close(cv) + + Must(install.ExecuteAction(p, driver, "install", spec, credspec, []byte(params), env, cv, nil)) + + effparams := Must(driver.Found.Files[install.InputParameters].Get()) + Expect(string(effparams)).To(StringEqualTrimmedWithContext(` +creds: test +testparam: value +`)) + }) + + It("executes with single credential property substitution", func() { + creds1 := credentials.NewCredentials(common.Properties{"user": "test"}) + + env.CredentialsContext().SetCredentialsForConsumer(cid1, creds1) + + p, _ := common.NewBufferedPrinter() + + mapping := ` +testparam: (( merge )) +creds: (( hasCredentials("mycred") ? getCredentials("mycred", "*") :"" )) +` + spec := &toi.PackageSpecification{ + CredentialsRequest: toi.CredentialsRequest{ + Credentials: map[string]toi.CredentialsRequestSpec{ + "mycred": { + ConsumerId: cid1, + Description: "test", + Optional: false, + }, + }, + }, + Executors: []toi.Executor{ + { + Actions: []string{"install"}, + Image: &toi.Image{ + Ref: "a/b:v1", + }, + ParameterMapping: []byte(mapping), + }, + }, + } + + credspec := &toi.Credentials{ + Credentials: map[string]toi.CredentialSpec{ + "mycred": { + ConsumerId: cid1, + }, + }, + } + + params := ` +testparam: value +` + + repo := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, "/ctf", 0, env)) + defer Close(repo) + cv := Must(repo.LookupComponentVersion(COMPONENT, VERSION)) + defer Close(cv) + + Must(install.ExecuteAction(p, driver, "install", spec, credspec, []byte(params), env, cv, nil)) + + effparams := Must(driver.Found.Files[install.InputParameters].Get()) + Expect(string(effparams)).To(StringEqualTrimmedWithContext(` +creds: test +testparam: value +`)) + }) + + It("executes with optional credential substitution without credentials", func() { + env.CredentialsContext().SetCredentialsForConsumer(cid1, creds1) + + p, _ := common.NewBufferedPrinter() + + mapping := ` +testparam: (( merge )) +creds: (( hasCredentials("mycred") ? [getCredentials("mycred")] :[] )) +` + spec := &toi.PackageSpecification{ + CredentialsRequest: toi.CredentialsRequest{ + Credentials: map[string]toi.CredentialsRequestSpec{ + "mycred": { + ConsumerId: cid1, + Description: "test", + Optional: true, + }, + }, + }, + Executors: []toi.Executor{ + { + Actions: []string{"install"}, + Image: &toi.Image{ + Ref: "a/b:v1", + }, + ParameterMapping: []byte(mapping), + }, + }, + } + + credspec := &toi.Credentials{} + + params := ` +testparam: value +` + + repo := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, "/ctf", 0, env)) + defer Close(repo) + cv := Must(repo.LookupComponentVersion(COMPONENT, VERSION)) + defer Close(cv) + + Must(install.ExecuteAction(p, driver, "install", spec, credspec, []byte(params), env, cv, nil)) + + effparams := Must(driver.Found.Files[install.InputParameters].Get()) + Expect(string(effparams)).To(StringEqualTrimmedWithContext(` +creds: [] +testparam: value +`)) + }) +}) diff --git a/api/ocm/tools/toi/install/bundle/spec.go b/api/ocm/tools/toi/install/bundle/spec.go new file mode 100644 index 000000000..7cfeb127a --- /dev/null +++ b/api/ocm/tools/toi/install/bundle/spec.go @@ -0,0 +1,32 @@ +package bundle + +import ( + "encoding/json" + + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/tools/toi" + "ocm.software/ocm/api/ocm/tools/toi/install" +) + +type BundleSpecification struct { + install.CredentialsRequest `json:",inline"` + Template json.RawMessage `json:"configTemplate,omitempty"` + Libraries []metav1.ResourceReference `json:"templateLibraries,omitempty"` + Scheme json.RawMessage `json:"configScheme,omitempty"` + + Actions []string `json:"actions,omitempty"` + Outputs map[string]string `json:"outputs,omitempty"` +} + +type InstallationSpecification struct { + ResourceRef *metav1.ResourceReference `json:"resourceRef,omitempty"` + Image *toi.Image `json:"image,omitempty"` + + Actions map[string]string `json:"actions,omitempty"` + Required map[string]string `json:"required,omitempty"` +} + +type InstallationValues struct { + install.Credentials `json:",inline"` + Settings json.RawMessage `json:"values,omitempty"` +} diff --git a/api/ocm/tools/toi/install/credentials.go b/api/ocm/tools/toi/install/credentials.go new file mode 100644 index 000000000..78988a917 --- /dev/null +++ b/api/ocm/tools/toi/install/credentials.go @@ -0,0 +1,185 @@ +package install + +import ( + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/spiff/features" + "github.com/mandelsoft/spiff/spiffing" + + globalconfig "ocm.software/ocm/api/config/extensions/config" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/config" + "ocm.software/ocm/api/credentials/extensions/repositories/directcreds" + "ocm.software/ocm/api/credentials/extensions/repositories/memory" + memorycfg "ocm.software/ocm/api/credentials/extensions/repositories/memory/config" + "ocm.software/ocm/api/ocm/tools/toi" + "ocm.software/ocm/api/utils" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" +) + +type ( + Credentials = toi.Credentials + CredentialSpec = toi.CredentialSpec + CredentialsRequest = toi.CredentialsRequest + CredentialsRequestSpec = toi.CredentialsRequestSpec +) + +type CredentialValues map[string]common.Properties + +func ParseCredentialSpecification(data []byte, desc string) (*Credentials, error) { + spiff := spiffing.New().WithFeatures(features.CONTROL, features.INTERPOLATION) + + templ, err := spiff.Unmarshal(desc, data) + if err != nil { + return nil, errors.Newf("invalid credential settings: %s", err) + } + + cfg, err := spiff.Cascade(templ, nil) + if err != nil { + return nil, errors.Wrapf(err, "error processing credential settings") + } + final, err := spiff.Marshal(cfg) + if err != nil { + return nil, errors.Wrapf(err, "credential marshalling") + } + var spec Credentials + + err = runtime.DefaultYAMLEncoding.Unmarshal(final, &spec) + if err != nil { + return nil, errors.Wrapf(err, "credentials settings") + } + return &spec, nil +} + +func ParseCredentialRequest(data []byte) (*CredentialsRequest, error) { + var req CredentialsRequest + + err := runtime.DefaultYAMLEncoding.Unmarshal(data, &req) + if err != nil { + return nil, errors.Wrapf(err, "cannot parse credential request") + } + return &req, err +} + +func GetCredentials(ctx credentials.Context, spec *Credentials, req map[string]CredentialsRequestSpec, mapping map[string]string) (*globalconfig.Config, CredentialValues, error) { + cfg := config.New() + mem := memorycfg.New("default") + memrepo := memory.NewRepositorySpec("default") + list := errors.ErrListf("providing requested credentials") + + credvalues := CredentialValues{} + var sub *errors.ErrorList + for _, n := range utils.StringMapKeys(req) { + r := req[n] + list.Add(sub.Result()) + sub = errors.ErrListf("credential request %q", n) + found, ok := spec.Credentials[n] + if !ok { + if !r.Optional { + sub.Add(errors.ErrNotFound("credential", n)) + } + continue + } + creds, consumer, err := evaluate(ctx, &found) + if err != nil { + sub.Add(errors.Wrapf(err, "failed to evaluate")) + continue + } + mapped := n + if mapping != nil { + mapped = mapping[n] + } + if mapped == "" { + return nil, nil, errors.Newf("mapping missing credential %q", n) + } + credvalues[mapped] = creds + err = mem.AddCredentials(mapped, creds) + if err != nil { + sub.Add(errors.Wrapf(err, "failed to add credentials")) + continue + } + if len(consumer) != 0 { + err = cfg.AddConsumer(consumer, credentials.NewCredentialsSpec(mapped, memrepo)) + if err != nil { + sub.Add(errors.Newf("failed to add consumer %s from config", consumer)) + continue + } + } + if len(r.ConsumerId) != 0 { + err = cfg.AddConsumer(r.ConsumerId, credentials.NewCredentialsSpec(mapped, memrepo)) + if err != nil { + sub.Add(errors.Newf("failed to add consumer %s from request", consumer)) + continue + } + } + } + for _, r := range spec.Forwarded { + if len(r.ConsumerId) == 0 { + return nil, nil, errors.ErrInvalid("consumer", r.ConsumerId.String()) + } + match := ctx.ConsumerIdentityMatchers().Get(r.ConsumerType) + if match == nil { + match = credentials.PartialMatch + } + src, err := ctx.GetCredentialsForConsumer(r.ConsumerId, match) + if err != nil || src == nil { + return nil, nil, errors.ErrNotFoundWrap(err, "consumer", r.ConsumerId.String()) + } + if src == nil { + return nil, nil, errors.ErrNotFoundWrap(err, "consumer", r.ConsumerId.String()) + } + creds, err := src.Credentials(ctx) + if err != nil { + return nil, nil, errors.Wrapf(err, "cannot get credentials for %s", r.ConsumerId.String()) + } + props := creds.Properties() + cfg.AddConsumer(r.ConsumerId, directcreds.NewCredentials(props)) + } + + list.Add(sub.Result()) + main := globalconfig.New() + main.AddConfig(mem) + main.AddConfig(cfg) + return main, credvalues, list.Result() +} + +func evaluate(ctx credentials.Context, spec *CredentialSpec) (common.Properties, credentials.ConsumerIdentity, error) { + var err error + var props common.Properties + var src credentials.CredentialsSource + cnt := 0 + if len(spec.Credentials) > 0 { + cnt++ + props = spec.Credentials + } + if spec.Reference != nil { + cnt++ + src, err = spec.Reference.Credentials(ctx) + if err != nil { + return nil, nil, errors.Wrapf(err, "cannot evaluate credential reference") + } + } + if spec.ConsumerId != nil { + cnt++ + match := ctx.ConsumerIdentityMatchers().Get(spec.ConsumerType) + if match == nil { + match = credentials.PartialMatch + } + src, err = ctx.GetCredentialsForConsumer(spec.ConsumerId, match) + if err != nil { + return nil, nil, errors.ErrNotFoundWrap(err, "consumer", spec.ConsumerId.String()) + } + } + if cnt > 1 { + return nil, nil, errors.Newf("only one of consumer id or reference or credentials possible") + } + if src != nil { + creds, err := src.Credentials(ctx) + if err != nil { + return nil, nil, errors.Wrapf(err, "cannot get credentials for %s", spec.ConsumerId.String()) + } + props = creds.Properties() + } + + return props, spec.TargetConsumerId, nil +} diff --git a/pkg/toi/install/credentials_test.go b/api/ocm/tools/toi/install/credentials_test.go similarity index 84% rename from pkg/toi/install/credentials_test.go rename to api/ocm/tools/toi/install/credentials_test.go index 07a6c9804..c4daf6f85 100644 --- a/pkg/toi/install/credentials_test.go +++ b/api/ocm/tools/toi/install/credentials_test.go @@ -5,15 +5,15 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/config" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/credentials/identity/hostpath" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/directcreds" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/memory" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/toi/install" + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/credentials/extensions/repositories/directcreds" + "ocm.software/ocm/api/credentials/extensions/repositories/memory" + "ocm.software/ocm/api/credentials/identity/hostpath" + "ocm.software/ocm/api/ocm/tools/toi/install" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" ) var _ = Describe("credential mapping", func() { diff --git a/pkg/toi/install/execute.go b/api/ocm/tools/toi/install/execute.go similarity index 76% rename from pkg/toi/install/execute.go rename to api/ocm/tools/toi/install/execute.go index 108ed0d00..31405ae9a 100644 --- a/pkg/toi/install/execute.go +++ b/api/ocm/tools/toi/install/execute.go @@ -3,12 +3,12 @@ package install import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" - "github.com/open-component-model/ocm/pkg/toi" + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + utils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/ocm/tools/toi" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + common "ocm.software/ocm/api/utils/misc" ) func Execute(p common.Printer, d Driver, name string, rid metav1.Identity, credsrc blobaccess.DataSource, paramsrc blobaccess.DataSource, octx ocm.Context, cv ocm.ComponentVersionAccess, resolver ocm.ComponentVersionResolver) (*OperationResult, error) { diff --git a/pkg/toi/install/functions.go b/api/ocm/tools/toi/install/functions.go similarity index 97% rename from pkg/toi/install/functions.go rename to api/ocm/tools/toi/install/functions.go index bd928ceda..b0ca906dc 100644 --- a/pkg/toi/install/functions.go +++ b/api/ocm/tools/toi/install/functions.go @@ -5,7 +5,7 @@ import ( "github.com/mandelsoft/spiff/spiffing" "github.com/mandelsoft/spiff/yaml" - "github.com/open-component-model/ocm/pkg/contexts/ocm" + "ocm.software/ocm/api/ocm" ) func NewFunctions(ctx ocm.Context, credvals CredentialValues) spiffing.Functions { diff --git a/api/ocm/tools/toi/install/interface.go b/api/ocm/tools/toi/install/interface.go new file mode 100644 index 000000000..3c96bdd77 --- /dev/null +++ b/api/ocm/tools/toi/install/interface.go @@ -0,0 +1,56 @@ +package install + +import ( + "io" + + "ocm.software/ocm/api/ocm/tools/toi" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +const ( + PathTOI = "/toi" + Inputs = "inputs" + Outputs = "outputs" + PathExec = PathTOI + "/run" + PathOutputs = PathTOI + "/" + Outputs + PathInputs = PathTOI + "/" + Inputs + InputParameters = "parameters" + InputConfig = "config" + InputOCMConfig = "ocmconfig" + InputOCMRepo = "ocmrepo" +) + +type Driver interface { + SetConfig(props map[string]string) error + Exec(op *Operation) (*OperationResult, error) +} + +// Operation describes the data passed into the driver to run an operation. +type Operation struct { + // Action is the action to be performed. It is passed a srgument to the executable + Action string + // ComponentVersion is the name of the root component/version to install + ComponentVersion string + // Image is the image to invoke + Image toi.Image + // Environment contains environment variables that should be injected into the container execution + Environment map[string]string + // Files contains files that should be injected into the invocation image. + Files map[string]blobaccess.BlobAccess + // Outputs map of output (sub)paths (e.g. NAME) to the name of the output. + // Indicates which outputs the driver should return the contents of in the OperationResult. + Outputs map[string]string + // Output stream for log messages from the driver + Out io.Writer + // Output stream for error messages from the driver + Err io.Writer +} + +// OperationResult is the output of the Driver running an Operation. +type OperationResult struct { + // Outputs maps from the name of the output to its content. + Outputs map[string][]byte + + // Error is any errors from executing the operation. + Error error +} diff --git a/pkg/toi/install/suite_test.go b/api/ocm/tools/toi/install/suite_test.go similarity index 100% rename from pkg/toi/install/suite_test.go rename to api/ocm/tools/toi/install/suite_test.go diff --git a/api/ocm/tools/toi/logging.go b/api/ocm/tools/toi/logging.go new file mode 100644 index 000000000..dae6bacdd --- /dev/null +++ b/api/ocm/tools/toi/logging.go @@ -0,0 +1,9 @@ +package toi + +import ( + logging2 "ocm.software/ocm/api/utils/logging" +) + +var REALM = logging2.DefineSubRealm("TOI logging", "toi") + +var Log = logging2.DynamicLogger(REALM) diff --git a/api/ocm/tools/toi/spec.go b/api/ocm/tools/toi/spec.go new file mode 100644 index 000000000..f4c30b1ee --- /dev/null +++ b/api/ocm/tools/toi/spec.go @@ -0,0 +1,165 @@ +package toi + +import ( + "encoding/json" + "fmt" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/cpi" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + common "ocm.software/ocm/api/utils/misc" +) + +const ( + TypeTOIPackage = "toiPackage" + PackageSpecificationMimeType = "application/vnd.toi.ocm.software.package.v1+yaml" + + TypeYAML = resourcetypes.OCM_YAML + + AdditionalResourceConfigFile = "configFile" + AdditionalResourceCredentialsFile = "credentialsFile" +) + +const ( + TypeTOIExecutor = "toiExecutor" + ExecutorSpecificationMimeType = "application/vnd.toi.ocm.software.executor.v1+yaml" +) + +type PackageSpecification struct { + CredentialsRequest `json:",inline"` + Template json.RawMessage `json:"configTemplate,omitempty"` + Libraries []metav1.ResourceReference `json:"templateLibraries,omitempty"` + Scheme json.RawMessage `json:"configScheme,omitempty"` + Executors []Executor `json:"executors"` + Description string `json:"description"` + AdditionalResources map[string]*AdditionalResource `json:"additionalResources,omitempty"` +} + +type AdditionalResource struct { + *metav1.ResourceReference `json:",inline"` + Content json.RawMessage `json:"content,omitempty"` +} + +type Executor struct { + Actions []string `json:"actions,omitempty"` + ResourceRef *metav1.ResourceReference `json:"resourceRef,omitempty"` + Image *Image `json:"image,omitempty"` + CredentialMapping map[string]string `json:"credentialMapping,omitempty"` + ParameterMapping json.RawMessage `json:"parameterMapping,omitempty"` + Config json.RawMessage `json:"config,omitempty"` + Outputs map[string]string `json:"outputs,omitempty"` +} + +func (e *Executor) Name() string { + if e.ResourceRef != nil { + return e.ResourceRef.String() + } + if e.Image != nil { + return e.Image.String() + } + return "unspecified executor" +} + +type Image struct { + Ref string `json:"ref"` + Digest string `json:"digest"` +} + +func (i *Image) String() string { + r := "" + if i.Ref != "" { + r = i.Ref + } + if i.Digest != "" { + r += "@" + i.Digest + } + return r +} + +//////////////////////////////////////////////////////////////////////////////// + +type ExecutorSpecification struct { + CredentialsRequest `json:",inline"` + Actions []string `json:"actions,omitempty"` + Image *Image `json:"image,omitempty"` + ImageRef *metav1.ResourceReference `json:"imageRef,omitempty"` + Template json.RawMessage `json:"configTemplate,omitempty"` + Libraries []metav1.ResourceReference `json:"templateLibraries,omitempty"` + Scheme json.RawMessage `json:"configScheme,omitempty"` + Outputs map[string]OutputSpec `json:"outputs,omitempty"` +} + +type OutputSpec struct { + Description string `json:"description,omitempty"` +} + +//////////////////////////////////////////////////////////////////////////////// + +type CredentialsRequest struct { + Credentials map[string]CredentialsRequestSpec `json:"credentials,omitempty"` +} + +type CredentialsRequestSpec struct { + // ConsumerId specified to consumer id the credentials are used for + ConsumerId credentials.ConsumerIdentity `json:"consumerId,omitempty"` + // Description described the usecase the credentials will be used for + Description string `json:"description"` + // Properties describes the meaning of the used properties for this + // credential set. + Properties common.Properties `json:"properties"` + // Optional set to true make the request optional + Optional bool `json:"optional,omitempty"` +} + +var ErrUndefined error = errors.New("nil reference") + +func (s *CredentialsRequestSpec) Match(o *CredentialsRequestSpec) error { + if o == nil { + return ErrUndefined + } + if !s.ConsumerId.Equals(o.ConsumerId) { + return fmt.Errorf("consumer id mismatch") + } + for k := range o.Properties { + if _, ok := s.Properties[k]; !ok { + return fmt.Errorf("property %q not declared", k) + } + } + if s.Optional && !o.Optional { + return fmt.Errorf("cannot be optional") + } + return nil +} + +type Credentials struct { + Credentials map[string]CredentialSpec `json:"credentials,omitempty"` + + // Forwarded may define a list of consumer ids, which should be taken from the + // local configuration and forwarded to the TOI executor in addition to the + // credentials explicitly requested by the installation package. + Forwarded []ForwardSpec `json:"forwardedConsumers,omitempty"` +} + +type CredentialSpec struct { + // ConsumerId specifies the consumer id to look for the credentials + ConsumerId credentials.ConsumerIdentity `json:"consumerId,omitempty"` + // ConsumerType is the optional type used for matching the credentials + ConsumerType string `json:"consumerType,omitempty"` + // Reference refers to credentials store in some other repo + Reference *cpi.GenericCredentialsSpec `json:"reference,omitempty"` + // Credentials are direct credentials (one of Reference or Credentials must be set) + Credentials common.Properties `json:"credentials,omitempty"` + + // TargetConsumerId specifies the consumer id to feed with these credentials + TargetConsumerId credentials.ConsumerIdentity `json:"targetConsumerId,omitempty"` +} + +type ForwardSpec struct { + // ConsumerId specifies the consumer id to look for the credentials + ConsumerId credentials.ConsumerIdentity `json:"consumerId"` + // ConsumerType is the optional type used for matching the credentials + ConsumerType string `json:"consumerType,omitempty"` +} diff --git a/api/ocm/tools/toi/support/README.md b/api/ocm/tools/toi/support/README.md new file mode 100644 index 000000000..462c06e9c --- /dev/null +++ b/api/ocm/tools/toi/support/README.md @@ -0,0 +1,49 @@ +### Support for TOI Executors + +This package provides a generic command line tool support to provide +TOI executor CLIs. + +Such a CLI wraps an executor specific executor function. If no options +are passed it complies to the TOI image binding contract. + +For development purposes it can be called with a bunch of option to fake +the file system binding and redirect it to explicitly specified files. + +It provides a contract to the executor function of type + +
+
+func(options *ExecutorOptions) error
+
+
+ + which already reolves all those dependencies by providing an + [ExecutorOptions](support.go#:~:text=type%20ExecutorOptions%20struct,%7D) + object with the prepared contract data, including access to to + the `ocm.ComponentVersionAccess` of the component version providing + the package to work on. + + A typical `main` function of an executor could then look like: + +
+     package main
+
+     import (
+        "os"
+
+        clictx "ocm.software/ocm/api/cli"
+
+        "yourpackage"
+     )
+
+     func main() {
+        ctx:=clictx.New()
+        // special configuration of the context. e.g. setting a virstual filesystem
+        c := support.NewCLICommand(ctx.OCMContext(), "your executor name", yourpackage.ExecutorFunction)
+        if err := c.Execute(); err != nil {
+            os.Exit(1)
+        }
+     }
+ 
+ + diff --git a/api/ocm/tools/toi/support/app.go b/api/ocm/tools/toi/support/app.go new file mode 100644 index 000000000..cdefa9a44 --- /dev/null +++ b/api/ocm/tools/toi/support/app.go @@ -0,0 +1,171 @@ +package support + +import ( + "fmt" + "strings" + + _ "ocm.software/ocm/api/cli/config" + + "github.com/mandelsoft/goutils/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + datactg "ocm.software/ocm/api/datacontext/config/attrs" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/tools/toi" + "ocm.software/ocm/api/ocm/tools/toi/install" + "ocm.software/ocm/api/utils/clisupport" + "ocm.software/ocm/api/utils/cobrautils" + "ocm.software/ocm/api/utils/cobrautils/logopts" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/api/version" +) + +type BootstrapperCLIOptions struct { + ExecutorOptions + logopts.Options + CredentialSettings []string + Settings []string +} + +func NewCLICommand(ctx ocm.Context, name string, exec func(options *ExecutorOptions) error) *cobra.Command { + if ctx == nil { + ctx = ocm.DefaultContext() + } + opts := &BootstrapperCLIOptions{ + ExecutorOptions: ExecutorOptions{ + Context: ctx, + }, + } + cmd := &cobra.Command{ + Use: name + " {} [] ", + Short: "Bootstrapper using the OCM bootstrap mechanism", + Version: version.Get().String(), + TraverseChildren: true, + SilenceUsage: true, + DisableFlagsInUseLine: true, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + action := "" + if len(args) > 0 { + if len(args) > 1 { + action = args[0] + opts.ComponentVersionName = args[1] + } else { + opts.ComponentVersionName = args[0] + } + } + opts.Action = action + return opts.Complete() + }, + RunE: func(cmd *cobra.Command, args []string) error { + out.Outf(opts.OutputContext, "This is %s (%s)\n", name, version.Get().String()) + e := &Executor{Completed: true, Options: &opts.ExecutorOptions, Run: exec} + return e.Execute() + }, + } + cobrautils.TweakCommand(cmd, nil) + + cmd.AddCommand(NewVersionCommand()) + opts.AddFlags(cmd.Flags()) + cmd.InitDefaultHelpCmd() + + return cmd +} + +func (o *BootstrapperCLIOptions) AddFlags(fs *pflag.FlagSet) { + o.Options.AddFlags(fs) + fs.StringVarP(&o.OCMConfig, "ocmconfig", "", "", "ocm configuration file") + fs.StringArrayVarP(&o.CredentialSettings, "cred", "C", nil, "credential setting") + fs.StringArrayVarP(&o.Settings, "attribute", "X", nil, "attribute setting") + + fs.StringVarP(&o.Inputs, "inputs", "", "", "input path") + fs.StringVarP(&o.Outputs, "outputs", "", "", "output path") + fs.StringVarP(&o.Root, "bootstraproot", "", install.PathTOI, "bootstrapper contract root folder") + fs.StringVarP(&o.Config, "config", "", "", "bootstrapper configuration input file") + fs.StringVarP(&o.Parameters, "parameters", "", "", "bootstrapper parameter input file") + fs.StringVarP(&o.RepoPath, "ctf", "", "", "bootstrapper transport archive") +} + +func (o *BootstrapperCLIOptions) Complete() error { + o.Options.Configure(o.Context, o.Context.LoggingContext()) + if err := o.ExecutorOptions.Complete(); err != nil { + return fmt.Errorf("unable to complete options: %w", err) + } + + id := credentials.ConsumerIdentity{} + attrs := common.Properties{} + + for _, s := range o.CredentialSettings { + i := strings.Index(s, "=") + if i < 0 { + return errors.ErrInvalid("credential setting", s) + } + + name := s[:i] + value := s[i+1:] + + if strings.HasPrefix(name, ":") { + if len(attrs) != 0 { + o.Context.CredentialsContext().SetCredentialsForConsumer(id, credentials.NewCredentials(attrs)) + id = credentials.ConsumerIdentity{} + attrs = common.Properties{} + } + name = name[1:] + id[name] = value + } else { + attrs[name] = value + } + + if len(name) == 0 { + return errors.ErrInvalid("credential setting", s) + } + } + + if len(attrs) != 0 { + o.Context.CredentialsContext().SetCredentialsForConsumer(id, credentials.NewCredentials(attrs)) + } else if len(id) != 0 { + return errors.Newf("empty credential attribute set for %s", id.String()) + } + + set, err := clisupport.ParseLabels(vfsattr.Get(o.Context), o.Settings, "attribute setting") + if err == nil && len(set) > 0 { + ctx := o.Context.ConfigContext() + spec := datactg.New() + for _, s := range set { + attr := s.Name + eff := datacontext.DefaultAttributeScheme.Shortcuts()[attr] + if eff != "" { + attr = eff + } + err = spec.AddRawAttribute(attr, s.Value) + if err != nil { + return errors.Wrapf(err, "attribute %s", s.Name) + } + } + err = ctx.ApplyConfig(spec, "cli") + } + + if err != nil { + return fmt.Errorf("unable to parse labels: %w", err) + } + + o.Logger = toi.Log + return nil +} + +func NewVersionCommand() *cobra.Command { + return &cobra.Command{ + Use: "version", + Aliases: []string{"v"}, + Short: "displays the version", + Run: func(cmd *cobra.Command, args []string) { + v := version.Get() + //nolint:forbidigo // It's an intentional Printf. + fmt.Printf("%#v", v) + }, + } +} diff --git a/api/ocm/tools/toi/support/support.go b/api/ocm/tools/toi/support/support.go new file mode 100644 index 000000000..5eeb8ffc0 --- /dev/null +++ b/api/ocm/tools/toi/support/support.go @@ -0,0 +1,198 @@ +package support + +import ( + "fmt" + "io" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/logging" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/extensions/repositories/memory" + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + ocmutils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/ocm/tools/toi/install" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/out" +) + +type ExecutorOptions struct { + Context ocm.Context + Logger logging.Logger + OutputContext out.Context + Action string + ComponentVersionName string + Root string + Inputs string + Outputs string + OCMConfig string + Config string + ConfigData []byte + Parameters string + ParameterData []byte + RepoPath string + Repository ocm.Repository + CredentialRepo credentials.Repository + ComponentVersion ocm.ComponentVersionAccess + Closer func() error +} + +func (o *ExecutorOptions) FileSystem() vfs.FileSystem { + return vfsattr.Get(o.Context) +} + +func (o *ExecutorOptions) Complete() error { + if o.ComponentVersionName == "" { + return fmt.Errorf("component version required") + } + compvers, err := common.ParseNameVersion(o.ComponentVersionName) + if err != nil { + return fmt.Errorf("unable to parse component name and version: %w", err) + } + + if o.OutputContext == nil { + o.OutputContext = out.New() + } + + if o.Action == "" { + o.Action = "install" + } + + if o.Root == "" { + o.Root = install.PathTOI + } + + if o.Inputs == "" { + o.Inputs = o.Root + "/" + install.Inputs + } + + if o.Outputs == "" { + o.Outputs = o.Root + "/" + install.Outputs + } + + if o.RepoPath == "" { + o.RepoPath = o.Inputs + "/" + install.InputOCMRepo + } + + if o.Config == "" { + cfg := o.Inputs + "/" + install.InputConfig + if ok, err := vfs.FileExists(o.FileSystem(), cfg); ok && err == nil { + o.Config = cfg + } + } + + if o.Config != "" && o.ConfigData == nil { + o.ConfigData, err = utils.ReadFile(o.Config, o.FileSystem()) + if err != nil { + return errors.Wrapf(err, "cannot read config %q", o.Config) + } + } + + if o.OCMConfig == "" { + cfg, err := utils.ResolvePath(o.Inputs + "/" + install.InputOCMConfig) + if err != nil { + return errors.Wrapf(err, "cannot resolve OCM config %q", o.Inputs) + } + if ok, err := vfs.FileExists(o.FileSystem(), cfg); ok && err == nil { + o.OCMConfig = cfg + } + } + + o.Context, err = ocmutils.Configure(o.Context, o.OCMConfig) + if err != nil { + return fmt.Errorf("unable to configure context: %w", err) + } + + if o.Parameters == "" { + p, err := utils.ResolvePath(o.Inputs + "/" + install.InputParameters) + if err != nil { + return errors.Wrapf(err, "cannot resolve path %q", o.Inputs) + } + if ok, err := vfs.FileExists(o.FileSystem(), p); ok && err == nil { + o.Parameters = p + } + } + + if o.Parameters != "" && o.ParameterData == nil { + o.ParameterData, err = utils.ReadFile(o.Parameters, o.FileSystem()) + if err != nil { + return errors.Wrapf(err, "cannot read parameters %q", o.Config) + } + } + + var repoCloser io.Closer + if o.Repository == nil { + repo, err := ctf.Open(o.Context, accessobj.ACC_READONLY, o.RepoPath, 0, accessio.PathFileSystem(o.FileSystem())) + if err != nil { + return errors.Wrapf(err, "cannot open ctf %q", o.RepoPath) + } + o.Repository = repo + repoCloser = repo + } + + var versCloser io.Closer + + if o.ComponentVersion == nil { + cv, err := o.Repository.LookupComponentVersion(compvers.GetName(), compvers.GetVersion()) + if err != nil { + return fmt.Errorf("failed component version lookup: %w", err) + } + o.ComponentVersion = cv + versCloser = cv + } + + old := o.Closer + o.Closer = func() error { + list := errors.ErrListf("closing") + if versCloser != nil { + list.Add(errors.Wrapf(versCloser.Close(), "component version")) + } + if repoCloser != nil { + list.Add(errors.Wrapf(repoCloser.Close(), "repository")) + } + if old != nil { + list.Add(errors.Wrapf(old(), "external closer")) + } + return list.Result() + } + + if o.CredentialRepo == nil { + c, err := o.Context.CredentialsContext().RepositoryForSpec(memory.NewRepositorySpec("default")) + if err != nil { + return errors.Wrapf(err, "cannot get default memory based credential repository") + } + o.CredentialRepo = c + } + return nil +} + +type Executor struct { + Completed bool + Options *ExecutorOptions + Run func(o *ExecutorOptions) error +} + +func (e *Executor) Execute() error { + if e.Options == nil { + e.Completed = false + e.Options = &ExecutorOptions{} + } + if !e.Completed { + err := e.Options.Complete() + if err != nil { + return fmt.Errorf("unable to complete options: %w", err) + } + } + list := errors.ErrListf("executor:") + list.Add(e.Run(e.Options)) + if e.Options.Closer != nil { + list.Add(e.Options.Closer()) + } + return list.Result() +} diff --git a/pkg/contexts/ocm/transfer/autohandler.go b/api/ocm/tools/transfer/autohandler.go similarity index 94% rename from pkg/contexts/ocm/transfer/autohandler.go rename to api/ocm/tools/transfer/autohandler.go index f559c951b..8c6b04b5e 100644 --- a/pkg/contexts/ocm/transfer/autohandler.go +++ b/api/ocm/tools/transfer/autohandler.go @@ -5,8 +5,8 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" ) func init() { diff --git a/pkg/contexts/ocm/transfer/autohandler_test.go b/api/ocm/tools/transfer/autohandler_test.go similarity index 82% rename from pkg/contexts/ocm/transfer/autohandler_test.go rename to api/ocm/tools/transfer/autohandler_test.go index 8a627abed..7e1c664f2 100644 --- a/pkg/contexts/ocm/transfer/autohandler_test.go +++ b/api/ocm/tools/transfer/autohandler_test.go @@ -7,10 +7,10 @@ import ( "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" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" + "ocm.software/ocm/api/ocm/tools/transfer" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/spiff" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" ) type errorOption struct { diff --git a/api/ocm/tools/transfer/convenience.go b/api/ocm/tools/transfer/convenience.go new file mode 100644 index 000000000..d89bd589b --- /dev/null +++ b/api/ocm/tools/transfer/convenience.go @@ -0,0 +1,28 @@ +package transfer + +import ( + "ocm.software/ocm/api/ocm" + common "ocm.software/ocm/api/utils/misc" +) + +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// TransferWithHandler uses the specified transfer handler to control +// the transfer process. +func TransferWithHandler(pr common.Printer, cv ocm.ComponentVersionAccess, tgt ocm.Repository, handler TransferHandler) error { + return TransferVersion(pr, nil, cv, tgt, handler) +} + +// Transfer uses the transfer handler based on the given options to control +// the transfer process. The default handler is the standard handler. +func Transfer(cv ocm.ComponentVersionAccess, tgt ocm.Repository, optlist ...TransferOption) error { + h, err := NewTransferHandler(optlist...) + if err != nil { + return err + } + var local localOptions + err = local.Eval(optlist...) + if err != nil { + return err + } + return TransferWithHandler(local.printer, cv, tgt, h) +} diff --git a/pkg/contexts/ocm/transfer/debug.go b/api/ocm/tools/transfer/debug.go similarity index 100% rename from pkg/contexts/ocm/transfer/debug.go rename to api/ocm/tools/transfer/debug.go diff --git a/api/ocm/tools/transfer/init.go b/api/ocm/tools/transfer/init.go new file mode 100644 index 000000000..a68ad2ce9 --- /dev/null +++ b/api/ocm/tools/transfer/init.go @@ -0,0 +1,7 @@ +package transfer + +import ( + _ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/config" + _ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/spiff" + _ "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" +) diff --git a/api/ocm/tools/transfer/internal/merge.go b/api/ocm/tools/transfer/internal/merge.go new file mode 100644 index 000000000..635a23983 --- /dev/null +++ b/api/ocm/tools/transfer/internal/merge.go @@ -0,0 +1,121 @@ +package internal + +import ( + "github.com/mandelsoft/logging" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/valuemergehandler" + "ocm.software/ocm/api/ocm/valuemergehandler/hpi" + "ocm.software/ocm/api/utils/runtime" +) + +// PrepareDescriptor provides a descriptor for the transport target based on a +// descriptor from the transport source and a descriptor already prsent at the +// target. +func PrepareDescriptor(log logging.Logger, ctx ocm.Context, s *compdesc.ComponentDescriptor, t *compdesc.ComponentDescriptor) (*compdesc.ComponentDescriptor, error) { + if ctx == nil { + ctx = ocm.DefaultContext() + } + + n := s.Copy() + err := MergeSignatures(t.Signatures, &n.Signatures) + if err == nil { + err = MergeLabels(log, ctx, t.Labels, &n.Labels) + } + if err == nil { + err = MergeLabels(log, ctx, t.Provider.Labels, &n.Provider.Labels) + } + if err == nil { + err = MergeElements(log, ctx, t.Sources, n.Sources) + } + if err == nil { + err = MergeElements(log, ctx, t.Resources, n.Resources) + } + if err == nil { + err = MergeElements(log, ctx, t.References, n.References) + } + + if err != nil { + return nil, err + } + return n, nil +} + +func MergeElements(log logging.Logger, ctx ocm.Context, s compdesc.ElementAccessor, t compdesc.ElementAccessor) error { + for i := 0; i < s.Len(); i++ { + es := s.Get(i) + id := es.GetMeta().GetIdentity(s) + et := compdesc.GetByIdentity(t, id) + if et != nil { + if err := MergeLabels(log, ctx, es.GetMeta().Labels, &et.GetMeta().Labels); err != nil { + return err + } + + // keep access for same digest + if aes, ok := es.(compdesc.ElementArtifactAccessor); ok { + if des, ok := es.(compdesc.ElementDigestAccessor); ok { + if des.GetDigest() != nil && des.GetDigest().Equal(et.(compdesc.ElementDigestAccessor).GetDigest()) { + et.(compdesc.ElementArtifactAccessor).SetAccess(aes.GetAccess()) + } + } + } + // keep digest for locally signed/hashed elements + if des, ok := es.(compdesc.ElementDigestAccessor); ok { + if des.GetDigest() != nil { + det := et.(compdesc.ElementDigestAccessor) + if det.GetDigest() == nil { + det.SetDigest(des.GetDigest()) + } + } + } + } + } + return nil +} + +// MergeLabels tries to merge old label states into the new target state. +func MergeLabels(log logging.Logger, ctx ocm.Context, s metav1.Labels, t *metav1.Labels) error { + for _, l := range s { + if l.Signing { + continue + } + idx := t.GetIndex(l.Name) + if idx < 0 { + log.Trace("appending label", "name", l.Name, "value", l.Value) + *t = append(*t, l) + } else { + err := MergeLabel(ctx, l, &(*t)[idx]) + if err != nil { + return err + } + log.Trace("merge result", "name", l.Name, "result", (*t)[idx].Value) + } + } + return nil +} + +func MergeLabel(ctx ocm.Context, s metav1.Label, t *metav1.Label) error { + r := valuemergehandler.Value{t.Value} + v := t.Version + if v == "" { + v = "v1" + } + mod, err := valuemergehandler.Merge(ctx, t.Merge, hpi.LabelHint(t.Name, v), runtime.RawValue{s.Value}, &r) + if mod { + t.Value = r.RawMessage + } + return err +} + +// MergeSignatures tries to merge old signatures into the new target state. +func MergeSignatures(s metav1.Signatures, t *metav1.Signatures) error { + for _, sig := range s { + idx := t.GetIndex(sig.Name) + if idx < 0 { + *t = append(*t, sig) + } + } + return nil +} diff --git a/api/ocm/tools/transfer/logging.go b/api/ocm/tools/transfer/logging.go new file mode 100644 index 000000000..344bec74c --- /dev/null +++ b/api/ocm/tools/transfer/logging.go @@ -0,0 +1,18 @@ +package transfer + +import ( + "github.com/mandelsoft/logging" + + "ocm.software/ocm/api/ocm" + ocmlog "ocm.software/ocm/api/utils/logging" +) + +var REALM = ocmlog.DefineSubRealm("OCM transfer handling", "transfer") + +type ContextProvider interface { + GetContext() ocm.Context +} + +func Logger(c ContextProvider, keyValuePairs ...interface{}) logging.Logger { + return c.GetContext().Logger(REALM).WithValues(keyValuePairs...) +} diff --git a/pkg/contexts/ocm/transfer/merge_test.go b/api/ocm/tools/transfer/merge_test.go similarity index 92% rename from pkg/contexts/ocm/transfer/merge_test.go rename to api/ocm/tools/transfer/merge_test.go index 1453442ad..0cdd8d8c8 100644 --- a/pkg/contexts/ocm/transfer/merge_test.go +++ b/api/ocm/tools/transfer/merge_test.go @@ -7,16 +7,16 @@ import ( "github.com/go-test/deep" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "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/contexts/ocm/compdesc/versions/v2" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/internal" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/handlers/maplistmerge" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/hpi" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/compdesc/versions/v2" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + "ocm.software/ocm/api/ocm/tools/transfer/internal" + "ocm.software/ocm/api/ocm/valuemergehandler/handlers/maplistmerge" + "ocm.software/ocm/api/ocm/valuemergehandler/hpi" ) func TouchLabels(s, d, e *metav1.Labels) { diff --git a/pkg/contexts/ocm/transfer/needs.go b/api/ocm/tools/transfer/needs.go similarity index 87% rename from pkg/contexts/ocm/transfer/needs.go rename to api/ocm/tools/transfer/needs.go index 8a31c4a43..197e38d8a 100644 --- a/pkg/contexts/ocm/transfer/needs.go +++ b/api/ocm/tools/transfer/needs.go @@ -3,9 +3,9 @@ package transfer import ( "github.com/go-test/deep" - "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" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" ) func needsResourceTransport(cv ocm.ComponentVersionAccess, s, t *compdesc.ComponentDescriptor, handler TransferHandler) bool { diff --git a/api/ocm/tools/transfer/options.go b/api/ocm/tools/transfer/options.go new file mode 100644 index 000000000..4c9e66dcf --- /dev/null +++ b/api/ocm/tools/transfer/options.go @@ -0,0 +1,69 @@ +package transfer + +import ( + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler" + common "ocm.software/ocm/api/utils/misc" +) + +type ( + // TransferOption if the interface for options given to transfer functions. + // The can influence the behaviour of the transfer process by configuring + // appropriate transfer handlers. + TransferOption = transferhandler.TransferOption + + // TransferOptions is the target interface for consumers of + // a TransferOption. + TransferOptions = transferhandler.TransferOptions + + // TransferHandler controls the transfer of component versions. + // It can be used to control the value transport of sources and resources + // on artifact level (by providing specific handling for dedicated artifact attributes), + // the concrete re/source transfer step, and the way how + // nested component version are transported. + // There are two implementations delivered as part of the OCM library: + // - package transferhandler.standard: able to select recursive transfer + // general value artifact transport. + // - package transferhandler.spiff: controls transfer using a spiff script. + // Custom implemetations can be used to gain fine-grained control + // over the transfer process, whose general flow is handled by + // a uniform Transfer function. + TransferHandler = transferhandler.TransferHandler +) + +// Local options do not relate to the transfer handler, but directly to the +// processing logic. They are formal transferhandler options to be passable to +// the option list but apply themselves only for the localOptions object. +// To distinguish them from transferhandler options, they do NOT implement +// the transferhandler.TransferOptionsCreator interface. +type localOptions struct { + printer common.Printer +} + +func (opts *localOptions) Eval(optlist ...transferhandler.TransferOption) error { + for _, o := range optlist { + if _, ok := o.(transferhandler.TransferOptionsCreator); !ok { + err := o.ApplyTransferOption(opts) + if err != nil { + return err + } + } + } + return nil +} + +// WithPrinter provides a explicit printer object. By default, +// a non-printing printer will be used. +func WithPrinter(p common.Printer) transferhandler.TransferOption { + return &localOptions{ + printer: p, + } +} + +func (l *localOptions) ApplyTransferOption(options TransferOptions) error { + if t, ok := options.(*localOptions); ok { + if l.printer != nil { + t.printer = l.printer + } + } + return nil +} diff --git a/pkg/contexts/ocm/transfer/suite_test.go b/api/ocm/tools/transfer/suite_test.go similarity index 100% rename from pkg/contexts/ocm/transfer/suite_test.go rename to api/ocm/tools/transfer/suite_test.go diff --git a/api/ocm/tools/transfer/transfer.go b/api/ocm/tools/transfer/transfer.go new file mode 100644 index 000000000..849013886 --- /dev/null +++ b/api/ocm/tools/transfer/transfer.go @@ -0,0 +1,353 @@ +package transfer + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/finalizer" + "github.com/mandelsoft/logging" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + ocmcpi "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/none" + "ocm.software/ocm/api/ocm/tools/transfer/internal" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/api/utils/errkind" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" +) + +type WalkingState = common.WalkingState[*struct{}, interface{}] + +type TransportClosure = common.NameVersionInfo[*struct{}] + +func TransferVersion(printer common.Printer, closure TransportClosure, src ocmcpi.ComponentVersionAccess, tgt ocmcpi.Repository, handler TransferHandler) error { + if closure == nil { + closure = TransportClosure{} + } + state := WalkingState{Closure: closure} + return transferVersion(common.AssurePrinter(printer), Logger(src), state, src, tgt, handler) +} + +func transferVersion(printer common.Printer, log logging.Logger, state WalkingState, src ocmcpi.ComponentVersionAccess, tgt ocmcpi.Repository, handler TransferHandler) (rerr error) { + nv := common.VersionedElementKey(src) + log = log.WithValues("history", state.History.String(), "version", nv) + if ok, err := state.Add(ocm.KIND_COMPONENTVERSION, nv); !ok { + return err + } + log.Info("transferring version") + printer.Printf("transferring version %q...\n", nv) + if handler == nil { + var err error + handler, err = standard.New(standard.Overwrite()) + if err != nil { + return err + } + } + + var finalize finalizer.Finalizer + defer finalize.FinalizeWithErrorPropagation(&rerr) + + d := src.GetDescriptor() + + comp, err := tgt.LookupComponent(src.GetName()) + if err != nil { + return errors.Wrapf(err, "%s: lookup target component", state.History) + } + finalize.Close(comp, "closing target component") + + var ok bool + t, err := comp.LookupVersion(src.GetVersion()) + finalize.Close(t, "existing target version") + + // references have always to be handled, because of potentially different + // transport modes, which could affect the desired access methods in + // the target environment. + + // doTransport controls, whether the transport of the local component + // version has to be re-considered. + doTransport := true + + // doMerge controls. whether a potential current version in the target + // environment has to be merged into the transported one. + doMerge := false + + // doCopy controls, whether the artifact content has to be considered. + doCopy := true + + if err != nil { + if errors.IsErrNotFound(err) { + t, err = comp.NewVersion(src.GetVersion()) + finalize.Close(t, "new target version") + } + } else { + ok, err = handler.EnforceTransport(src, t) + if err != nil { + return err + } + if ok { + // execute transport as if the component version were not present + // on the target side. + } else { + // determine transport mode for component version present + // on the target side. + if eq := d.Equivalent(t.GetDescriptor()); eq.IsHashEqual() { + if eq.IsEquivalent() { + if !needsResourceTransport(src, d, t.GetDescriptor(), handler) { + printer.Printf(" version %q already present -> skip transport\n", nv) + doTransport = false + } else { + printer.Printf(" version %q already present -> but requires resource transport\n", nv) + } + } else { + ok, err = handler.UpdateVersion(src, t) + if err != nil { + return err + } + if !ok { + printer.Printf(" version %q requires update of volatile data, but skipped\n", nv) + return nil + } + ok, err = handler.OverwriteVersion(src, t) + if ok { + printer.Printf(" warning: version %q already present, but transport enforced by overwrite option)\n", nv) + doMerge = false + doCopy = true + } else { + printer.Printf(" updating volatile properties of %q\n", nv) + doMerge = true + doCopy = false + } + } + } else { + msg := " version %q already present, but" + if eq.IsLocalHashEqual() { + if eq.IsArtifactDetectable() { + msg += " differs because some artifact digests are changed" + } else { + // TODO: option to precalculate missing digests (as pre equivalent step). + msg += " might differ, because not all artifact digests are known" + } + } else { + if eq.IsArtifactDetectable() { + if eq.IsArtifactEqual() { + msg += " differs because signature relevant properties have been changed" + } else { + msg += " differs because some artifacts and signature relevant properties have been changed" + } + } else { + msg += "differs because signature relevant properties have been changed (and not all artifact digests are known)" + } + } + ok, err = handler.OverwriteVersion(src, t) + if ok { + doMerge = false + printer.Printf("warning: "+msg+" (transport enforced by overwrite option)\n", nv) + } else { + printer.Printf(msg+" -> transport aborted (use option overwrite option to enforce transport)\n", nv) + return errors.ErrAlreadyExists(ocm.KIND_COMPONENTVERSION, nv.String()) + } + } + } + } + if err != nil { + return errors.Wrapf(err, "%s: creating target version", state.History) + } + + subp := printer.AddGap(" ") + list := errors.ErrListf("component references for %s", nv) + log.Info(" transferring references") + for _, r := range d.References { + cv, shdlr, err := handler.TransferVersion(src.Repository(), src, &r, tgt) + if err != nil { + return errors.Wrapf(err, "%s: nested component %s[%s:%s]", state.History, r.GetName(), r.ComponentName, r.GetVersion()) + } + if cv != nil { + list.Add(transferVersion(subp, log.WithValues("ref", r.Name), state, cv, tgt, shdlr)) + list.Addf(nil, cv.Close(), "closing reference %s", r.Name) + } + } + + if doTransport { + var n *compdesc.ComponentDescriptor + if doMerge { + log.WithValues("source", src.GetDescriptor(), "target", t.GetDescriptor()).Info(" applying 2-way merge") + n, err = internal.PrepareDescriptor(log, src.GetContext(), src.GetDescriptor(), t.GetDescriptor()) + if err != nil { + return err + } + } else { + n = src.GetDescriptor().Copy() + } + + var unstr *runtime.UnstructuredTypedObject + if !ocm.IsIntermediate(tgt.GetSpecification()) { + unstr, err = runtime.ToUnstructuredTypedObject(tgt.GetSpecification()) + if err != nil { + unstr = nil + } + } + if unstr != nil { + n.RepositoryContexts = append(n.RepositoryContexts, unstr) + } + + // just to be sure: both modes set to false would produce + // corrupted content in target. + // If no copy is done, merge must keep the access methods in target!!! + if !doMerge || doCopy { + err = copyVersion(printer, log, state.History, src, t, n, handler) + if err != nil { + return err + } + } else { + *t.GetDescriptor() = *n + } + + printer.Printf("...adding component version...\n") + log.Info(" adding component version") + list.Add(comp.AddVersion(t)) + } + return list.Result() +} + +func CopyVersion(printer common.Printer, log logging.Logger, hist common.History, src ocm.ComponentVersionAccess, t ocm.ComponentVersionAccess, handler TransferHandler) (rerr error) { + return copyVersion(common.AssurePrinter(printer), log, hist, src, t, src.GetDescriptor().Copy(), handler) +} + +// copyVersion (purely internal) expects an already prepared target comp desc for t given as prep. +func copyVersion(printer common.Printer, log logging.Logger, hist common.History, src ocm.ComponentVersionAccess, t ocm.ComponentVersionAccess, prep *compdesc.ComponentDescriptor, handler TransferHandler) (rerr error) { + var finalize finalizer.Finalizer + + defer errors.PropagateError(&rerr, finalize.Finalize) + + if handler == nil { + handler = standard.NewDefaultHandler(nil) + } + + srccd := src.GetDescriptor() + cur := *t.GetDescriptor() + *t.GetDescriptor() = *prep + log.Info(" transferring resources") + for i, r := range src.GetResources() { + var m ocmcpi.AccessMethod + + nested := finalize.Nested() + + a, err := r.Access() + if err == nil { + m, err = a.AccessMethod(src) + nested.Close(m, fmt.Sprintf("%s: transferring resource %d: closing access method", hist, i)) + } + if err == nil { + ok := a.IsLocal(src.GetContext()) + if !ok { + if !none.IsNone(a.GetKind()) { + ok, err = handler.TransferResource(src, a, r) + if err == nil && !ok { + log.Info("transport omitted", "resource", r.Meta().Name, "index", i, "access", a.GetType()) + } + } + } + if ok { + var old compdesc.Resource + + hint := ocmcpi.ArtifactNameHint(a, src) + old, err = cur.GetResourceByIdentity(r.Meta().GetIdentity(srccd.Resources)) + + changed := err != nil || old.Digest == nil || !old.Digest.Equal(r.Meta().Digest) + valueNeeded := err == nil && needsTransport(src.GetContext(), r, &old) + if changed || valueNeeded { + var msgs []interface{} + if !errors.IsErrNotFound(err) { + if err != nil { + return err + } + if !changed && valueNeeded { + msgs = []interface{}{"copy"} + } else { + msgs = []interface{}{"overwrite"} + } + } + notifyArtifactInfo(printer, log, "resource", i, r.Meta(), hint, msgs...) + err = handler.HandleTransferResource(r, m, hint, t) + } else { + if err == nil { // old resource found -> keep current access method + t.SetResource(r.Meta(), old.Access, ocm.ModifyResource(), ocm.SkipVerify()) + } + notifyArtifactInfo(printer, log, "resource", i, r.Meta(), hint, "already present") + } + } + } + if err != nil { + if !errors.IsErrUnknownKind(err, errkind.KIND_ACCESSMETHOD) { + return errors.Wrapf(err, "%s: transferring resource %d", hist, i) + } + printer.Printf("WARN: %s: transferring resource %d: %s (enforce transport by reference)\n", hist, i, err) + } + err = nested.Finalize() + if err != nil { + return err + } + } + + log.Info(" transferring sources") + for i, r := range src.GetSources() { + var m ocmcpi.AccessMethod + + a, err := r.Access() + if err == nil { + m, err = a.AccessMethod(src) + } + if err == nil { + ok := a.IsLocal(src.GetContext()) + if !ok { + if !none.IsNone(a.GetKind()) { + ok, err = handler.TransferSource(src, a, r) + if err == nil && !ok { + log.Info("transport omitted", "source", r.Meta().Name, "index", i, "access", a.GetType()) + } + } + } + if ok { + // sources do not have digests fo far, so they have to copied, always. + hint := ocmcpi.ArtifactNameHint(a, src) + notifyArtifactInfo(printer, log, "source", i, r.Meta(), hint) + err = errors.Join(err, handler.HandleTransferSource(r, m, hint, t)) + } + err = errors.Join(err, m.Close()) + } + if err != nil { + if !errors.IsErrUnknownKind(err, errkind.KIND_ACCESSMETHOD) { + return errors.Wrapf(err, "%s: transferring source %d", hist, i) + } + printer.Printf("WARN: %s: transferring source %d: %s (enforce transport by reference)\n", hist, i, err) + } + } + return nil +} + +func notifyArtifactInfo(printer common.Printer, log logging.Logger, kind string, index int, meta compdesc.ArtifactMetaAccess, hint string, msgs ...interface{}) { + msg := "copying" + cmsg := "..." + if len(msgs) > 0 { + if m, ok := msgs[0].(string); ok { + msg = fmt.Sprintf(m, msgs[1:]...) + } else { + msg = fmt.Sprint(msgs...) + } + cmsg = " (" + msg + ")" + } + if printer != nil { + if hint != "" { + printer.Printf("...%s %d %s[%s](%s)%s\n", kind, index, meta.GetName(), meta.GetType(), hint, cmsg) + } else { + printer.Printf("...%s %d %s[%s]%s\n", kind, index, meta.GetName(), meta.GetType(), cmsg) + } + } + if hint != "" { + log.Debug("handle artifact", "kind", kind, "name", meta.GetName(), "type", meta.GetType(), "index", index, "hint", hint, "message", msg) + } else { + log.Debug("handle artifact", "kind", kind, "name", meta.GetName(), "type", meta.GetType(), "index", index, "message", msg) + } +} diff --git a/api/ocm/tools/transfer/transferhandler/config/config.go b/api/ocm/tools/transfer/transferhandler/config/config.go new file mode 100644 index 000000000..b97800553 --- /dev/null +++ b/api/ocm/tools/transfer/transferhandler/config/config.go @@ -0,0 +1,102 @@ +package scriptoption + +import ( + cfgcpi "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ConfigType = "transport.ocm" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX + ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" +) + +func init() { + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1, usage)) +} + +// Config describes a set of transport options. +type Config struct { + runtime.ObjectVersionedType `json:",inline"` + Recursive *bool `json:"recursive,omitempty"` + ResourcesByValue *bool `json:"resourcesByValue,omitempty"` + LocalByValue *bool `json:"localResourcesByValue,omitempty"` + SourcesByValue *bool `json:"sourcesByValue,omitempty"` + KeepGlobalAccess *bool `json:"keepGlobalAccess,omitempty"` + StopOnExisting *bool `json:"stopOnExistingVersion,omitempty"` + Overwrite *bool `json:"overwrite,omitempty"` + OmitAccessTypes []string `json:"omitAccessTypes,omitempty"` +} + +// NewConfig creates a new memory ConfigSpec. +func NewConfig() *Config { + return &Config{ + ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), + } +} + +func (c *Config) GetType() string { + return ConfigType +} + +func (c *Config) ApplyTo(ctx cfgcpi.Context, target interface{}) error { + if c.Recursive != nil { + if opts, ok := target.(standard.RecursiveOption); ok { + opts.SetRecursive(*c.Recursive) + } + } + if c.ResourcesByValue != nil { + if opts, ok := target.(standard.ResourcesByValueOption); ok { + opts.SetResourcesByValue(*c.ResourcesByValue) + } + } + if c.LocalByValue != nil { + if opts, ok := target.(standard.LocalResourcesByValueOption); ok { + opts.SetLocalResourcesByValue(*c.LocalByValue) + } + } + if c.SourcesByValue != nil { + if opts, ok := target.(standard.SourcesByValueOption); ok { + opts.SetSourcesByValue(*c.SourcesByValue) + } + } + if c.KeepGlobalAccess != nil { + if opts, ok := target.(standard.KeepGlobalAccessOption); ok { + opts.SetKeepGlobalAccess(*c.KeepGlobalAccess) + } + } + if c.StopOnExisting != nil { + if opts, ok := target.(standard.StopOnExistingVersionOption); ok { + opts.SetStopOnExistingVersion(*c.StopOnExisting) + } + } + if c.Overwrite != nil { + if opts, ok := target.(standard.OverwriteOption); ok { + opts.SetOverwrite(*c.Overwrite) + } + } + if c.OmitAccessTypes != nil { + if opts, ok := target.(standard.OmitAccessTypesOption); ok { + opts.SetOmittedAccessTypes(c.OmitAccessTypes...) + } + } + return nil +} + +const usage = ` +The config type ` + ConfigType + ` can be used to define transfer scripts: + +
+    type: ` + ConfigType + `
+    recursive: true
+    overwrite: true
+    localResourcesByValue: false
+    resourcesByValue: true
+    sourcesByValue: false
+    keepGlobalAccess: false
+    stopOnExistingVersion: false
+    omitAccessTypes:
+    - s3
+
+` diff --git a/api/ocm/tools/transfer/transferhandler/doc.go b/api/ocm/tools/transfer/transferhandler/doc.go new file mode 100644 index 000000000..4c31ef297 --- /dev/null +++ b/api/ocm/tools/transfer/transferhandler/doc.go @@ -0,0 +1,40 @@ +// Package transferhandler provides the API for transfer handlers used +// during the transfer process of an OCM component. +// There is a common generic transfer functionality in package transfer, +// which can be used to transfer component versions from one OCM repository +// to another one. To flexible enough for various use cases this generic handling +// uses extension points to control the concrete transfer behaviour. +// These extension points can be implemented by dedicated transfer handlers +// to influence the transfer process. +// +// For example: +// +// - In a dedicated scenario only components from a dedicated provider should +// not be transferred per value in a transfer operation. +// - Another scenario could require to replace dedicated access methods. +// +// Such specific logic is hard to formalize, but nevertheless the basic transfer +// flow is always the same. Therefore, it makes no sense to reimplement the flow +// just because some inner process steps or decisions should look slightly +// different. Because of this transfer handlers are introduces, which allow to +// separate such decisions and step implementations form the generic flow and +// to provided scenario specific implementations with reimplementing the complete +// transitive transfer handling. +// +// For common use cases two standard handlers are provided, which can be used +// to formally describe typical transfer scenarios: +// - package [ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard] +// provides a standard behaviour configurable +// by some common options, like transport-by-value. +// - package [ocm.software/ocm/api/ocm/tools/transfer/transferhandler/spiff] +// provides a handler configurable by a [spiff] script. +// +// Transfer handlers may accept transfer options to be configured. +// For operations not taking a transferhandler but only transfer options, +// the given options are used to find the best matching transfer handling accepting +// those options. Therefore, standard transfer handles can be registered. +// Besides this any user defined transfer handler may be used by using operation +// flavors directly accepting a transfer handler. +// +// [spiff]: https://github.com/mandelsoft/spiff/README.md +package transferhandler diff --git a/pkg/contexts/ocm/transfer/transferhandler/registry.go b/api/ocm/tools/transfer/transferhandler/registry.go similarity index 100% rename from pkg/contexts/ocm/transfer/transferhandler/registry.go rename to api/ocm/tools/transfer/transferhandler/registry.go diff --git a/pkg/contexts/ocm/transfer/transferhandler/spiff/README.md b/api/ocm/tools/transfer/transferhandler/spiff/README.md similarity index 100% rename from pkg/contexts/ocm/transfer/transferhandler/spiff/README.md rename to api/ocm/tools/transfer/transferhandler/spiff/README.md diff --git a/api/ocm/tools/transfer/transferhandler/spiff/handler.go b/api/ocm/tools/transfer/transferhandler/spiff/handler.go new file mode 100644 index 000000000..0a8aafff4 --- /dev/null +++ b/api/ocm/tools/transfer/transferhandler/spiff/handler.go @@ -0,0 +1,309 @@ +package spiff + +import ( + "encoding/json" + "strconv" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/spiff/dynaml" + "github.com/mandelsoft/spiff/features" + "github.com/mandelsoft/spiff/spiffing" + "github.com/mandelsoft/spiff/yaml" + "github.com/sirupsen/logrus" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/api/utils/runtime" +) + +type Handler struct { + standard.Handler + opts *Options + spiff spiffing.Spiff +} + +func New(opts ...transferhandler.TransferOption) (transferhandler.TransferHandler, error) { + options := &Options{} + err := transferhandler.ApplyOptions(options, opts...) + if err != nil { + return nil, err + } + spiff := spiffing.New().WithFeatures(features.CONTROL, features.INTERPOLATION) + if options.GetScriptFilesystem() != nil { + spiff = spiff.WithFileSystem(options.fs) + } + return &Handler{ + Handler: *standard.NewDefaultHandler(&options.Options), + opts: options, + spiff: spiff, + }, nil +} + +// TODO: handle update and overwrite per script + +func (h *Handler) UpdateVersion(src ocm.ComponentVersionAccess, tgt ocm.ComponentVersionAccess) (bool, error) { + if ok, err := h.Handler.UpdateVersion(src, tgt); !ok || err != nil { + return ok, nil + } + if h.opts.GetScript() == nil { + return false, nil + } + binding := h.getBinding(src, nil, nil, nil, nil) + return h.EvalBool("update", binding, "process") +} + +func (h *Handler) EnforceTransport(src ocm.ComponentVersionAccess, tgt ocm.ComponentVersionAccess) (bool, error) { + if ok, err := h.Handler.EnforceTransport(src, tgt); ok || err != nil { + return ok, nil + } + if h.opts.GetScript() == nil { + return false, nil + } + binding := h.getBinding(src, nil, nil, nil, nil) + return h.EvalBool("enforceTransport", binding, "process") +} + +func (h *Handler) OverwriteVersion(src ocm.ComponentVersionAccess, tgt ocm.ComponentVersionAccess) (bool, error) { + if ok, err := h.Handler.OverwriteVersion(src, tgt); ok || err != nil { + return ok, nil + } + if h.opts.GetScript() == nil { + return false, nil + } + binding := h.getBinding(src, nil, nil, nil, nil) + return h.EvalBool("overwrite", binding, "process") +} + +func (h *Handler) TransferVersion(repo ocm.Repository, src ocm.ComponentVersionAccess, meta *compdesc.ComponentReference, tgt ocm.Repository) (ocm.ComponentVersionAccess, transferhandler.TransferHandler, error) { + if src == nil || h.opts.IsRecursive() { + if h.opts.GetScript() == nil { + return h.Handler.TransferVersion(repo, src, meta, tgt) + } + binding := h.getBinding(src, nil, &meta.ElementMeta, nil, tgt) + result, r, s, err := h.EvalRecursion("componentversion", binding, "process") + if err != nil { + return nil, nil, err + } + if result { + if r != nil { + repo, err = repo.GetContext().RepositoryForConfig(r, runtime.DefaultJSONEncoding) + if err != nil { + return nil, nil, err + } + } + if s == nil { + return h.Handler.TransferVersion(repo, src, meta, tgt) + } + opts := *h.opts + opts.script = s + cv, _, err := h.Handler.TransferVersion(repo, src, meta, tgt) + return cv, &Handler{ + Handler: h.Handler, + opts: &opts, + }, err + } + } + return nil, nil, nil +} + +func (h *Handler) TransferResource(src ocm.ComponentVersionAccess, a ocm.AccessSpec, r ocm.ResourceAccess) (bool, error) { + if !h.opts.IsResourcesByValue() { + return false, nil + } + if h.opts.GetScript() == nil { + return true, nil + } + binding := h.getBinding(src, a, &r.Meta().ElementMeta, &r.Meta().Type, nil) + return h.EvalBool("resource", binding, "process") +} + +func (h *Handler) TransferSource(src ocm.ComponentVersionAccess, a ocm.AccessSpec, r ocm.SourceAccess) (bool, error) { + if !h.opts.IsSourcesByValue() { + return false, nil + } + if h.opts.GetScript() == nil { + return true, nil + } + binding := h.getBinding(src, a, &r.Meta().ElementMeta, &r.Meta().Type, nil) + return h.EvalBool("source", binding, "process") +} + +func (h *Handler) getBinding(src ocm.ComponentVersionAccess, a ocm.AccessSpec, m *compdesc.ElementMeta, typ *string, tgt ocm.Repository) map[string]interface{} { + binding := map[string]interface{}{} + if src != nil { + binding["component"] = getCVAttrs(src) + } + + if a != nil { + binding["access"] = getData(a) + } + if m != nil { + binding["element"] = getData(m) + } + if typ != nil { + binding["element"].(map[string]interface{})["type"] = *typ + } + if tgt != nil { + binding["target"] = getData(tgt.GetSpecification()) + } + return binding +} + +func getData(in interface{}) interface{} { + var v interface{} + + d, err := json.Marshal(in) + if err != nil { + logrus.Error(err) + } + + if err := json.Unmarshal(d, &v); err != nil { + logrus.Error(err) + } + + return v +} + +func getCVAttrs(cv ocm.ComponentVersionAccess) map[string]interface{} { + provider := map[string]interface{}{} + data, err := json.Marshal(cv.GetDescriptor().Provider) + if err != nil { + logrus.Error(err) + } + json.Unmarshal(data, &provider) + + labels := cv.GetDescriptor().Labels.AsMap() + + values := map[string]interface{}{} + values["name"] = cv.GetName() + values["version"] = cv.GetVersion() + values["provider"] = provider + values["labels"] = labels + return values +} + +func (h *Handler) Eval(binding map[string]interface{}) (spiffing.Node, error) { + spiff, err := h.spiff.WithValues(binding) + if err != nil { + return nil, err + } + node, err := spiff.Unmarshal("script", h.opts.GetScript()) + if err != nil { + return nil, err + } + return spiff.Cascade(node, nil) +} + +func (h *Handler) EvalBool(mode string, binding map[string]interface{}, key string) (bool, error) { + binding = map[string]interface{}{ + "mode": mode, + "values": binding, + } + r, err := h.Eval(binding) + if err != nil { + return false, err + } + return h.evalBool(r, key) +} + +func (h *Handler) EvalRecursion(mode string, binding map[string]interface{}, key string) (bool, []byte, []byte, error) { + binding = map[string]interface{}{ + "mode": mode, + "values": binding, + } + r, err := h.Eval(binding) + if err != nil { + return false, nil, nil, err + } + + valueMap, ok := r.Value().(map[string]spiffing.Node) + if !ok { + return false, nil, nil, errors.ErrUnknown("transfer script field", key) + } + r = valueMap[key] + if r == nil { + return false, nil, nil, errors.ErrUnknown("transfer script field", key) + } + + b, err := h.evalBoolValue(r) + if err == nil { + // flat boolean without result structure + return b, nil, nil, nil + } + valueMap, ok = r.Value().(map[string]spiffing.Node) + if !ok { + return false, nil, nil, errors.ErrInvalid("transfer script result field type", dynaml.ExpressionType(r)) + } + // now we expect a result structure + // process: bool + // repospec: map + // script: template + b, err = h.evalBool(r, key) + if err != nil || !b { + return false, nil, nil, err + } + var script []byte + v := valueMap["script"] + if v != nil && v.Value() != nil { + if t, ok := v.Value().(dynaml.TemplateValue); ok { + if m, ok := t.Orig.Value().(map[string]spiffing.Node); ok { + delete(m, "<<") + delete(m, "<<<") + } else { + return false, nil, nil, errors.ErrInvalid("script template type", dynaml.ExpressionType(t.Orig)) + } + script, err = yaml.Marshal(t.Orig) + if err != nil { + return false, nil, nil, err + } + } else { + return false, nil, nil, errors.ErrInvalid("script type", dynaml.ExpressionType(v)) + } + } + + var repospec []byte + v = valueMap["repospec"] + if v != nil && v.Value() != nil { + if _, ok := v.Value().(map[string]spiffing.Node); ok { + spec, err := yaml.Normalize(v) + if err == nil { + repospec, err = json.Marshal(spec) + } + if err != nil { + return false, nil, nil, errors.Wrapf(err, "invalid field repospec") + } + } else { + return false, nil, nil, errors.ErrInvalid("repospec type", dynaml.ExpressionType(v)) + } + } + return true, repospec, script, nil +} + +func (h *Handler) evalBool(r spiffing.Node, key string) (bool, error) { + v := r.Value().(map[string]spiffing.Node)[key] + if v == nil { + return false, errors.ErrUnknown("field", key) + } + return h.evalBoolValue(v) +} + +func (h *Handler) evalBoolValue(v spiffing.Node) (bool, error) { + switch b := v.Value().(type) { + case bool: + return b, nil + case int64: + return b != 0, nil + case float64: + return b != 0, nil + case string: + r, err := strconv.ParseBool(b) + if err != nil { + return len(b) > 0, nil + } + return r, nil + default: + return false, errors.ErrInvalid("boolean result", dynaml.ExpressionType(v)) + } +} diff --git a/api/ocm/tools/transfer/transferhandler/spiff/handler_test.go b/api/ocm/tools/transfer/transferhandler/spiff/handler_test.go new file mode 100644 index 000000000..72e2e5aa9 --- /dev/null +++ b/api/ocm/tools/transfer/transferhandler/spiff/handler_test.go @@ -0,0 +1,298 @@ +package spiff_test + +import ( + "encoding/json" + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + . "ocm.software/ocm/api/oci/testhelper" + + "github.com/mandelsoft/goutils/testutils" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + ocmutils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/ocm/tools/transfer" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/spiff" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/mime" +) + +const ( + ARCH = "/tmp/ctf" + ARCH2 = "/tmp/ctf2" + PROVIDER = "mandelsoft" + VERSION = "v1" + COMPONENT = "github.com/mandelsoft/test" + COMPONENT2 = "github.com/mandelsoft/test2" + OUT = "/tmp/res" + OCIPATH = "/tmp/oci" + OCIHOST = "alias" +) + +const script1 = ` +rules: + resource: + <<: (( &template )) + process: (( values.component.name == "github.com/mandelsoft/test" )) + componentversion: + <<: (( &template )) + process: + process: true + script: (( rules.default )) + repospec: + type: dummy + default: + <<: (( &template )) + process: false + +process: (( (*(rules[mode] || rules.default)).process )) +` + +var _ = Describe("Transfer handler", func() { + It("handles bool", func() { + handler, err := spiff.New(spiff.Script([]byte(script1))) + Expect(err).To(Succeed()) + + binding := map[string]interface{}{ + "component": map[string]interface{}{ + "name": COMPONENT, + "version": VERSION, + }, + } + ok, err := handler.(*spiff.Handler).EvalBool("resource", binding, "process") + Expect(err).To(Succeed()) + Expect(ok).To(BeTrue()) + }) + + It("handles componentversion", func() { + handler, err := spiff.New(spiff.Script([]byte(script1))) + Expect(err).To(Succeed()) + + binding := map[string]interface{}{ + "component": map[string]interface{}{ + "name": COMPONENT, + "version": VERSION, + }, + } + ok, r, s, err := handler.(*spiff.Handler).EvalRecursion("componentversion", binding, "process") + Expect(err).To(Succeed()) + Expect(ok).To(BeTrue()) + Expect(string(s)).To(Equal("process: false\n")) + Expect(string(r)).To(Equal("{\"type\":\"dummy\"}")) + }) + + It("handles simple componentversion", func() { + handler, err := spiff.New(spiff.Script([]byte(script1))) + Expect(err).To(Succeed()) + binding := map[string]interface{}{ + "component": map[string]interface{}{ + "name": COMPONENT, + "version": VERSION, + }, + } + ok, r, s, err := handler.(*spiff.Handler).EvalRecursion("resource", binding, "process") + Expect(err).To(Succeed()) + Expect(ok).To(BeTrue()) + Expect(r).To(BeNil()) + Expect(s).To(BeNil()) + }) + + Context("handler", func() { + var env *Builder + var ldesc *artdesc.Descriptor + + BeforeEach(func() { + env = NewBuilder() + + FakeOCIRepo(env, OCIPATH, OCIHOST) + + env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { + ldesc = OCIManifest1(env) + OCIManifest2(env) + }) + + env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { + env.Component(COMPONENT, func() { + env.Version(VERSION, func() { + env.Provider(PROVIDER) + env.Resource("testdata", "", "PlainText", metav1.LocalRelation, func() { + env.BlobStringData(mime.MIME_TEXT, "testdata") + }) + env.Resource("value", "", resourcetypes.OCI_IMAGE, metav1.LocalRelation, func() { + env.Access( + ociartifact.New(oci.StandardOCIRef(OCIHOST+".alias", OCINAMESPACE, OCIVERSION)), + ) + env.Label("transportByValue", true) + }) + env.Resource("ref", "", resourcetypes.OCI_IMAGE, metav1.LocalRelation, func() { + env.Access( + ociartifact.New(oci.StandardOCIRef(OCIHOST+".alias", OCINAMESPACE2, OCIVERSION)), + ) + }) + }) + }) + }) + + env.OCMCommonTransport(ARCH2, accessio.FormatDirectory, func() { + env.Component(COMPONENT2, func() { + env.Version(VERSION, func() { + env.Reference("ref", COMPONENT, VERSION) + env.Provider(PROVIDER) + }) + }) + }) + }) + + AfterEach(func() { + env.Cleanup() + }) + + const script2 = ` +rules: + resource: + <<: (( &template )) +# process: (( values.element.name == "value" )) + process: (( values.element.labels.transportByValue.value || false )) + + default: + <<: (( &template )) + process: false + +process: (( (*(rules[mode] || rules.default)).process )) +` + + It("it should copy all resource by value to a ctf file without script", func() { + src, err := ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env) + Expect(err).To(Succeed()) + cv, err := src.LookupComponentVersion(COMPONENT, VERSION) + Expect(err).To(Succeed()) + tgt, err := ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env) + Expect(err).To(Succeed()) + defer tgt.Close() + + handler, err := spiff.New(standard.ResourcesByValue()) + Expect(err).To(Succeed()) + err = transfer.TransferVersion(nil, nil, cv, tgt, handler) + Expect(err).To(Succeed()) + Expect(env.DirExists(OUT)).To(BeTrue()) + + list, err := tgt.ComponentLister().GetComponents("", true) + Expect(err).To(Succeed()) + Expect(list).To(Equal([]string{COMPONENT})) + comp, err := tgt.LookupComponentVersion(COMPONENT, VERSION) + Expect(err).To(Succeed()) + Expect(len(comp.GetDescriptor().Resources)).To(Equal(3)) + + data, err := json.Marshal(comp.GetDescriptor().Resources[2].Access) + Expect(err).To(Succeed()) + fmt.Printf("%s\n", string(data)) + hash := HashManifest2(artifactset.DefaultArtifactSetDescriptorFileName) + Expect(string(data)).To(testutils.StringEqualWithContext("{\"localReference\":\"" + hash + "\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\"" + OCINAMESPACE2 + ":" + OCIVERSION + "\",\"type\":\"localBlob\"}")) + + data, err = json.Marshal(comp.GetDescriptor().Resources[1].Access) + Expect(err).To(Succeed()) + fmt.Printf("%s\n", string(data)) + hash = HashManifest1(artifactset.DefaultArtifactSetDescriptorFileName) + Expect(string(data)).To(Equal("{\"localReference\":\"" + hash + "\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\"" + OCINAMESPACE + ":" + OCIVERSION + "\",\"type\":\"localBlob\"}")) + + racc, err := comp.GetResourceByIndex(1) + Expect(err).To(Succeed()) + reader, err := ocmutils.GetResourceReader(racc) + Expect(err).To(Succeed()) + defer reader.Close() + set, err := artifactset.Open(accessobj.ACC_READONLY, "", 0, accessio.Reader(reader)) + Expect(err).To(Succeed()) + defer set.Close() + + _, blob, err := set.GetBlobData(ldesc.Digest) + Expect(err).To(Succeed()) + data, err = blob.Get() + Expect(err).To(Succeed()) + Expect(string(data)).To(Equal("manifestlayer")) + }) + + It("it should copy one resource by value to a ctf file with script", func() { + src, err := ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env) + Expect(err).To(Succeed()) + cv, err := src.LookupComponentVersion(COMPONENT, VERSION) + Expect(err).To(Succeed()) + tgt, err := ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env) + Expect(err).To(Succeed()) + defer tgt.Close() + + handler, err := spiff.New(standard.ResourcesByValue(), spiff.Script([]byte(script2))) + Expect(err).To(Succeed()) + err = transfer.TransferVersion(nil, nil, cv, tgt, handler) + Expect(err).To(Succeed()) + Expect(env.DirExists(OUT)).To(BeTrue()) + + list, err := tgt.ComponentLister().GetComponents("", true) + Expect(err).To(Succeed()) + Expect(list).To(Equal([]string{COMPONENT})) + comp, err := tgt.LookupComponentVersion(COMPONENT, VERSION) + Expect(err).To(Succeed()) + Expect(len(comp.GetDescriptor().Resources)).To(Equal(3)) + + // index 2: by ref + data, err := json.Marshal(comp.GetDescriptor().Resources[2].Access) + Expect(err).To(Succeed()) + Expect(string(data)).To(Equal("{\"imageReference\":\"" + oci.StandardOCIRef(OCIHOST+".alias", OCINAMESPACE2, OCIVERSION) + "\",\"type\":\"" + ociartifact.Type + "\"}")) + + // index 1: by value + data, err = json.Marshal(comp.GetDescriptor().Resources[1].Access) + Expect(err).To(Succeed()) + hash := HashManifest1(artifactset.DefaultArtifactSetDescriptorFileName) + Expect(string(data)).To(Equal("{\"localReference\":\"" + hash + "\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\"" + OCINAMESPACE + ":" + OCIVERSION + "\",\"type\":\"localBlob\"}")) + + racc, err := comp.GetResourceByIndex(1) + Expect(err).To(Succeed()) + reader, err := ocmutils.GetResourceReader(racc) + Expect(err).To(Succeed()) + defer reader.Close() + set, err := artifactset.Open(accessobj.ACC_READONLY, "", 0, accessio.Reader(reader)) + Expect(err).To(Succeed()) + defer set.Close() + + _, blob, err := set.GetBlobData(ldesc.Digest) + Expect(err).To(Succeed()) + data, err = blob.Get() + Expect(err).To(Succeed()) + Expect(string(data)).To(Equal("manifestlayer")) + }) + + It("it should use additional resolver to resolve component ref", func() { + parentSrc, err := ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH2, 0, env) + Expect(err).To(Succeed()) + cv, err := parentSrc.LookupComponentVersion(COMPONENT2, VERSION) + Expect(err).To(Succeed()) + childSrc, err := ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env) + Expect(err).To(Succeed()) + tgt, err := ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env) + Expect(err).To(Succeed()) + defer tgt.Close() + handler, err := standard.New(standard.Recursive(), standard.Resolver(childSrc)) + Expect(err).To(Succeed()) + err = transfer.TransferVersion(nil, nil, cv, tgt, handler) + Expect(err).To(Succeed()) + Expect(env.DirExists(OUT)).To(BeTrue()) + + list, err := tgt.ComponentLister().GetComponents("", true) + Expect(err).To(Succeed()) + Expect(list).To(ContainElements([]string{COMPONENT2, COMPONENT})) + _, err = tgt.LookupComponentVersion(COMPONENT2, VERSION) + Expect(err).To(Succeed()) + _, err = tgt.LookupComponentVersion(COMPONENT, VERSION) + Expect(err).To(Succeed()) + }) + }) +}) diff --git a/api/ocm/tools/transfer/transferhandler/spiff/options.go b/api/ocm/tools/transfer/transferhandler/spiff/options.go new file mode 100644 index 000000000..68ba1c2aa --- /dev/null +++ b/api/ocm/tools/transfer/transferhandler/spiff/options.go @@ -0,0 +1,149 @@ +package spiff + +import ( + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/spiff/spiffing" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/api/utils" +) + +func init() { + transferhandler.RegisterHandler(100, &TransferOptionsCreator{}) +} + +type Options struct { + standard.Options + script []byte + fs vfs.FileSystem +} + +var ( + _ transferhandler.TransferOption = (*Options)(nil) + + _ ScriptOption = (*Options)(nil) + _ ScriptFilesystemOption = (*Options)(nil) +) + +type TransferOptionsCreator = transferhandler.SpecializedOptionsCreator[*Options, Options] + +func (o *Options) NewOptions() transferhandler.TransferHandlerOptions { + return &Options{} +} + +func (o *Options) NewTransferHandler() (transferhandler.TransferHandler, error) { + return New(o) +} + +func (o *Options) ApplyTransferOption(target transferhandler.TransferOptions) error { + if len(o.script) > 0 { + if opts, ok := target.(ScriptOption); ok { + opts.SetScript(o.script) + } + } + if o.fs != nil { + if opts, ok := target.(ScriptFilesystemOption); ok { + opts.SetScriptFilesystem(o.fs) + } + } + return o.Options.ApplyTransferOption(target) +} + +func (o *Options) SetScript(data []byte) { + o.script = data +} + +func (o *Options) GetScript() []byte { + return o.script +} + +func (o *Options) SetScriptFilesystem(fs vfs.FileSystem) { + o.fs = fs +} + +func (o *Options) GetScriptFilesystem() vfs.FileSystem { + return o.fs +} + +/////////////////////////////////////////////////////////////////////////////// + +type ScriptOption interface { + SetScript(data []byte) + GetScript() []byte +} + +type scriptOption struct { + TransferOptionsCreator + source string + script func() ([]byte, error) +} + +func (o *scriptOption) ApplyTransferOption(to transferhandler.TransferOptions) error { + if o.script == nil { + return nil + } + script, err := o.script() + if err != nil { + return err + } + _, err = spiffing.New().Unmarshal(o.source, script) + if err != nil { + return err + } + + if eff, ok := to.(ScriptOption); ok { + eff.SetScript(script) + return nil + } else { + return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "script") + } +} + +func Script(data []byte) transferhandler.TransferOption { + if data == nil { + return &scriptOption{ + source: "script", + } + } + return &scriptOption{ + source: "script", + script: func() ([]byte, error) { return data, nil }, + } +} + +func ScriptByFile(path string, fss ...vfs.FileSystem) transferhandler.TransferOption { + path, _ = utils.ResolvePath(path) + return &scriptOption{ + source: path, + script: func() ([]byte, error) { return vfs.ReadFile(utils.FileSystem(fss...), path) }, + } +} + +/////////////////////////////////////////////////////////////////////////////// + +type ScriptFilesystemOption interface { + SetScriptFilesystem(fs vfs.FileSystem) + GetScriptFilesystem() vfs.FileSystem +} + +type filesystemOption struct { + TransferOptionsCreator + fs vfs.FileSystem +} + +func (o *filesystemOption) ApplyTransferOption(to transferhandler.TransferOptions) error { + if eff, ok := to.(ScriptFilesystemOption); ok { + eff.SetScriptFilesystem(o.fs) + return nil + } else { + return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "script filesystem") + } +} + +func ScriptFilesystem(fs vfs.FileSystem) transferhandler.TransferOption { + return &filesystemOption{ + fs: fs, + } +} diff --git a/pkg/contexts/ocm/transfer/transferhandler/spiff/suite_test.go b/api/ocm/tools/transfer/transferhandler/spiff/suite_test.go similarity index 100% rename from pkg/contexts/ocm/transfer/transferhandler/spiff/suite_test.go rename to api/ocm/tools/transfer/transferhandler/spiff/suite_test.go diff --git a/api/ocm/tools/transfer/transferhandler/standard/handler.go b/api/ocm/tools/transfer/transferhandler/standard/handler.go new file mode 100644 index 000000000..3b871bfca --- /dev/null +++ b/api/ocm/tools/transfer/transferhandler/standard/handler.go @@ -0,0 +1,109 @@ +package standard + +import ( + "time" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler" + "ocm.software/ocm/api/utils/accessio" +) + +type Handler struct { + opts *Options +} + +func NewDefaultHandler(opts *Options) *Handler { + if opts == nil { + opts = &Options{} + } + return &Handler{opts: opts} +} + +func New(opts ...transferhandler.TransferOption) (transferhandler.TransferHandler, error) { + defaultOpts := &Options{} + err := transferhandler.ApplyOptions(defaultOpts, opts...) + if err != nil { + return nil, err + } + return NewDefaultHandler(defaultOpts), nil +} + +func (h *Handler) UpdateVersion(src ocm.ComponentVersionAccess, tgt ocm.ComponentVersionAccess) (bool, error) { + return !h.opts.IsSkipUpdate(), nil +} + +func (h *Handler) EnforceTransport(src ocm.ComponentVersionAccess, tgt ocm.ComponentVersionAccess) (bool, error) { + return h.opts.IsTransportEnforced(), nil +} + +func (h *Handler) OverwriteVersion(src ocm.ComponentVersionAccess, tgt ocm.ComponentVersionAccess) (bool, error) { + return h.opts.IsOverwrite(), nil +} + +func (h *Handler) TransferVersion(repo ocm.Repository, src ocm.ComponentVersionAccess, meta *compdesc.ComponentReference, tgt ocm.Repository) (ocm.ComponentVersionAccess, transferhandler.TransferHandler, error) { + if src == nil || h.opts.IsRecursive() { + if h.opts.IsStopOnExistingVersion() && tgt != nil { + if found, err := tgt.ExistsComponentVersion(meta.ComponentName, meta.Version); found || err != nil { + return nil, nil, errors.Wrapf(err, "failed looking up in target") + } + } + compoundResolver := ocm.NewCompoundResolver(repo, h.opts.GetResolver()) + cv, err := compoundResolver.LookupComponentVersion(meta.GetComponentName(), meta.Version) + return cv, h, err + } + return nil, nil, nil +} + +func (h *Handler) TransferResource(src ocm.ComponentVersionAccess, a ocm.AccessSpec, r ocm.ResourceAccess) (bool, error) { + if h.opts.IsAccessTypeOmitted(a.GetType()) { + return false, nil + } + if h.opts.IsLocalResourcesByValue() { + if r.Meta().Relation == metav1.LocalRelation { + return true, nil + } + } + return h.opts.IsResourcesByValue(), nil +} + +func (h *Handler) TransferSource(src ocm.ComponentVersionAccess, a ocm.AccessSpec, r ocm.SourceAccess) (bool, error) { + if h.opts.IsAccessTypeOmitted(a.GetType()) { + return false, nil + } + return h.opts.IsSourcesByValue(), nil +} + +func (h *Handler) HandleTransferResource(r ocm.ResourceAccess, m cpi.AccessMethod, hint string, t ocm.ComponentVersionAccess) error { + blob, err := accspeccpi.BlobAccessForAccessMethod(m) + if err != nil { + return err + } + defer blob.Close() + return accessio.Retry(h.opts.GetRetries(), time.Second, func() error { + return t.SetResourceBlob(r.Meta(), blob, hint, h.GlobalAccess(t.GetContext(), m), ocm.SkipVerify()) + }) +} + +func (h *Handler) HandleTransferSource(r ocm.SourceAccess, m cpi.AccessMethod, hint string, t ocm.ComponentVersionAccess) error { + blob, err := accspeccpi.BlobAccessForAccessMethod(m) + if err != nil { + return err + } + defer blob.Close() + return accessio.Retry(h.opts.GetRetries(), time.Second, func() error { + return t.SetSourceBlob(r.Meta(), blob, hint, h.GlobalAccess(t.GetContext(), m)) + }) +} + +func (h *Handler) GlobalAccess(ctx ocm.Context, m ocm.AccessMethod) ocm.AccessSpec { + if h.opts.IsKeepGlobalAccess() { + return m.AccessSpec().GlobalAccessSpec(ctx) + } + return nil +} diff --git a/api/ocm/tools/transfer/transferhandler/standard/handler_test.go b/api/ocm/tools/transfer/transferhandler/standard/handler_test.go new file mode 100644 index 000000000..2a4a7936c --- /dev/null +++ b/api/ocm/tools/transfer/transferhandler/standard/handler_test.go @@ -0,0 +1,528 @@ +package standard_test + +import ( + "encoding/json" + "fmt" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + . "ocm.software/ocm/api/oci/testhelper" + . "ocm.software/ocm/api/ocm/testhelper" + + "github.com/mandelsoft/goutils/finalizer" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/attrs/compositionmodeattr" + "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + ocmsign "ocm.software/ocm/api/ocm/tools/signing" + "ocm.software/ocm/api/ocm/tools/transfer" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/mime" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ARCH = "/tmp/ctf" + ARCH2 = "/tmp/ctf2" + PROVIDER = "mandelsoft" + VERSION = "v1" + COMPONENT = "github.com/mandelsoft/test" + COMPONENT2 = "github.com/mandelsoft/test2" + OUT = "/tmp/res" + OCIPATH = "/tmp/oci" + OCIHOST = "alias" + SIGNATURE = "test" + SIGN_ALGO = rsa.Algorithm +) + +type optionsChecker struct { + standard.TransferOptionsCreator +} + +var _ transferhandler.TransferOption = (*optionsChecker)(nil) + +func (o *optionsChecker) ApplyTransferOption(options transferhandler.TransferOptions) error { + if _, ok := options.(*standard.Options); !ok { + return fmt.Errorf("unexpected options type %T", options) + } + return nil +} + +var _ = Describe("Transfer handler", func() { + var env *Builder + var ldesc *artdesc.Descriptor + + BeforeEach(func() { + env = NewBuilder() + + env.RSAKeyPair(SIGNATURE) + + env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { + ldesc = OCIManifest1(env) + }) + + FakeOCIRepo(env, OCIPATH, OCIHOST) + + env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { + env.Component(COMPONENT, func() { + env.Version(VERSION, func() { + env.Provider(PROVIDER) + TestDataResource(env) + env.Resource("artifact", "", resourcetypes.OCI_IMAGE, metav1.LocalRelation, func() { + env.Access( + ociartifact.New(oci.StandardOCIRef(OCIHOST+".alias", OCINAMESPACE, OCIVERSION)), + ) + }) + }) + }) + }) + + env.OCMCommonTransport(ARCH2, accessio.FormatDirectory, func() { + env.Component(COMPONENT2, func() { + env.Version(VERSION, func() { + env.Reference("ref", COMPONENT, VERSION) + env.Provider(PROVIDER) + }) + }) + }) + }) + + AfterEach(func() { + env.Cleanup() + }) + + It("test", func() { + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) + defer Close(src, "source") + cv := Must(src.LookupComponentVersion(COMPONENT, VERSION)) + defer Close(cv, "source cv") + tgt := Must(ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) + defer Close(tgt, "target") + + tcv := Must(tgt.NewComponentVersion(cv.GetName(), cv.GetVersion())) + defer Close(tcv, "target version") + + res := Must(cv.GetResource(metav1.NewIdentity("artifact"))) + acc := Must(res.Access()) + + m := Must(acc.AccessMethod(cv)) + defer Close(m, "method") + + blob := Must(accspeccpi.BlobAccessForAccessMethod(m)) + defer Close(blob, "blob") + MustBeSuccessful(tcv.SetResourceBlob(res.Meta(), blob, "", nil, ocm.SkipVerify())) + + MustBeSuccessful(tgt.AddComponentVersion(tcv)) + }) + + DescribeTable("it should copy a resource by value to a ctf file", func(acc string, compose bool, topts ...transferhandler.TransferOption) { + compositionmodeattr.Set(env.OCMContext(), compose) + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) + defer Close(src, "source") + cv := Must(src.LookupComponentVersion(COMPONENT, VERSION)) + defer Close(cv, "source cv") + tgt := Must(ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) + defer Close(tgt, "target") + + // handler, err := standard.New(standard.ResourcesByValue()) + p, buf := common.NewBufferedPrinter() + opts := append(topts, standard.ResourcesByValue(), transfer.WithPrinter(p), &optionsChecker{}) + MustBeSuccessful(transfer.Transfer(cv, tgt, opts...)) + Expect(env.DirExists(OUT)).To(BeTrue()) + + Expect(string(buf.Bytes())).To(StringEqualTrimmedWithContext(` +transferring version "github.com/mandelsoft/test:v1"... +...resource 0 testdata[PlainText]... +...resource 1 artifact[ociImage](ocm/value:v2.0)... +...adding component version... +`)) + }, + Entry("without preserve global", + "{\"localReference\":\"%s\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\""+OCINAMESPACE+":"+OCIVERSION+"\",\"type\":\"localBlob\"}", + false), + Entry("with preserve global", + "{\"globalAccess\":{\"imageReference\":\"alias.alias/ocm/value:v2.0\",\"type\":\"ociArtifact\"},\"localReference\":\"%s\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\"ocm/value:v2.0\",\"type\":\"localBlob\"}", + false, + standard.KeepGlobalAccess()), + + Entry("with composition and without preserve global", + "{\"localReference\":\"%s\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\""+OCINAMESPACE+":"+OCIVERSION+"\",\"type\":\"localBlob\"}", + true), + Entry("with composition and with preserve global", + "{\"globalAccess\":{\"imageReference\":\"alias.alias/ocm/value:v2.0\",\"type\":\"ociArtifact\"},\"localReference\":\"%s\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\"ocm/value:v2.0\",\"type\":\"localBlob\"}", + true, + standard.KeepGlobalAccess()), + ) + + DescribeTable("it should copy a resource by value to a ctf file", func(acc string, compose bool, topts ...transferhandler.TransferOption) { + compositionmodeattr.Set(env.OCMContext(), compose) + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) + defer Close(src, "source") + cv := Must(src.LookupComponentVersion(COMPONENT, VERSION)) + defer Close(cv, "source cv") + tgt := Must(ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) + defer Close(tgt, "target") + + // handler, err := standard.New(standard.ResourcesByValue()) + p, buf := common.NewBufferedPrinter() + opts := append(topts, standard.ResourcesByValue(), transfer.WithPrinter(p), &optionsChecker{}) + MustBeSuccessful(transfer.Transfer(cv, tgt, opts...)) + Expect(env.DirExists(OUT)).To(BeTrue()) + + Expect(string(buf.Bytes())).To(StringEqualTrimmedWithContext(` +transferring version "github.com/mandelsoft/test:v1"... +...resource 0 testdata[PlainText]... +...resource 1 artifact[ociImage](ocm/value:v2.0)... +...adding component version... +`)) + + var nested finalizer.Finalizer + defer Defer(nested.Finalize) + + list := Must(tgt.ComponentLister().GetComponents("", true)) + Expect(list).To(Equal([]string{COMPONENT})) + tcv := Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) + nested.Close(tcv, "target cv") + Expect(len(tcv.GetDescriptor().Resources)).To(Equal(2)) + data := Must(json.Marshal(tcv.GetDescriptor().Resources[1].Access)) + + fmt.Printf("%s\n", string(data)) + hash := HashManifest1(artifactset.DefaultArtifactSetDescriptorFileName) + Expect(string(data)).To(StringEqualWithContext(fmt.Sprintf(acc, hash))) + + tcd := tcv.GetDescriptor().Copy() + r := Must(tcv.GetResourceByIndex(1)) + meth := Must(r.AccessMethod()) + nested.Close(meth, "method") + reader := Must(meth.Reader()) + nested.Close(reader, "reader") + set := Must(artifactset.Open(accessobj.ACC_READONLY, "", 0, accessio.Reader(reader))) + nested.Close(set, "set") + + _, blob := Must2(set.GetBlobData(ldesc.Digest)) + nested.Close(blob) + data = Must(blob.Get()) + Expect(string(data)).To(Equal("manifestlayer")) + + MustBeSuccessful(nested.Finalize()) + + // retransfer + buf.Reset() + MustBeSuccessful(transfer.Transfer(cv, tgt, opts...)) + Expect(string(buf.Bytes())).To(StringEqualTrimmedWithContext(` +transferring version "github.com/mandelsoft/test:v1"... + version "github.com/mandelsoft/test:v1" already present -> skip transport`)) + tcv = Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) + nested.Close(tcv, "target cv") + Expect(tcd).To(DeepEqual(tcv.GetDescriptor())) + MustBeSuccessful(nested.Finalize()) + + // modify volatile + cv.GetDescriptor().Labels.Set("new", "newvalue") + tcd.Labels.Set("new", "newvalue") + buf.Reset() + MustBeSuccessful(transfer.Transfer(cv, tgt, opts...)) + Expect(string(buf.Bytes())).To(StringEqualTrimmedWithContext(` +transferring version "github.com/mandelsoft/test:v1"... + updating volatile properties of "github.com/mandelsoft/test:v1" +...adding component version... +`)) + tcv = Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) + nested.Close(tcv, "target cv") + Expect(tcd).To(DeepEqual(tcv.GetDescriptor())) + MustBeSuccessful(nested.Finalize()) + + // modify one artifact and overwrite + MustBeSuccessful(cv.SetResourceBlob(Must(cv.GetResourceByIndex(0)).Meta().Fresh(), blobaccess.ForString(mime.MIME_TEXT, "otherdata"), "", nil)) + tcd.Resources[0].Digest = DS_OTHERDATA + tcd.Resources[0].Access = Must(runtime.ToUnstructuredVersionedTypedObject(localblob.New("sha256:"+D_OTHERDATA, "", mime.MIME_TEXT, nil))) + buf.Reset() + MustBeSuccessful(transfer.Transfer(cv, tgt, append(opts, standard.Overwrite())...)) + Expect(string(buf.Bytes())).To(StringEqualTrimmedWithContext(` +transferring version "github.com/mandelsoft/test:v1"... +warning: version "github.com/mandelsoft/test:v1" already present, but differs because some artifact digests are changed (transport enforced by overwrite option) +...resource 0 testdata[PlainText] (overwrite) +...resource 1 artifact[ociImage](ocm/value:v2.0) (already present) +...adding component version... +`)) + tcv = Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) + nested.Close(tcv, "target cv") + Expect(tcd).To(DeepEqual(tcv.GetDescriptor())) + MustBeSuccessful(nested.Finalize()) + }, + Entry("without preserve global", + "{\"localReference\":\"%s\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\""+OCINAMESPACE+":"+OCIVERSION+"\",\"type\":\"localBlob\"}", + false), + Entry("with preserve global", + "{\"globalAccess\":{\"imageReference\":\"alias.alias/ocm/value:v2.0\",\"type\":\"ociArtifact\"},\"localReference\":\"%s\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\"ocm/value:v2.0\",\"type\":\"localBlob\"}", + false, + standard.KeepGlobalAccess()), + + Entry("with composition and without preserve global", + "{\"localReference\":\"%s\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\""+OCINAMESPACE+":"+OCIVERSION+"\",\"type\":\"localBlob\"}", + true), + Entry("with composition and with preserve global", + "{\"globalAccess\":{\"imageReference\":\"alias.alias/ocm/value:v2.0\",\"type\":\"ociArtifact\"},\"localReference\":\"%s\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\"ocm/value:v2.0\",\"type\":\"localBlob\"}", + true, + standard.KeepGlobalAccess()), + ) + + DescribeTable("it should copy a resource by value to a ctf file for re-transport", func(acc string, mode bool, topts ...transferhandler.TransferOption) { + compositionmodeattr.Set(env.OCMContext(), mode) + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) + defer Close(src, "source") + cv := Must(src.LookupComponentVersion(COMPONENT, VERSION)) + defer Close(cv, "source cv") + tgt := Must(ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) + defer Close(tgt, "target") + + // transfer by reference, first + p, buf := common.NewBufferedPrinter() + opts := append(topts, transfer.WithPrinter(p), &optionsChecker{}) + MustBeSuccessful(transfer.Transfer(cv, tgt, opts...)) + Expect(env.DirExists(OUT)).To(BeTrue()) + Expect(string(buf.Bytes())).To(StringEqualTrimmedWithContext(` +transferring version "github.com/mandelsoft/test:v1"... +...resource 0 testdata[PlainText]... +...adding component version... +`)) + var nested finalizer.Finalizer + defer Defer(nested.Finalize) + + list := Must(tgt.ComponentLister().GetComponents("", true)) + Expect(list).To(Equal([]string{COMPONENT})) + tcv := Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) + nested.Close(tcv, "target cv") + Expect(len(tcv.GetDescriptor().Resources)).To(Equal(2)) + data := Must(json.Marshal(tcv.GetDescriptor().Resources[1].Access)) + + tcd := tcv.GetDescriptor().Copy() + r := Must(tcv.GetResourceByIndex(1)) + Expect(Must(r.Access()).GetType()).To(Equal(ociartifact.Type)) + MustBeSuccessful(nested.Finalize()) + + // retransfer with value transport + buf.Reset() + opts = append(topts, standard.ResourcesByValue(), transfer.WithPrinter(p), &optionsChecker{}) + MustBeSuccessful(transfer.Transfer(cv, tgt, opts...)) + Expect(env.DirExists(OUT)).To(BeTrue()) + Expect(string(buf.Bytes())).To(StringEqualTrimmedWithContext(` +transferring version "github.com/mandelsoft/test:v1"... + version "github.com/mandelsoft/test:v1" already present -> but requires resource transport +...resource 0 testdata[PlainText] (already present) +...resource 1 artifact[ociImage](ocm/value:v2.0) (copy) +...adding component version... +`)) + + list = Must(tgt.ComponentLister().GetComponents("", true)) + Expect(list).To(Equal([]string{COMPONENT})) + tcv = Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) + nested.Close(tcv, "target cv") + Expect(len(tcv.GetDescriptor().Resources)).To(Equal(2)) + data = Must(json.Marshal(tcv.GetDescriptor().Resources[1].Access)) + + fmt.Printf("%s\n", string(data)) + hash := HashManifest1(artifactset.DefaultArtifactSetDescriptorFileName) + Expect(string(data)).To(StringEqualWithContext(fmt.Sprintf(acc, hash))) + + tcd = tcv.GetDescriptor().Copy() + r = Must(tcv.GetResourceByIndex(1)) + meth := Must(r.AccessMethod()) + nested.Close(meth, "method") + reader := Must(meth.Reader()) + nested.Close(reader, "reader") + set := Must(artifactset.Open(accessobj.ACC_READONLY, "", 0, accessio.Reader(reader))) + nested.Close(set, "set") + + _, blob := Must2(set.GetBlobData(ldesc.Digest)) + nested.Close(blob) + data = Must(blob.Get()) + Expect(string(data)).To(Equal("manifestlayer")) + + MustBeSuccessful(nested.Finalize()) + + // retransfer + buf.Reset() + MustBeSuccessful(transfer.Transfer(cv, tgt, opts...)) + Expect(string(buf.Bytes())).To(StringEqualTrimmedWithContext(` +transferring version "github.com/mandelsoft/test:v1"... + version "github.com/mandelsoft/test:v1" already present -> skip transport`)) + tcv = Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) + nested.Close(tcv, "target cv") + Expect(tcd).To(DeepEqual(tcv.GetDescriptor())) + MustBeSuccessful(nested.Finalize()) + + // modify volatile + cv.GetDescriptor().Labels.Set("new", "newvalue") + tcd.Labels.Set("new", "newvalue") + buf.Reset() + MustBeSuccessful(transfer.Transfer(cv, tgt, opts...)) + Expect(string(buf.Bytes())).To(StringEqualTrimmedWithContext(` +transferring version "github.com/mandelsoft/test:v1"... + updating volatile properties of "github.com/mandelsoft/test:v1" +...adding component version... +`)) + tcv = Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) + nested.Close(tcv, "target cv") + Expect(tcd).To(DeepEqual(tcv.GetDescriptor())) + MustBeSuccessful(nested.Finalize()) + + // modify one artifact and overwrite + MustBeSuccessful(cv.SetResourceBlob(Must(cv.GetResourceByIndex(0)).Meta().Fresh(), blobaccess.ForString(mime.MIME_TEXT, "otherdata"), "", nil)) + tcd.Resources[0].Digest = DS_OTHERDATA + tcd.Resources[0].Access = Must(runtime.ToUnstructuredVersionedTypedObject(localblob.New("sha256:"+D_OTHERDATA, "", mime.MIME_TEXT, nil))) + buf.Reset() + transfer.Breakpoints = true + MustBeSuccessful(transfer.Transfer(cv, tgt, append(opts, standard.ResourcesByValue(), standard.Overwrite())...)) + transfer.Breakpoints = false + Expect(string(buf.Bytes())).To(StringEqualTrimmedWithContext(` +transferring version "github.com/mandelsoft/test:v1"... +warning: version "github.com/mandelsoft/test:v1" already present, but differs because some artifact digests are changed (transport enforced by overwrite option) +...resource 0 testdata[PlainText] (overwrite) +...resource 1 artifact[ociImage](ocm/value:v2.0) (already present) +...adding component version... +`)) + tcv = Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) + nested.Close(tcv, "target cv") + ntcd := tcv.GetDescriptor() + Expect(tcd).To(DeepEqual(ntcd)) + MustBeSuccessful(nested.Finalize()) + }, + Entry("without preserve global", + "{\"localReference\":\"%s\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\""+OCINAMESPACE+":"+OCIVERSION+"\",\"type\":\"localBlob\"}", + false), + Entry("with preserve global", + "{\"globalAccess\":{\"imageReference\":\"alias.alias/ocm/value:v2.0\",\"type\":\"ociArtifact\"},\"localReference\":\"%s\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\"ocm/value:v2.0\",\"type\":\"localBlob\"}", + false, + standard.KeepGlobalAccess()), + Entry("with composition and without preserve global", + "{\"localReference\":\"%s\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\""+OCINAMESPACE+":"+OCIVERSION+"\",\"type\":\"localBlob\"}", + true), + Entry("with composition and with preserve global", + "{\"globalAccess\":{\"imageReference\":\"alias.alias/ocm/value:v2.0\",\"type\":\"ociArtifact\"},\"localReference\":\"%s\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\"ocm/value:v2.0\",\"type\":\"localBlob\"}", + true, + standard.KeepGlobalAccess()), + ) + + It("disable value transport of oci access", func() { + src, err := ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env) + Expect(err).To(Succeed()) + cv, err := src.LookupComponentVersion(COMPONENT, VERSION) + Expect(err).To(Succeed()) + tgt, err := ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env) + Expect(err).To(Succeed()) + defer tgt.Close() + + opts := &standard.Options{} + Expect(opts.Apply(standard.ResourcesByValue(), standard.OmitAccessTypes(ociartifact.Type))).To(Succeed()) + Expect(opts.IsResourcesByValue()).To(BeTrue()) + Expect(opts.IsAccessTypeOmitted(ociartifact.Type)).To(BeTrue()) + Expect(opts.IsAccessTypeOmitted(ociartifact.LegacyType)).To(BeFalse()) + + handler := standard.NewDefaultHandler(opts) + Expect(err).To(Succeed()) + err = transfer.TransferVersion(nil, nil, cv, tgt, handler) + Expect(err).To(Succeed()) + Expect(env.DirExists(OUT)).To(BeTrue()) + + list, err := tgt.ComponentLister().GetComponents("", true) + Expect(err).To(Succeed()) + Expect(list).To(Equal([]string{COMPONENT})) + comp, err := tgt.LookupComponentVersion(COMPONENT, VERSION) + Expect(err).To(Succeed()) + Expect(len(comp.GetDescriptor().Resources)).To(Equal(2)) + + r, err := comp.GetResourceByIndex(1) + Expect(err).To(Succeed()) + + a, err := r.Access() + Expect(err).To(Succeed()) + Expect(a.GetType()).To(Equal(ociartifact.Type)) + }) + + It("it should use additional resolver to resolve component ref", func() { + parentSrc, err := ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH2, 0, env) + Expect(err).To(Succeed()) + cv, err := parentSrc.LookupComponentVersion(COMPONENT2, VERSION) + Expect(err).To(Succeed()) + childSrc, err := ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env) + Expect(err).To(Succeed()) + tgt, err := ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env) + Expect(err).To(Succeed()) + defer tgt.Close() + handler, err := standard.New(standard.Recursive(), standard.Resolver(childSrc)) + Expect(err).To(Succeed()) + err = transfer.TransferVersion(nil, nil, cv, tgt, handler) + Expect(err).To(Succeed()) + Expect(env.DirExists(OUT)).To(BeTrue()) + + list, err := tgt.ComponentLister().GetComponents("", true) + Expect(err).To(Succeed()) + Expect(list).To(ContainElements([]string{COMPONENT2, COMPONENT})) + _, err = tgt.LookupComponentVersion(COMPONENT2, VERSION) + Expect(err).To(Succeed()) + _, err = tgt.LookupComponentVersion(COMPONENT, VERSION) + Expect(err).To(Succeed()) + }) + + It("it should copy signatures", func() { + src, err := ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env) + Expect(err).To(Succeed()) + cv, err := src.LookupComponentVersion(COMPONENT, VERSION) + Expect(err).To(Succeed()) + + resolver := ocm.NewCompoundResolver(src) + + opts := ocmsign.NewOptions( + ocmsign.Sign(signingattr.Get(env.OCMContext()).GetSigner(SIGN_ALGO), SIGNATURE), + ocmsign.Resolver(resolver), + ocmsign.Update(), ocmsign.VerifyDigests(), + ) + Expect(opts.Complete(env.OCMContext())).To(Succeed()) + digest := "45aefd9317bde6c66d5edca868cf7b9a5313a6f965609af4e58bbfd44ae6e92c" + dig, err := ocmsign.Apply(nil, nil, cv, opts) + Expect(err).To(Succeed()) + fmt.Print(dig.Value) + Expect(dig.Value).To(Equal(digest)) + + Expect(len(cv.GetDescriptor().Signatures)).To(Equal(1)) + + tgt, err := ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env) + Expect(err).To(Succeed()) + defer tgt.Close() + handler, err := standard.New(standard.ResourcesByValue()) + Expect(err).To(Succeed()) + err = transfer.TransferVersion(nil, nil, cv, tgt, handler) + Expect(err).To(Succeed()) + Expect(env.DirExists(OUT)).To(BeTrue()) + + resolver = ocm.NewCompoundResolver(tgt) + + opts = ocmsign.NewOptions( + ocmsign.Resolver(resolver), + ocmsign.VerifySignature(SIGNATURE), + ocmsign.Update(), ocmsign.VerifyDigests(), + ) + Expect(opts.Complete(env.OCMContext())).To(Succeed()) + dig, err = ocmsign.Apply(nil, nil, cv, opts) + Expect(err).To(Succeed()) + Expect(dig.Value).To(Equal(digest)) + }) +}) diff --git a/api/ocm/tools/transfer/transferhandler/standard/options.go b/api/ocm/tools/transfer/transferhandler/standard/options.go new file mode 100644 index 000000000..bfc052475 --- /dev/null +++ b/api/ocm/tools/transfer/transferhandler/standard/options.go @@ -0,0 +1,662 @@ +package standard + +import ( + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/maputils" + "github.com/mandelsoft/goutils/optionutils" + "github.com/mandelsoft/goutils/set" + "github.com/mandelsoft/goutils/sliceutils" + "golang.org/x/exp/slices" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler" + "ocm.software/ocm/api/utils/runtime" +) + +func init() { + transferhandler.RegisterHandler(1000, &TransferOptionsCreator{}) +} + +type Options struct { + retries *int + recursive *bool + resourcesByValue *bool + localByValue *bool + sourcesByValue *bool + keepGlobalAccess *bool + stopOnExisting *bool + enforceTransport *bool + overwrite *bool + skipUpdate *bool + omitAccessTypes set.Set[string] + omitArtifactTypes set.Set[string] + resolver ocm.ComponentVersionResolver +} + +var ( + _ transferhandler.TransferOption = (*Options)(nil) + + _ RetryOption = (*Options)(nil) + _ ResourcesByValueOption = (*Options)(nil) + _ LocalResourcesByValueOption = (*Options)(nil) + _ EnforceTransportOption = (*Options)(nil) + _ OverwriteOption = (*Options)(nil) + _ SkipUpdateOption = (*Options)(nil) + _ SourcesByValueOption = (*Options)(nil) + _ RecursiveOption = (*Options)(nil) + _ ResolverOption = (*Options)(nil) + _ KeepGlobalAccessOption = (*Options)(nil) + _ OmitAccessTypesOption = (*Options)(nil) + _ OmitArtifactTypesOption = (*Options)(nil) +) + +type TransferOptionsCreator = transferhandler.SpecializedOptionsCreator[*Options, Options] + +func (o *Options) NewOptions() transferhandler.TransferHandlerOptions { + return &Options{} +} + +func (o *Options) NewTransferHandler() (transferhandler.TransferHandler, error) { + return New(o) +} + +func (o *Options) ApplyTransferOption(target transferhandler.TransferOptions) error { + if o.retries != nil { + if opts, ok := target.(RetryOption); ok { + opts.SetRetries(*o.retries) + } + } + if o.recursive != nil { + if opts, ok := target.(RecursiveOption); ok { + opts.SetRecursive(*o.recursive) + } + } + if o.skipUpdate != nil { + if opts, ok := target.(SkipUpdateOption); ok { + opts.SetSkipUpdate(*o.skipUpdate) + } + } + if o.resourcesByValue != nil { + if opts, ok := target.(ResourcesByValueOption); ok { + opts.SetResourcesByValue(*o.resourcesByValue) + } + } + if o.localByValue != nil { + if opts, ok := target.(LocalResourcesByValueOption); ok { + opts.SetLocalResourcesByValue(*o.localByValue) + } + } + if o.sourcesByValue != nil { + if opts, ok := target.(SourcesByValueOption); ok { + opts.SetSourcesByValue(*o.sourcesByValue) + } + } + if o.keepGlobalAccess != nil { + if opts, ok := target.(KeepGlobalAccessOption); ok { + opts.SetKeepGlobalAccess(*o.keepGlobalAccess) + } + } + if o.stopOnExisting != nil { + if opts, ok := target.(StopOnExistingVersionOption); ok { + opts.SetStopOnExistingVersion(*o.stopOnExisting) + } + } + if o.enforceTransport != nil { + if opts, ok := target.(EnforceTransportOption); ok { + opts.SetEnforceTransport(*o.enforceTransport) + } + } + if o.overwrite != nil { + if opts, ok := target.(OverwriteOption); ok { + opts.SetOverwrite(*o.overwrite) + } + } + if o.omitAccessTypes != nil { + if opts, ok := target.(OmitAccessTypesOption); ok { + opts.SetOmittedAccessTypes(maputils.OrderedKeys(o.omitAccessTypes)...) + } + } + if o.omitArtifactTypes != nil { + if opts, ok := target.(OmitArtifactTypesOption); ok { + opts.SetOmittedArtifactTypes(maputils.OrderedKeys(o.omitAccessTypes)...) + } + } + if o.resolver != nil { + if opts, ok := target.(ResolverOption); ok { + opts.SetResolver(o.resolver) + } + } + return nil +} + +func (o *Options) Apply(opts ...transferhandler.TransferOption) error { + return transferhandler.ApplyOptions(o, opts...) +} + +func (o *Options) SetEnforceTransport(enforce bool) { + o.enforceTransport = &enforce +} + +func (o *Options) IsTransportEnforced() bool { + return optionutils.AsBool(o.enforceTransport) +} + +func (o *Options) SetOverwrite(overwrite bool) { + o.overwrite = &overwrite +} + +func (o *Options) IsOverwrite() bool { + return optionutils.AsBool(o.overwrite) +} + +func (o *Options) SetSkipUpdate(skipupdate bool) { + o.skipUpdate = &skipupdate +} + +func (o *Options) IsSkipUpdate() bool { + return optionutils.AsBool(o.skipUpdate) +} + +func (o *Options) SetRecursive(recursive bool) { + o.recursive = &recursive +} + +func (o *Options) IsRecursive() bool { + return optionutils.AsBool(o.recursive) +} + +func (o *Options) SetResourcesByValue(resourcesByValue bool) { + o.resourcesByValue = &resourcesByValue +} + +func (o *Options) IsResourcesByValue() bool { + return optionutils.AsBool(o.resourcesByValue) +} + +func (o *Options) SetLocalResourcesByValue(resourcesByValue bool) { + o.localByValue = &resourcesByValue +} + +func (o *Options) IsLocalResourcesByValue() bool { + return optionutils.AsBool(o.localByValue) +} + +func (o *Options) SetSourcesByValue(sourcesByValue bool) { + o.sourcesByValue = &sourcesByValue +} + +func (o *Options) IsSourcesByValue() bool { + return optionutils.AsBool(o.sourcesByValue) +} + +func (o *Options) SetKeepGlobalAccess(keepGlobalAccess bool) { + o.keepGlobalAccess = &keepGlobalAccess +} + +func (o *Options) IsKeepGlobalAccess() bool { + return optionutils.AsBool(o.keepGlobalAccess) +} + +func (o *Options) SetRetries(retries int) { + o.retries = &retries +} + +func (o *Options) GetRetries() int { + if o.retries == nil { + return 0 + } + return *o.retries +} + +func (o *Options) SetResolver(resolver ocm.ComponentVersionResolver) { + o.resolver = resolver +} + +func (o *Options) GetResolver() ocm.ComponentVersionResolver { + return o.resolver +} + +func (o *Options) SetStopOnExistingVersion(stopOnExistingVersion bool) { + o.stopOnExisting = &stopOnExistingVersion +} + +func (o *Options) IsStopOnExistingVersion() bool { + return optionutils.AsBool(o.stopOnExisting) +} + +func (o *Options) SetOmittedAccessTypes(list ...string) { + o.omitAccessTypes = set.New[string]() + for _, t := range list { + o.omitAccessTypes.Add(t) + } +} + +func (o *Options) AddOmittedAccessTypes(list ...string) { + if o.omitAccessTypes == nil { + o.omitAccessTypes = set.New[string]() + } + for _, t := range list { + o.omitAccessTypes.Add(t) + } +} + +func (o *Options) GetOmittedAccessTypes() []string { + if o.omitAccessTypes == nil { + return nil + } + return maputils.OrderedKeys(o.omitAccessTypes) +} + +func (o *Options) IsAccessTypeOmitted(t string) bool { + if o.omitAccessTypes == nil { + return false + } + if o.omitAccessTypes.Contains(t) { + return true + } + k, _ := runtime.KindVersion(t) + return o.omitAccessTypes.Contains(k) +} + +func (o *Options) SetOmittedArtifactTypes(list ...string) { + o.omitArtifactTypes = set.New[string]() + for _, t := range list { + o.omitArtifactTypes.Add(t) + } +} + +func (o *Options) AddOmittedArtifactTypes(list ...string) { + if o.omitArtifactTypes == nil { + o.omitArtifactTypes = set.New[string]() + } + for _, t := range list { + o.omitArtifactTypes.Add(t) + } +} + +func (o *Options) GetOmittedArtifactTypes() []string { + if o.omitArtifactTypes == nil { + return nil + } + return maputils.OrderedKeys(o.omitArtifactTypes) +} + +func (o *Options) IsArtifactTypeOmitted(t string) bool { + if o.omitArtifactTypes == nil { + return false + } + if o.omitArtifactTypes.Contains(t) { + return true + } + return o.omitArtifactTypes.Contains(t) +} + +////////////////////////////////////////////////////////////////////////////// + +type EnforceTransportOption interface { + SetEnforceTransport(bool) + IsTransportEnforced() bool +} + +type enforceTransportOption struct { + TransferOptionsCreator + enforce bool +} + +func (o *enforceTransportOption) ApplyTransferOption(to transferhandler.TransferOptions) error { + if eff, ok := to.(EnforceTransportOption); ok { + eff.SetEnforceTransport(o.enforce) + return nil + } else { + return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "enforceTransport") + } +} + +// EnforceTransport enforces a transport of a component version as it is. +// This controls whether transport is carried out +// as if the component version were not present at the destination. +func EnforceTransport(args ...bool) transferhandler.TransferOption { + return &enforceTransportOption{ + enforce: optionutils.GetOptionFlag(args...), + } +} + +/////////////////////////////////////////////////////////////////////////////// + +type OverwriteOption interface { + SetOverwrite(bool) + IsOverwrite() bool +} + +type overwriteOption struct { + TransferOptionsCreator + overwrite bool +} + +func (o *overwriteOption) ApplyTransferOption(to transferhandler.TransferOptions) error { + if eff, ok := to.(OverwriteOption); ok { + eff.SetOverwrite(o.overwrite) + return nil + } else { + return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "overwrite") + } +} + +// Overwrite enables the modification of digest relevant information in a component version. +func Overwrite(args ...bool) transferhandler.TransferOption { + return &overwriteOption{ + overwrite: optionutils.GetOptionFlag(args...), + } +} + +/////////////////////////////////////////////////////////////////////////////// + +type SkipUpdateOption interface { + SetSkipUpdate(bool) + IsSkipUpdate() bool +} + +type skipUpdateOption bool + +func (o skipUpdateOption) ApplyTransferOption(to transferhandler.TransferOptions) error { + if eff, ok := to.(SkipUpdateOption); ok { + eff.SetSkipUpdate(bool(o)) + return nil + } else { + return errors.ErrNotSupported("skip-update") + } +} + +// SkipUpdate enables the modification of non-digest (volatile) relevant information in a component version. +func SkipUpdate(args ...bool) transferhandler.TransferOption { + return skipUpdateOption(optionutils.GetOptionFlag(args...)) +} + +/////////////////////////////////////////////////////////////////////////////// + +type RetryOption interface { + SetRetries(n int) + GetRetries() int +} + +type retryOption struct { + TransferOptionsCreator + retries int +} + +func (o *retryOption) ApplyTransferOption(to transferhandler.TransferOptions) error { + if eff, ok := to.(RetryOption); ok { + eff.SetRetries(o.retries) + return nil + } else { + return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "retry") + } +} + +// Retries sets the number of retries for failing update operations. +func Retries(retries int) transferhandler.TransferOption { + return &retryOption{retries: retries} +} + +/////////////////////////////////////////////////////////////////////////////// + +type RecursiveOption interface { + SetRecursive(bool) + IsRecursive() bool +} + +type recursiveOption struct { + TransferOptionsCreator + flag bool +} + +func (o *recursiveOption) ApplyTransferOption(to transferhandler.TransferOptions) error { + if eff, ok := to.(RecursiveOption); ok { + eff.SetRecursive(o.flag) + return nil + } else { + return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "recursive") + } +} + +// Recursive enables the transport of the reference closure of a component version. +func Recursive(args ...bool) transferhandler.TransferOption { + return &recursiveOption{flag: optionutils.GetOptionFlag(args...)} +} + +/////////////////////////////////////////////////////////////////////////////// + +type ResourcesByValueOption interface { + SetResourcesByValue(bool) + IsResourcesByValue() bool +} + +type resourcesByValueOption struct { + TransferOptionsCreator + flag bool +} + +func (o *resourcesByValueOption) ApplyTransferOption(to transferhandler.TransferOptions) error { + if eff, ok := to.(ResourcesByValueOption); ok { + eff.SetResourcesByValue(o.flag) + return nil + } else { + return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "resources-by-value") + } +} + +// ResourcesByValue enables the transport a resources by values instead of by-reference. +func ResourcesByValue(args ...bool) transferhandler.TransferOption { + return &resourcesByValueOption{flag: optionutils.GetOptionFlag(args...)} +} + +/////////////////////////////////////////////////////////////////////////////// + +type LocalResourcesByValueOption interface { + SetLocalResourcesByValue(bool) + IsLocalResourcesByValue() bool +} + +type intrscsByValueOption struct { + TransferOptionsCreator + flag bool +} + +func (o *intrscsByValueOption) ApplyTransferOption(to transferhandler.TransferOptions) error { + if eff, ok := to.(LocalResourcesByValueOption); ok { + eff.SetLocalResourcesByValue(o.flag) + return nil + } else { + return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "local-resources by-value") + } +} + +// LocalResourcesByValue enables the transport a local (relation) resources by values instead of by-reference. +func LocalResourcesByValue(args ...bool) transferhandler.TransferOption { + return &intrscsByValueOption{flag: optionutils.GetOptionFlag(args...)} +} + +/////////////////////////////////////////////////////////////////////////////// + +type SourcesByValueOption interface { + SetSourcesByValue(bool) + IsSourcesByValue() bool +} + +type sourcesByValueOption struct { + TransferOptionsCreator + flag bool +} + +func (o *sourcesByValueOption) ApplyTransferOption(to transferhandler.TransferOptions) error { + if eff, ok := to.(SourcesByValueOption); ok { + eff.SetSourcesByValue(o.flag) + return nil + } else { + return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "sources by-value") + } +} + +// SourcesByValue enables the transport a sources by values instead of by-reference. +func SourcesByValue(args ...bool) transferhandler.TransferOption { + return &sourcesByValueOption{flag: optionutils.GetOptionFlag(args...)} +} + +/////////////////////////////////////////////////////////////////////////////// + +type ResolverOption interface { + GetResolver() ocm.ComponentVersionResolver + SetResolver(ocm.ComponentVersionResolver) +} + +type resolverOption struct { + TransferOptionsCreator + resolver ocm.ComponentVersionResolver +} + +func (o *resolverOption) ApplyTransferOption(to transferhandler.TransferOptions) error { + if eff, ok := to.(ResolverOption); ok { + eff.SetResolver(o.resolver) + return nil + } else { + return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "resolver") + } +} + +// Resolver specifies a resolver used to resolve nested component versions. +func Resolver(resolver ocm.ComponentVersionResolver) transferhandler.TransferOption { + return &resolverOption{ + resolver: resolver, + } +} + +/////////////////////////////////////////////////////////////////////////////// + +type KeepGlobalAccessOption interface { + SetKeepGlobalAccess(bool) + IsKeepGlobalAccess() bool +} + +type keepGlobalOption struct { + TransferOptionsCreator + flag bool +} + +func (o *keepGlobalOption) ApplyTransferOption(to transferhandler.TransferOptions) error { + if eff, ok := to.(KeepGlobalAccessOption); ok { + eff.SetKeepGlobalAccess(o.flag) + return nil + } else { + return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "keep-global-access") + } +} + +// KeepGlobalAccess enables to keep local blobs if uploaders are used to upload imported blobs. +func KeepGlobalAccess(args ...bool) transferhandler.TransferOption { + return &keepGlobalOption{flag: optionutils.GetOptionFlag(args...)} +} + +/////////////////////////////////////////////////////////////////////////////// + +type StopOnExistingVersionOption interface { + SetStopOnExistingVersion(bool) + IsStopOnExistingVersion() bool +} + +type stopOnExistingVersionOption struct { + TransferOptionsCreator + flag bool +} + +func (o *stopOnExistingVersionOption) ApplyTransferOption(to transferhandler.TransferOptions) error { + if eff, ok := to.(StopOnExistingVersionOption); ok { + eff.SetStopOnExistingVersion(o.flag) + return nil + } else { + return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "stop-on-existing") + } +} + +// StopOnExistingVersion stops the recursion on component versions already present in target. +func StopOnExistingVersion(args ...bool) transferhandler.TransferOption { + return &stopOnExistingVersionOption{flag: optionutils.GetOptionFlag(args...)} +} + +/////////////////////////////////////////////////////////////////////////////// + +type OmitAccessTypesOption interface { + SetOmittedAccessTypes(...string) + GetOmittedAccessTypes() []string +} + +type omitAccessTypesOption struct { + TransferOptionsCreator + add bool + list []string +} + +func (o *omitAccessTypesOption) ApplyTransferOption(to transferhandler.TransferOptions) error { + if eff, ok := to.(OmitAccessTypesOption); ok { + if o.add { + eff.SetOmittedAccessTypes(sliceutils.CopyAppend(eff.GetOmittedAccessTypes(), o.list...)...) + } else { + eff.SetOmittedAccessTypes(o.list...) + } + return nil + } else { + return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "omit-access-types") + } +} + +// OmitAccessTypes somits the specified access types from value transport. +func OmitAccessTypes(list ...string) transferhandler.TransferOption { + return &omitAccessTypesOption{ + list: slices.Clone(list), + } +} + +func AddOmittedAccessTypes(list ...string) transferhandler.TransferOption { + return &omitAccessTypesOption{ + add: true, + list: slices.Clone(list), + } +} + +/////////////////////////////////////////////////////////////////////////////// + +type OmitArtifactTypesOption interface { + SetOmittedArtifactTypes(...string) + GetOmittedArtifactTypes() []string +} + +type omitArtifactTypesOption struct { + add bool + list []string +} + +func (o *omitArtifactTypesOption) ApplyTransferOption(to transferhandler.TransferOptions) error { + if eff, ok := to.(OmitAccessTypesOption); ok { + if o.add { + eff.SetOmittedAccessTypes(append(eff.GetOmittedAccessTypes(), o.list...)...) + } else { + eff.SetOmittedAccessTypes(o.list...) + } + return nil + } else { + return errors.ErrNotSupported("omit-artifact-types") + } +} + +// OmitArtifactTypes somits the specified artifact types from value transport. +func OmitArtifactTypes(list ...string) transferhandler.TransferOption { + return &omitArtifactTypesOption{ + list: slices.Clone(list), + } +} + +func AddOmittedArtifactTypes(list ...string) transferhandler.TransferOption { + return &omitArtifactTypesOption{ + add: true, + list: slices.Clone(list), + } +} diff --git a/pkg/contexts/ocm/transfer/transferhandler/standard/suite_test.go b/api/ocm/tools/transfer/transferhandler/standard/suite_test.go similarity index 100% rename from pkg/contexts/ocm/transfer/transferhandler/standard/suite_test.go rename to api/ocm/tools/transfer/transferhandler/standard/suite_test.go diff --git a/pkg/contexts/ocm/transfer/transferhandler/standard/utils.go b/api/ocm/tools/transfer/transferhandler/standard/utils.go similarity index 100% rename from pkg/contexts/ocm/transfer/transferhandler/standard/utils.go rename to api/ocm/tools/transfer/transferhandler/standard/utils.go diff --git a/api/ocm/tools/transfer/transferhandler/standard/utils_test.go b/api/ocm/tools/transfer/transferhandler/standard/utils_test.go new file mode 100644 index 000000000..09bcd8552 --- /dev/null +++ b/api/ocm/tools/transfer/transferhandler/standard/utils_test.go @@ -0,0 +1,66 @@ +package standard_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/mandelsoft/goutils/generics" + + me "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" +) + +type Target struct { + flag *bool + text string +} + +////// + +var _ Flag = (*Target)(nil) + +type Flag interface { + SetFlag(b bool) +} + +func (t *Target) SetFlag(b bool) { + t.flag = &b +} + +////// + +var _ Text = (*Target)(nil) + +type Text interface { + SetText(v string) +} + +func (t *Target) SetText(v string) { + t.text = v +} + +var _ = Describe("utils", func() { + It("handles pointer arg", func() { + var v *bool + t := &Target{} + + me.HandleOption[Flag](v, t) + Expect(t.flag).To(BeNil()) + + v = generics.Pointer(false) + me.HandleOption[Flag](v, t) + Expect(t.flag).NotTo(BeNil()) + Expect(*t.flag).To(BeFalse()) + }) + + It("handles value arg", func() { + var v string + t := &Target{text: "old"} + + me.HandleOption[Text](v, t) + Expect(t.text).To(Equal("old")) + + v = "test" + me.HandleOption[Text](v, t) + Expect(t.text).To(Equal("test")) + }) +}) diff --git a/pkg/contexts/ocm/transfer/transferhandler/transferhandler.go b/api/ocm/tools/transfer/transferhandler/transferhandler.go similarity index 95% rename from pkg/contexts/ocm/transfer/transferhandler/transferhandler.go rename to api/ocm/tools/transfer/transferhandler/transferhandler.go index c1c1b8f13..1a5f72314 100644 --- a/pkg/contexts/ocm/transfer/transferhandler/transferhandler.go +++ b/api/ocm/tools/transfer/transferhandler/transferhandler.go @@ -3,10 +3,10 @@ package transferhandler import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/contexts/config" - "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" + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" ) const KIND_TRANSFEROPTION = "transfer option" diff --git a/pkg/contexts/ocm/transfer/transferrepo.go b/api/ocm/tools/transfer/transferrepo.go similarity index 86% rename from pkg/contexts/ocm/transfer/transferrepo.go rename to api/ocm/tools/transfer/transferrepo.go index 422b7339b..3fd202e43 100644 --- a/pkg/contexts/ocm/transfer/transferrepo.go +++ b/api/ocm/tools/transfer/transferrepo.go @@ -3,10 +3,10 @@ package transfer import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/common" - "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/transfer/transferhandler/standard" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + common "ocm.software/ocm/api/utils/misc" ) func TransferComponents(printer common.Printer, closure TransportClosure, repo ocm.Repository, prefix string, all bool, tgt ocm.Repository, handler TransferHandler) error { diff --git a/api/ocm/types/interface.go b/api/ocm/types/interface.go new file mode 100644 index 000000000..1103545e6 --- /dev/null +++ b/api/ocm/types/interface.go @@ -0,0 +1,52 @@ +package context + +import ( + "ocm.software/ocm/api/ocm/internal" +) + +type ( + Context = internal.Context + ContextProvider = internal.ContextProvider + LocalContextProvider = internal.LocalContextProvider + ComponentVersionResolver = internal.ComponentVersionResolver + Repository = internal.Repository + RepositorySpecHandlers = internal.RepositorySpecHandlers + RepositorySpecHandler = internal.RepositorySpecHandler + UniformRepositorySpec = internal.UniformRepositorySpec + ComponentLister = internal.ComponentLister + ComponentAccess = internal.ComponentAccess + ComponentVersionAccess = internal.ComponentVersionAccess + AccessSpec = internal.AccessSpec + GenericAccessSpec = internal.GenericAccessSpec + HintProvider = internal.HintProvider + AccessMethod = internal.AccessMethodImpl + AccessType = internal.AccessType + DataAccess = internal.DataAccess + BlobAccess = internal.BlobAccess + SourceAccess = internal.SourceAccess + SourceMeta = internal.SourceMeta + ResourceAccess = internal.ResourceAccess + ResourceMeta = internal.ResourceMeta + RepositorySpec = internal.RepositorySpec + GenericRepositorySpec = internal.GenericRepositorySpec + IntermediateRepositorySpecAspect = internal.IntermediateRepositorySpecAspect + RepositoryType = internal.RepositoryType + RepositoryTypeScheme = internal.RepositoryTypeScheme + RepositoryDelegationRegistry = internal.RepositoryDelegationRegistry + AccessTypeScheme = internal.AccessTypeScheme + ComponentReference = internal.ComponentReference +) + +type ( + DigesterType = internal.DigesterType + BlobDigester = internal.BlobDigester + BlobDigesterRegistry = internal.BlobDigesterRegistry + DigestDescriptor = internal.DigestDescriptor + HasherProvider = internal.HasherProvider + Hasher = internal.Hasher +) + +type ( + BlobHandlerRegistry = internal.BlobHandlerRegistry + BlobHandler = internal.BlobHandler +) diff --git a/pkg/contexts/ocm/usage.go b/api/ocm/usage.go similarity index 100% rename from pkg/contexts/ocm/usage.go rename to api/ocm/usage.go diff --git a/api/ocm/utils.go b/api/ocm/utils.go new file mode 100644 index 000000000..964f22879 --- /dev/null +++ b/api/ocm/utils.go @@ -0,0 +1,126 @@ +package ocm + +import ( + "fmt" + "io" + "strings" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/internal" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" +) + +//////////////////////////////////////////////////////////////////////////////// + +func AssureTargetRepository(session Session, ctx Context, targetref string, opts ...interface{}) (Repository, error) { + var format accessio.FileFormat + var archive string + var fs vfs.FileSystem + + for _, o := range opts { + switch v := o.(type) { + case vfs.FileSystem: + if fs == nil && v != nil { + fs = v + } + case accessio.FileFormat: + format = v + case string: + archive = v + default: + return nil, fmt.Errorf("invalid option type %T", o) + } + } + + ref, err := ParseRepo(targetref) + if err != nil { + return nil, err + } + if ref.TypeHint == "" { + ref.TypeHint = archive + } + if format != "" && ref.TypeHint != "" && !strings.Contains(ref.TypeHint, "+") { + for _, f := range ctf.SupportedFormats() { + if f == format { + ref.TypeHint += "+" + format.String() + } + } + } + ref.CreateIfMissing = true + target, err := session.DetermineRepositoryBySpec(ctx, &ref) + if err != nil { + if !errors.IsErrUnknown(err) || vfs.IsErrNotExist(err) || ref.Info == "" { + return nil, err + } + if ref.Type == "" { + ref.Type = format.String() + } + if ref.Type == "" { + return nil, fmt.Errorf("ctf format type required to create ctf") + } + target, err = ctf.Create(ctx, accessobj.ACC_CREATE, ref.Info, 0o770, accessio.PathFileSystem(utils.FileSystem(fs))) + if err != nil { + return nil, err + } + session.Closer(target) + } + return target, nil +} + +type AccessMethodSource = cpi.AccessMethodSource + +// ResourceReader gets a Reader for a given resource/source access. +// It provides a Reader handling the Close contract for the access method +// by connecting the access method's Close method to the Readers Close method . +// Deprecated: use ocmutils.GetResourceReader. +// It must be deprecated because of the support of free-floating ReSourceAccess +// implementations, they not necessarily provide an AccessMethod. +func ResourceReader(s AccessMethodSource) (io.ReadCloser, error) { + return cpi.ResourceReader(s) +} + +func IsIntermediate(spec RepositorySpec) bool { + if s, ok := spec.(IntermediateRepositorySpecAspect); ok { + return s.IsIntermediate() + } + return false +} + +func ComponentRefKey(ref *compdesc.ComponentReference) common.NameVersion { + return common.NewNameVersion(ref.GetComponentName(), ref.GetVersion()) +} + +func IsUnknownRepositorySpec(s RepositorySpec) bool { + return runtime.IsUnknown(s) +} + +func IsUnknownAccessSpec(s AccessSpec) bool { + return runtime.IsUnknown(s) +} + +func WrapContextProvider(ctx LocalContextProvider) ContextProvider { + return internal.WrapContextProvider(ctx) +} + +func ReferenceHint(spec AccessSpec, cv ComponentVersionAccess) string { + if h, ok := spec.(internal.HintProvider); ok { + return h.GetReferenceHint(cv) + } + return "" +} + +func GlobalAccess(spec AccessSpec, ctx Context) AccessSpec { + if h, ok := spec.(internal.GlobalAccessProvider); ok { + return h.GlobalAccessSpec(ctx) + } + return nil +} diff --git a/api/ocm/valuemergehandler/config/config_test.go b/api/ocm/valuemergehandler/config/config_test.go new file mode 100644 index 000000000..7ba084f81 --- /dev/null +++ b/api/ocm/valuemergehandler/config/config_test.go @@ -0,0 +1,64 @@ +package config_test + +import ( + "encoding/json" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/ocm" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/valuemergehandler" + "ocm.software/ocm/api/ocm/valuemergehandler/config" + "ocm.software/ocm/api/ocm/valuemergehandler/hpi" +) + +var _ = Describe("merge config", func() { + spec1 := Must(v1.NewMergeAlgorithmSpecification("test", "config1")) + spec2 := Must(v1.NewMergeAlgorithmSpecification("algo", "config2")) + + cfg := config.New() + cfg.Assign("test", spec1) + cfg.AssignLabel("l1", "v2", spec2) + + Context("serialize", func() { + It("serializes config", func() { + data := Must(json.Marshal(cfg)) + cfg2 := config.New() + MustBeSuccessful(json.Unmarshal(data, cfg2)) + Expect(cfg2).To(Equal(cfg)) + }) + }) + + Context("apply", func() { + It("applies directly", func() { + reg := hpi.NewRegistry() + + Expect(cfg.ApplyTo(nil, reg)).To(Succeed()) + + found := reg.GetAssignments() + expected := map[hpi.Hint]*hpi.Specification{ + "test": spec1, + "label:l1@v2": spec2, + } + + Expect(found).To(DeepEqual(expected)) + }) + + It("applies via config context", func() { + ctx := ocm.New(datacontext.MODE_INITIAL) + + Expect(ctx.ConfigContext().ApplyConfig(cfg, "programmatic")).To(Succeed()) + + Expect(valuemergehandler.For(ctx).GetHandlers()).To(Equal(valuemergehandler.Handlers{})) + found := valuemergehandler.For(ctx).GetAssignments() + expected := map[hpi.Hint]*hpi.Specification{ + "test": spec1, + "label:l1@v2": spec2, + } + Expect(found).To(DeepEqual(expected)) + }) + }) +}) diff --git a/pkg/contexts/ocm/valuemergehandler/config/suite_test.go b/api/ocm/valuemergehandler/config/suite_test.go similarity index 100% rename from pkg/contexts/ocm/valuemergehandler/config/suite_test.go rename to api/ocm/valuemergehandler/config/suite_test.go diff --git a/api/ocm/valuemergehandler/config/type.go b/api/ocm/valuemergehandler/config/type.go new file mode 100644 index 000000000..093f7a2da --- /dev/null +++ b/api/ocm/valuemergehandler/config/type.go @@ -0,0 +1,116 @@ +package config + +import ( + "ocm.software/ocm/api/config" + cfgcpi "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/ocm/valuemergehandler/hpi" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ConfigType = "merge" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX + ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" +) + +func init() { + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1)) +} + +// Config describes a memory based config interface. +type Config struct { + runtime.ObjectVersionedType `json:",inline"` + Labels []LabelAssignment + Assignments map[hpi.Hint]*hpi.Specification `json:"assignments,omitempty"` +} + +type LabelAssignment struct { + Name string `json:"name"` + Version string `json:"version,omitempty"` + Merge hpi.Specification `json:"merge,omitempty"` +} + +// New creates a new memory ConfigSpec. +func New() *Config { + return &Config{ + ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), + Assignments: map[hpi.Hint]*hpi.Specification{}, + } +} + +func (a *Config) GetType() string { + return ConfigType +} + +func (a *Config) Assign(name hpi.Hint, spec *hpi.Specification) { + if a.Assignments == nil { + a.Assignments = map[hpi.Hint]*hpi.Specification{} + } + if spec == nil { + delete(a.Assignments, name) + } else { + a.Assignments[name] = spec + } +} + +func (a *Config) AssignLabel(name string, version string, spec *hpi.Specification) { + if spec == nil { + for i, s := range a.Labels { + if s.Name == name && s.Version == version { + a.Labels = append(a.Labels[:i], a.Labels[i+1:]...) + return + } + } + } else { + a.Labels = append(a.Labels, LabelAssignment{ + Name: name, + Version: version, + Merge: *spec, + }) + } +} + +func (a *Config) ApplyTo(ctx config.Context, target interface{}) error { + var reg hpi.Registry + + t, ok := target.(hpi.Context) + if !ok { + reg, ok = target.(hpi.Registry) + if !ok { + return config.ErrNoContext(ConfigType) + } + } else { + reg = hpi.For(t) + } + + for n, s := range a.Assignments { + reg.AssignHandler(n, s) + } + for _, s := range a.Labels { + if s.Name == "" { + continue + } + reg.AssignHandler(hpi.LabelHint(s.Name, s.Version), &s.Merge) + } + return nil +} + +const usage = ` +The config type ` + ConfigType + ` can be used to set some +assignments for the merging of (label) values. It applies to a value +merge handler registry, either directly or via an OCM context. + +
+    type: ` + ConfigType + `
+    labels:
+    - name: acme.org/audit/level
+      merge:
+        algorithm: acme.org/audit
+        config: ...
+    assignments:
+       label:acme.org/audit/level@v1: 
+          algorithm: acme.org/audit
+          config: ...
+          ...
+
+` diff --git a/api/ocm/valuemergehandler/handlers/defaultmerge/config.go b/api/ocm/valuemergehandler/handlers/defaultmerge/config.go new file mode 100644 index 000000000..2fc076db1 --- /dev/null +++ b/api/ocm/valuemergehandler/handlers/defaultmerge/config.go @@ -0,0 +1,42 @@ +package defaultmerge + +import ( + // special case to resolve dependency cycles. + "github.com/mandelsoft/goutils/errors" + + hpi "ocm.software/ocm/api/ocm/valuemergehandler/internal" +) + +type Mode string + +func (m Mode) String() string { + return string(m) +} + +const ( + MODE_DEFAULT = Mode("") + MODE_NONE = Mode("none") + MODE_LOCAL = Mode("local") + MODE_INBOUND = Mode("inbound") +) + +func NewConfig(overwrite Mode) *Config { + return &Config{ + Overwrite: overwrite, + } +} + +type Config struct { + Overwrite Mode `json:"overwrite"` +} + +func (c Config) Complete(ctx hpi.Context) error { + switch c.Overwrite { + case MODE_NONE, MODE_LOCAL, MODE_INBOUND: + case "": + // leave choice to using algorithm + default: + return errors.ErrInvalid("merge overwrite mode", string(c.Overwrite)) + } + return nil +} diff --git a/api/ocm/valuemergehandler/handlers/defaultmerge/handler.go b/api/ocm/valuemergehandler/handlers/defaultmerge/handler.go new file mode 100644 index 000000000..0b256ae25 --- /dev/null +++ b/api/ocm/valuemergehandler/handlers/defaultmerge/handler.go @@ -0,0 +1,49 @@ +package defaultmerge + +import ( + "fmt" + "reflect" + + // special case to resolve dependency cycles. + hpi "ocm.software/ocm/api/ocm/valuemergehandler/internal" +) + +const ALGORITHM = "default" + +func init() { + hpi.Register(New()) +} + +// Value is the minimal structure of values usable with the merge algorithm. +type Value interface{} + +func New() hpi.Handler { + return hpi.New(ALGORITHM, desc, merge) +} + +var desc = ` +This handler merges arbitrary label values by deciding for +one or none side. + +It supports the following config structure: +- *overwrite* *string* (optional) determines how to handle conflicts. + + - none no change possible, if entry differs the merge is rejected. + - local the local value is preserved. + - inbound (default) the inbound value overwrites the local one. +` + +func merge(ctx hpi.Context, c *Config, lv Value, tv *Value) (bool, error) { + modified := false + if !reflect.DeepEqual(lv, tv) { + switch c.Overwrite { + // default = INBOUND: keep precalculated tarted = inbound CD + case MODE_LOCAL: + *tv = lv + modified = true + case MODE_NONE: + return false, fmt.Errorf("target value changed") + } + } + return modified, nil +} diff --git a/api/ocm/valuemergehandler/handlers/defaultmerge/handler_test.go b/api/ocm/valuemergehandler/handlers/defaultmerge/handler_test.go new file mode 100644 index 000000000..cd62025a6 --- /dev/null +++ b/api/ocm/valuemergehandler/handlers/defaultmerge/handler_test.go @@ -0,0 +1,48 @@ +package defaultmerge + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/utils/runtime" +) + +var _ = Describe("list merge", func() { + handler := New() + + var e1, e2 Value + var a, b runtime.RawValue + + BeforeEach(func() { + e1 = "v1" + e2 = "v2" + + MustBeSuccessful(a.SetValue(e1)) + MustBeSuccessful(b.SetValue(e1)) + }) + + It("merges no change", func() { + MustBeSuccessful(handler.Merge(nil, a, &b, nil)) + Expect(b).To(Equal(a)) + }) + + It("modified keeps local", func() { + MustBeSuccessful(a.SetValue(e2)) + MustBeSuccessful(handler.Merge(nil, a, &b, NewConfig(MODE_LOCAL))) + + Expect(b).To(DeepEqual(a)) + }) + + It("modified accept inbound", func() { + MustBeSuccessful(b.SetValue(e2)) + r := b.Copy() + MustBeSuccessful(handler.Merge(nil, a, &b, nil)) + Expect(b).To(Equal(r)) + }) + + It("fails for none mode", func() { + MustBeSuccessful(b.SetValue(e2)) + MustFailWithMessage(ErrorFrom(handler.Merge(nil, a, &b, NewConfig(MODE_NONE))), "[default]: target value changed") + }) +}) diff --git a/pkg/contexts/ocm/valuemergehandler/handlers/defaultmerge/suite_test.go b/api/ocm/valuemergehandler/handlers/defaultmerge/suite_test.go similarity index 100% rename from pkg/contexts/ocm/valuemergehandler/handlers/defaultmerge/suite_test.go rename to api/ocm/valuemergehandler/handlers/defaultmerge/suite_test.go diff --git a/api/ocm/valuemergehandler/handlers/init.go b/api/ocm/valuemergehandler/handlers/init.go new file mode 100644 index 000000000..2f9370544 --- /dev/null +++ b/api/ocm/valuemergehandler/handlers/init.go @@ -0,0 +1,8 @@ +package handlers + +import ( + _ "ocm.software/ocm/api/ocm/valuemergehandler/handlers/defaultmerge" + _ "ocm.software/ocm/api/ocm/valuemergehandler/handlers/maplistmerge" + _ "ocm.software/ocm/api/ocm/valuemergehandler/handlers/simplelistmerge" + _ "ocm.software/ocm/api/ocm/valuemergehandler/handlers/simplemapmerge" +) diff --git a/api/ocm/valuemergehandler/handlers/maplistmerge/config.go b/api/ocm/valuemergehandler/handlers/maplistmerge/config.go new file mode 100644 index 000000000..a58f9922f --- /dev/null +++ b/api/ocm/valuemergehandler/handlers/maplistmerge/config.go @@ -0,0 +1,41 @@ +package maplistmerge + +import ( + "ocm.software/ocm/api/ocm/valuemergehandler/handlers/defaultmerge" + "ocm.software/ocm/api/ocm/valuemergehandler/hpi" + "ocm.software/ocm/api/utils" +) + +type Mode = defaultmerge.Mode + +const ( + MODE_DEFAULT = defaultmerge.MODE_DEFAULT + MODE_NONE = defaultmerge.MODE_NONE + MODE_LOCAL = defaultmerge.MODE_LOCAL + MODE_INBOUND = defaultmerge.MODE_INBOUND +) + +func NewConfig(field string, overwrite Mode, entries ...*hpi.Specification) *Config { + return &Config{ + KeyField: field, + Config: *defaultmerge.NewConfig(overwrite), + Entries: utils.Optional(entries...), + } +} + +type Config struct { + defaultmerge.Config + KeyField string `json:"keyField"` + Entries *hpi.Specification `json:"entries,omitempty"` +} + +func (c *Config) Complete(ctx hpi.Context) error { + err := c.Config.Complete(ctx) + if err != nil { + return err + } + if c.KeyField == "" { + c.KeyField = "name" + } + return nil +} diff --git a/api/ocm/valuemergehandler/handlers/maplistmerge/handler.go b/api/ocm/valuemergehandler/handlers/maplistmerge/handler.go new file mode 100644 index 000000000..f2d292d3d --- /dev/null +++ b/api/ocm/valuemergehandler/handlers/maplistmerge/handler.go @@ -0,0 +1,94 @@ +package maplistmerge + +import ( + "fmt" + "reflect" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/valuemergehandler/hpi" +) + +const ALGORITHM = "mapListMerge" + +func init() { + hpi.Register(New()) +} + +type ( + // Value is the minimal structure of values usable with the merge algorithm. + Value = []Entry + Entry = map[string]interface{} +) + +func New() hpi.Handler { + return hpi.New(ALGORITHM, desc, merge) +} + +var desc = ` +This handler merges values with a list of map values by observing a key field +to identify similar map entries. +The default entry key is taken from map field name. + +It supports the following config structure: +- *keyField* *string* (optional) + + the key field to identify entries in the maps. + +- *overwrite* *string* (optional) determines how to handle conflicts. + + - none (default) no change possible, if entry differs the merge is rejected. + - local the local value is preserved. + - inbound the inbound value overwrites the local one. + +- *entries *merge spec* (optional) + + The merge specification (algorithm and config) used to merge conflicting + changes in list entries. +` + +func merge(ctx cpi.Context, c *Config, lv Value, tv *Value) (bool, error) { + var err error + + subm := false + modified := false + for _, le := range lv { + key := le[c.KeyField] + if key != nil { + found := -1 + for i, te := range *tv { + if te[c.KeyField] == key { + found = i + if !reflect.DeepEqual(le, te) { + switch c.Overwrite { + case MODE_DEFAULT: + if c.Entries != nil { + subm, te, err = hpi.GenericMerge(ctx, c.Entries, "", le, te) + if err != nil { + return false, errors.Wrapf(err, "entry identity %q", key) + } + if subm { + (*tv)[i] = te + modified = true + } + break + } + fallthrough + case MODE_NONE: + return false, fmt.Errorf("target value for %q changed", key) + case MODE_LOCAL: + (*tv)[i] = le + modified = true + } + } + } + } + if found < 0 { + *tv = append(*tv, le) + modified = true + } + } + } + return modified, nil +} diff --git a/api/ocm/valuemergehandler/handlers/maplistmerge/handler_test.go b/api/ocm/valuemergehandler/handlers/maplistmerge/handler_test.go new file mode 100644 index 000000000..ccf22c1ed --- /dev/null +++ b/api/ocm/valuemergehandler/handlers/maplistmerge/handler_test.go @@ -0,0 +1,195 @@ +package maplistmerge_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/ocm" + me "ocm.software/ocm/api/ocm/valuemergehandler/handlers/maplistmerge" + "ocm.software/ocm/api/ocm/valuemergehandler/handlers/simplemapmerge" + "ocm.software/ocm/api/ocm/valuemergehandler/hpi" +) + +type ( + Value = me.Value + VEntry = simplemapmerge.Value + Config = me.Config +) + +const ( + ALGORITHM = me.ALGORITHM + MODE_NONE = me.MODE_NONE + MODE_LOCAL = me.MODE_LOCAL + MODE_INBOUND = me.MODE_INBOUND +) + +var ( + NewConfig = me.NewConfig + New = me.New +) + +var _ = Describe("list merge", func() { + handler := New() + + var e1, e2, e3, e4 map[string]interface{} + var va, vn Value + var a, b hpi.Value + + BeforeEach(func() { + e1 = VEntry{ + "name": "name1", + "data": "entry1", + } + e2 = VEntry{ + "name": "name2", + "data": "entry2", + } + e3 = VEntry{ + "name": "name3", + "data": "entry3", + } + e4 = VEntry{ + "name": "name4", + "data": "entry4", + } + + va = Value{e1, e2} + vn = Value{e1, e2} + + MustBeSuccessful(a.SetValue(va)) + b = a.Copy() + }) + + It("merges no change", func() { + MustBeSuccessful(handler.Merge(nil, a, &b, nil)) + Expect(b).To(Equal(a)) + }) + + It("adds new entry", func() { + MustBeSuccessful(a.SetValue(append(va, e3))) + MustBeSuccessful(handler.Merge(nil, a, &b, nil)) + Expect(b).To(Equal(a)) + }) + + It("adds new entry on both sides", func() { + MustBeSuccessful(a.SetValue(append(vn, e4))) + MustBeSuccessful(b.SetValue(append(vn, e3))) + + MustBeSuccessful(handler.Merge(nil, a, &b, nil)) + + var r Value + MustBeSuccessful(b.GetValue(&r)) + + Expect(r).To(DeepEqual(append(vn, e3, e4))) + }) + + It("updates to inbound", func() { + vn[0]["data"] = "X" + MustBeSuccessful(b.SetValue(vn)) + r := b.Copy() + MustBeSuccessful(handler.Merge(nil, a, &b, NewConfig("name", MODE_INBOUND))) + + Expect(b).To(DeepEqual(r)) + }) + + It("keeps local", func() { + vn[0]["data"] = "X" + MustBeSuccessful(b.SetValue(vn)) + MustBeSuccessful(handler.Merge(nil, a, &b, NewConfig("name", MODE_LOCAL))) + + Expect(b).To(DeepEqual(a)) + }) + + It("fails for none mode", func() { + vn[0]["data"] = "X" + MustBeSuccessful(b.SetValue(vn)) + MustFailWithMessage(ErrorFrom(handler.Merge(nil, a, &b, NewConfig("name", MODE_NONE))), "[mapListMerge]: target value for \"name1\" changed") + }) + + It("fails for wrong type", func() { + MustBeSuccessful(b.SetValue(true)) + MustFailWithMessage(ErrorFrom(handler.Merge(nil, a, &b, nil)), "[mapListMerge] inbound value is not valid: json: cannot unmarshal bool into Go value of type []map[string]interface {}") + MustFailWithMessage(ErrorFrom(handler.Merge(nil, b, &a, nil)), "[mapListMerge] local value is not valid: json: cannot unmarshal bool into Go value of type []map[string]interface {}") + }) + + Context("cascading", func() { + var d1, d2 Value + var cfg *Config + var keycfg *Config + var m1, m2 simplemapmerge.Value + + BeforeEach(func() { + m1 = simplemapmerge.Value{ + "key": "name1", + "value": "value1", + } + m2 = simplemapmerge.Value{ + "key": "name2", + "value": "value3", + } + d1 = Value{ + m1, m2, + } + + MustBeSuccessful(a.SetValue(d1)) + b = a.Copy() + MustBeSuccessful(b.GetValue(&d2)) + cfg = NewConfig("key", "", Must(hpi.NewSpecification(simplemapmerge.ALGORITHM, simplemapmerge.NewConfig(simplemapmerge.MODE_INBOUND)))) + keycfg = NewConfig("key", "") + }) + + It("handles equal", func() { + MustBeSuccessful(handler.Merge(ocm.DefaultContext(), a, &b, cfg)) + + Expect(b).To(DeepEqual(a)) + }) + + It("handles merge", func() { + d1[0]["local"] = "local" + d2[0]["inbound"] = "inbound" + + MustBeSuccessful(a.SetValue(d1)) + MustBeSuccessful(b.SetValue(d2)) + + d2[0]["local"] = "local" + + MustFailWithMessage(ErrorFrom(handler.Merge(nil, a, &b, keycfg)), "[mapListMerge]: target value for \"name1\" changed") + MustBeSuccessful(handler.Merge(ocm.DefaultContext(), a, &b, cfg)) + + var r Value + MustBeSuccessful(b.GetValue(&r)) + Expect(r).To(DeepEqual(d2)) + }) + + It("resolves to inbound", func() { + d2[0]["data"] = "inbound" + + MustBeSuccessful(a.SetValue(d1)) + MustBeSuccessful(b.SetValue(d2)) + + MustFailWithMessage(ErrorFrom(handler.Merge(nil, a, &b, keycfg)), "[mapListMerge]: target value for \"name1\" changed") + MustBeSuccessful(handler.Merge(ocm.DefaultContext(), a, &b, cfg)) + + var r Value + MustBeSuccessful(b.GetValue(&r)) + Expect(r).To(DeepEqual(d2)) + }) + + It("resolves to local", func() { + cfg = NewConfig("key", "", Must(hpi.NewSpecification(simplemapmerge.ALGORITHM, simplemapmerge.NewConfig(MODE_LOCAL)))) + + d1[0]["data"] = "local" + + MustBeSuccessful(a.SetValue(d1)) + MustBeSuccessful(b.SetValue(d2)) + + MustFailWithMessage(ErrorFrom(handler.Merge(nil, a, &b, keycfg)), "[mapListMerge]: target value for \"name1\" changed") + MustBeSuccessful(handler.Merge(ocm.DefaultContext(), a, &b, cfg)) + + var r Value + MustBeSuccessful(b.GetValue(&r)) + Expect(r).To(DeepEqual(d1)) + }) + }) +}) diff --git a/pkg/contexts/ocm/valuemergehandler/handlers/maplistmerge/suite_test.go b/api/ocm/valuemergehandler/handlers/maplistmerge/suite_test.go similarity index 100% rename from pkg/contexts/ocm/valuemergehandler/handlers/maplistmerge/suite_test.go rename to api/ocm/valuemergehandler/handlers/maplistmerge/suite_test.go diff --git a/api/ocm/valuemergehandler/handlers/plugin/config.go b/api/ocm/valuemergehandler/handlers/plugin/config.go new file mode 100644 index 000000000..6855974df --- /dev/null +++ b/api/ocm/valuemergehandler/handlers/plugin/config.go @@ -0,0 +1,17 @@ +package plugin + +import ( + "encoding/json" + + "ocm.software/ocm/api/ocm/valuemergehandler" +) + +type Config struct { + json.RawMessage +} + +var _ valuemergehandler.Config = (*Config)(nil) + +func (c Config) Complete(valuemergehandler.Context) error { + return nil +} diff --git a/api/ocm/valuemergehandler/handlers/plugin/handler.go b/api/ocm/valuemergehandler/handlers/plugin/handler.go new file mode 100644 index 000000000..f0b0ebefa --- /dev/null +++ b/api/ocm/valuemergehandler/handlers/plugin/handler.go @@ -0,0 +1,61 @@ +package plugin + +import ( + "encoding/json" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/ocm/plugin" + "ocm.software/ocm/api/ocm/plugin/descriptor" + "ocm.software/ocm/api/ocm/valuemergehandler/hpi" +) + +// pluginHandler delegates action to a plugin based handler. +type pluginHandler struct { + plugin plugin.Plugin + descriptor *descriptor.ValueMergeHandlerDescriptor +} + +func New(p plugin.Plugin, name string) (hpi.Handler, error) { + md := p.GetValueMergeHandlerDescriptor(name) + if md == nil { + return nil, errors.ErrUnknown(hpi.KIND_VALUE_MERGE_ALGORITHM, name, plugin.KIND_PLUGIN, p.Name()) + } + + return &pluginHandler{ + plugin: p, + descriptor: md, + }, nil +} + +func (b *pluginHandler) Algorithm() string { + return b.descriptor.Name +} + +func (b *pluginHandler) Description() string { + return b.descriptor.Description +} + +func (b *pluginHandler) DecodeConfig(data []byte) (hpi.Config, error) { + var cfg Config + err := json.Unmarshal(data, &cfg) + if err != nil { + return nil, err + } + return &cfg, nil +} + +func (b *pluginHandler) Merge(_ hpi.Context, src hpi.Value, tgt *hpi.Value, cfg hpi.Config) (bool, error) { + spec, err := hpi.NewSpecification(b.descriptor.Name, cfg) + if err != nil { + return false, err + } + mod, r, err := b.plugin.MergeValue(spec, src, *tgt) + if err != nil { + return false, err + } + if mod { + tgt.RawMessage = r.RawMessage + } + return mod, nil +} diff --git a/api/ocm/valuemergehandler/handlers/plugin/handler_test.go b/api/ocm/valuemergehandler/handlers/plugin/handler_test.go new file mode 100644 index 000000000..5353ef2df --- /dev/null +++ b/api/ocm/valuemergehandler/handlers/plugin/handler_test.go @@ -0,0 +1,67 @@ +//go:build unix + +package plugin_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + . "ocm.software/ocm/api/ocm/plugin/testutils" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/plugin/registration" + "ocm.software/ocm/api/ocm/valuemergehandler" + "ocm.software/ocm/api/ocm/valuemergehandler/handlers/defaultmerge" + "ocm.software/ocm/api/ocm/valuemergehandler/hpi" +) + +const ( + PLUGIN = "merge" + ALGORITHM = "acme.org/test" +) + +var _ = Describe("plugin value merge handler", func() { + var ctx ocm.Context + var env *Builder + var plugins TempPluginDir + var registry valuemergehandler.Registry + + BeforeEach(func() { + env = NewBuilder(nil) + ctx = env.OCMContext() + plugins = Must(ConfigureTestPlugins(ctx, "testdata")) + registry = valuemergehandler.For(ctx) + }) + + AfterEach(func() { + plugins.Cleanup() + env.Cleanup() + }) + + It("executes handler", func() { + registration.RegisterExtensions(ctx) + + Expect(registry.GetHandler(ALGORITHM)).NotTo(BeNil()) + + spec := Must(valuemergehandler.NewSpecification(ALGORITHM, defaultmerge.NewConfig("test"))) + var local, inbound valuemergehandler.Value + + local.SetValue("local") + inbound.SetValue("inbound") + mod := Must(valuemergehandler.Merge(ctx, spec, "", local, &inbound)) + + Expect(mod).To(BeTrue()) + Expect(inbound.RawMessage).To(YAMLEqual(`{"mode":"resolved"}`)) + }) + + It("assigns specs", func() { + registration.RegisterExtensions(ctx) + + Expect(registry.GetHandler(ALGORITHM)).NotTo(BeNil()) + + s := registry.GetAssignment(hpi.LabelHint("testlabel", "v2")) + Expect(s).NotTo(BeNil()) + Expect(s.Algorithm).To(Equal("simpleMapMerge")) + }) +}) diff --git a/pkg/contexts/ocm/valuemergehandler/handlers/plugin/suite_test.go b/api/ocm/valuemergehandler/handlers/plugin/suite_test.go similarity index 100% rename from pkg/contexts/ocm/valuemergehandler/handlers/plugin/suite_test.go rename to api/ocm/valuemergehandler/handlers/plugin/suite_test.go diff --git a/pkg/contexts/ocm/valuemergehandler/handlers/plugin/testdata/merge b/api/ocm/valuemergehandler/handlers/plugin/testdata/merge similarity index 100% rename from pkg/contexts/ocm/valuemergehandler/handlers/plugin/testdata/merge rename to api/ocm/valuemergehandler/handlers/plugin/testdata/merge diff --git a/api/ocm/valuemergehandler/handlers/simplelistmerge/config.go b/api/ocm/valuemergehandler/handlers/simplelistmerge/config.go new file mode 100644 index 000000000..8aa13aae7 --- /dev/null +++ b/api/ocm/valuemergehandler/handlers/simplelistmerge/config.go @@ -0,0 +1,19 @@ +package simplelistmerge + +import ( + "ocm.software/ocm/api/ocm/valuemergehandler/hpi" +) + +func NewConfig(fields ...string) *Config { + return &Config{IgnoredFields: fields} +} + +type Config struct { + IgnoredFields []string `json:"ignoredFields,omitempty"` +} + +var _ hpi.Config = (*Config)(nil) + +func (c *Config) Complete(ctx hpi.Context) error { + return nil +} diff --git a/api/ocm/valuemergehandler/handlers/simplelistmerge/handler.go b/api/ocm/valuemergehandler/handlers/simplelistmerge/handler.go new file mode 100644 index 000000000..65df12c30 --- /dev/null +++ b/api/ocm/valuemergehandler/handlers/simplelistmerge/handler.go @@ -0,0 +1,63 @@ +package simplelistmerge + +import ( + "reflect" + + "ocm.software/ocm/api/ocm/valuemergehandler/hpi" +) + +const ALGORITHM = "simpleListMerge" + +func init() { + hpi.Register(New()) +} + +type ( + // Value is the minimal structure of values usable with the merge algorithm. + Value = []Entry + Entry = interface{} +) + +func New() hpi.Handler { + return hpi.New(ALGORITHM, desc, merge) +} + +var desc = ` +This handler merges simple list labels values. + +It supports the following config structure: +- *overwrite* *string* (optional) determines how to handle conflicts. + +` + +func merge(ctx hpi.Context, c *Config, lv Value, tv *Value) (bool, error) { + modified := false +outer: + for _, le := range lv { + for _, te := range *tv { + if equal(c, le, te) { + continue outer + } + } + *tv = append(*tv, le) + modified = true + } + return modified, nil +} + +func equal(c *Config, le, te Entry) bool { + if c == nil || len(c.IgnoredFields) == 0 { + return reflect.DeepEqual(le, te) + } + + if lm, ok := le.(map[string]interface{}); ok { + if tm, ok := te.(map[string]interface{}); ok { + for _, n := range c.IgnoredFields { + delete(lm, n) + delete(tm, n) + } + return reflect.DeepEqual(lm, tm) + } + } + return reflect.DeepEqual(le, te) +} diff --git a/api/ocm/valuemergehandler/handlers/simplelistmerge/handler_test.go b/api/ocm/valuemergehandler/handlers/simplelistmerge/handler_test.go new file mode 100644 index 000000000..ca5bde030 --- /dev/null +++ b/api/ocm/valuemergehandler/handlers/simplelistmerge/handler_test.go @@ -0,0 +1,74 @@ +package simplelistmerge_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + me "ocm.software/ocm/api/ocm/valuemergehandler/handlers/simplelistmerge" + "ocm.software/ocm/api/ocm/valuemergehandler/hpi" +) + +type ( + Value = me.Value + Config = me.Config +) + +var ( + NewConfig = me.NewConfig + New = me.New +) + +var _ = Describe("list merge", func() { + handler := New() + + var e1, e2 Value + var a, b hpi.Value + + BeforeEach(func() { + e1 = []interface{}{ + "name1", + "entry1", + } + e2 = []interface{}{ + "name1", + "entry1", + } + + MustBeSuccessful(a.SetValue(e1)) + b = a.Copy() + }) + + It("merges no change", func() { + MustBeSuccessful(handler.Merge(nil, a, &b, nil)) + Expect(b).To(Equal(a)) + }) + + It("adds new entry", func() { + e1 = append(e1, "local") + MustBeSuccessful(a.SetValue(e1)) + MustBeSuccessful(handler.Merge(nil, a, &b, nil)) + Expect(b).To(Equal(a)) + }) + + It("adds new entry on both sides", func() { + e1 = append(e1, "local") + e2 = append(e2, "inbound") + MustBeSuccessful(a.SetValue(e1)) + MustBeSuccessful(b.SetValue(e2)) + + MustBeSuccessful(handler.Merge(nil, a, &b, nil)) + + var r Value + MustBeSuccessful(b.GetValue(&r)) + + e2 = append(e2, "local") + Expect(r).To(DeepEqual(e2)) + }) + + It("fails for wrong type", func() { + MustBeSuccessful(b.SetValue(true)) + MustFailWithMessage(ErrorFrom(handler.Merge(nil, a, &b, nil)), "[simpleListMerge] inbound value is not valid: json: cannot unmarshal bool into Go value of type []interface {}") + MustFailWithMessage(ErrorFrom(handler.Merge(nil, b, &a, nil)), "[simpleListMerge] local value is not valid: json: cannot unmarshal bool into Go value of type []interface {}") + }) +}) diff --git a/pkg/contexts/ocm/valuemergehandler/handlers/simplelistmerge/suite_test.go b/api/ocm/valuemergehandler/handlers/simplelistmerge/suite_test.go similarity index 100% rename from pkg/contexts/ocm/valuemergehandler/handlers/simplelistmerge/suite_test.go rename to api/ocm/valuemergehandler/handlers/simplelistmerge/suite_test.go diff --git a/api/ocm/valuemergehandler/handlers/simplemapmerge/config.go b/api/ocm/valuemergehandler/handlers/simplemapmerge/config.go new file mode 100644 index 000000000..111724c7e --- /dev/null +++ b/api/ocm/valuemergehandler/handlers/simplemapmerge/config.go @@ -0,0 +1,28 @@ +package simplemapmerge + +import ( + "ocm.software/ocm/api/ocm/valuemergehandler/handlers/defaultmerge" + "ocm.software/ocm/api/ocm/valuemergehandler/hpi" + "ocm.software/ocm/api/utils" +) + +type Mode = defaultmerge.Mode + +const ( + MODE_DEFAULT = defaultmerge.MODE_DEFAULT + MODE_NONE = defaultmerge.MODE_NONE + MODE_LOCAL = defaultmerge.MODE_LOCAL + MODE_INBOUND = defaultmerge.MODE_INBOUND +) + +func NewConfig(overwrite Mode, entries ...*hpi.Specification) *Config { + return &Config{ + Config: *defaultmerge.NewConfig(overwrite), + Entries: utils.Optional(entries...), + } +} + +type Config struct { + defaultmerge.Config + Entries *hpi.Specification `json:"entries,omitempty"` +} diff --git a/api/ocm/valuemergehandler/handlers/simplemapmerge/handler.go b/api/ocm/valuemergehandler/handlers/simplemapmerge/handler.go new file mode 100644 index 000000000..aea03d6d7 --- /dev/null +++ b/api/ocm/valuemergehandler/handlers/simplemapmerge/handler.go @@ -0,0 +1,89 @@ +package simplemapmerge + +import ( + "fmt" + "reflect" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/ocm/valuemergehandler/hpi" +) + +const ALGORITHM = "simpleMapMerge" + +func init() { + hpi.Register(New()) +} + +type ( + // Value is the minimal structure of values usable with the merge algorithm. + Value = map[string]Entry + Entry = interface{} +) + +func New() hpi.Handler { + return hpi.New(ALGORITHM, desc, merge) +} + +var desc = ` +This handler merges simple map labels values. + +It supports the following config structure: +- *overwrite* *string* (optional) determines how to handle conflicts. + + - none (default) no change possible, if entry differs the merge is rejected. + - local the local value is preserved. + - inbound the inbound value overwrites the local one. + +- *entries *merge spec* (optional) + + The merge specification (algorithm and config) used to merge conflicting + changes in map entries. +` + +func merge(ctx hpi.Context, c *Config, lv Value, tv *Value) (bool, error) { + var err error + + subm := false + modified := false + for lk, le := range lv { + if te, ok := (*tv)[lk]; ok { + if !reflect.DeepEqual(le, te) { + switch c.Overwrite { + case MODE_DEFAULT: + if c.Entries != nil { + hpi.Log.Trace("different entry found in target -> merge it", "name", lk, "entries", c.Entries) + subm, te, err = hpi.GenericMerge(ctx, c.Entries, "", le, te) + if err != nil { + return false, errors.Wrapf(err, "map key %q", lk) + } + if subm { + (*tv)[lk] = te + modified = true + hpi.Log.Trace("entry merge result", "result", (*tv)) + } else { + hpi.Log.Trace("not modified") + } + break + } + fallthrough + case MODE_NONE: + hpi.Log.Trace("different entry found in target -> fail", "name", lk) + return false, fmt.Errorf("target value for %q changed", lk) + case MODE_LOCAL: + (*tv)[lk] = le + hpi.Log.Trace("different entry found in target -> use local", "name", lk, "result", (*tv)) + modified = true + } + } else { + hpi.Log.Trace("entry found in target", "name", lk) + } + } else { + (*tv)[lk] = le + hpi.Log.Trace("entry not found in target -> append it", "name", lk, "result", (*tv)) + modified = true + } + } + hpi.Log.Trace("merge result", "modified", modified, "result", (*tv)) + return modified, nil +} diff --git a/api/ocm/valuemergehandler/handlers/simplemapmerge/handler_test.go b/api/ocm/valuemergehandler/handlers/simplemapmerge/handler_test.go new file mode 100644 index 000000000..e74151594 --- /dev/null +++ b/api/ocm/valuemergehandler/handlers/simplemapmerge/handler_test.go @@ -0,0 +1,175 @@ +package simplemapmerge_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/ocm" + me "ocm.software/ocm/api/ocm/valuemergehandler/handlers/simplemapmerge" + "ocm.software/ocm/api/ocm/valuemergehandler/hpi" +) + +type ( + Value = me.Value + Config = me.Config +) + +const ( + ALGORITHM = me.ALGORITHM + MODE_NONE = me.MODE_NONE + MODE_LOCAL = me.MODE_LOCAL + MODE_INBOUND = me.MODE_INBOUND +) + +var ( + NewConfig = me.NewConfig + New = me.New +) + +var _ = Describe("list merge", func() { + handler := New() + + var e1, e2 Value + var a, b hpi.Value + + BeforeEach(func() { + e1 = map[string]interface{}{ + "name": "name1", + "data": "entry1", + } + e2 = map[string]interface{}{ + "name": "name1", + "data": "entry1", + } + + MustBeSuccessful(a.SetValue(e1)) + b = a.Copy() + }) + + It("merges no change", func() { + MustBeSuccessful(handler.Merge(nil, a, &b, nil)) + Expect(b).To(Equal(a)) + }) + + It("adds new entry", func() { + e1["local"] = "local" + MustBeSuccessful(a.SetValue(e1)) + MustBeSuccessful(handler.Merge(nil, a, &b, nil)) + Expect(b).To(Equal(a)) + }) + + It("adds new entry on both sides", func() { + e1["local"] = "local" + e2["inbound"] = "inbound" + MustBeSuccessful(a.SetValue(e1)) + MustBeSuccessful(b.SetValue(e2)) + + MustBeSuccessful(handler.Merge(nil, a, &b, nil)) + + var r Value + MustBeSuccessful(b.GetValue(&r)) + + e2["local"] = "local" + Expect(r).To(DeepEqual(e2)) + }) + + It("updates to inbound", func() { + e2["name"] = "inbound" + MustBeSuccessful(b.SetValue(e2)) + r := b.Copy() + MustBeSuccessful(handler.Merge(nil, a, &b, NewConfig(MODE_INBOUND, nil))) + + Expect(b).To(DeepEqual(r)) + }) + + It("keeps local", func() { + e2["name"] = "inbound" + MustBeSuccessful(b.SetValue(e1)) + MustBeSuccessful(handler.Merge(nil, a, &b, NewConfig(MODE_LOCAL, nil))) + + Expect(b).To(DeepEqual(a)) + }) + + It("fails for none mode", func() { + e2["data"] = "X" + MustBeSuccessful(b.SetValue(e2)) + MustFailWithMessage(ErrorFrom(handler.Merge(nil, a, &b, NewConfig(MODE_NONE, nil))), "[simpleMapMerge]: target value for \"data\" changed") + }) + + It("fails for wrong type", func() { + MustBeSuccessful(b.SetValue(true)) + MustFailWithMessage(ErrorFrom(handler.Merge(nil, a, &b, nil)), "[simpleMapMerge] inbound value is not valid: json: cannot unmarshal bool into Go value of type map[string]interface {}") + MustFailWithMessage(ErrorFrom(handler.Merge(nil, b, &a, nil)), "[simpleMapMerge] local value is not valid: json: cannot unmarshal bool into Go value of type map[string]interface {}") + }) + + Context("cascading", func() { + var d1, d2 Value + var cfg *Config + + BeforeEach(func() { + d1 = Value{ + "k1": e1, + "k2": e2, + } + + MustBeSuccessful(a.SetValue(d1)) + b = a.Copy() + MustBeSuccessful(b.GetValue(&d2)) + cfg = NewConfig("", Must(hpi.NewSpecification(ALGORITHM, NewConfig(MODE_INBOUND)))) + }) + + It("handles equal", func() { + MustBeSuccessful(handler.Merge(ocm.DefaultContext(), a, &b, cfg)) + + Expect(b).To(DeepEqual(a)) + }) + + It("handles merge", func() { + d1["k1"].(Value)["local"] = "local" + d2["k1"].(Value)["inbound"] = "inbound" + + MustBeSuccessful(a.SetValue(d1)) + MustBeSuccessful(b.SetValue(d2)) + + d2["k1"].(Value)["local"] = "local" + + MustFailWithMessage(ErrorFrom(handler.Merge(nil, a, &b, nil)), "[simpleMapMerge]: target value for \"k1\" changed") + MustBeSuccessful(handler.Merge(ocm.DefaultContext(), a, &b, cfg)) + + var r Value + MustBeSuccessful(b.GetValue(&r)) + Expect(r).To(DeepEqual(d2)) + }) + + It("resolves to inbound", func() { + d2["k1"].(Value)["data"] = "inbound" + + MustBeSuccessful(a.SetValue(d1)) + MustBeSuccessful(b.SetValue(d2)) + + MustFailWithMessage(ErrorFrom(handler.Merge(nil, a, &b, nil)), "[simpleMapMerge]: target value for \"k1\" changed") + MustBeSuccessful(handler.Merge(ocm.DefaultContext(), a, &b, cfg)) + + var r Value + MustBeSuccessful(b.GetValue(&r)) + Expect(r).To(DeepEqual(d2)) + }) + + It("resolves to local", func() { + cfg = NewConfig("", Must(hpi.NewSpecification(ALGORITHM, NewConfig(MODE_LOCAL)))) + + d1["k1"].(Value)["data"] = "local" + + MustBeSuccessful(a.SetValue(d1)) + MustBeSuccessful(b.SetValue(d2)) + + MustFailWithMessage(ErrorFrom(handler.Merge(nil, a, &b, nil)), "[simpleMapMerge]: target value for \"k1\" changed") + MustBeSuccessful(handler.Merge(ocm.DefaultContext(), a, &b, cfg)) + + var r Value + MustBeSuccessful(b.GetValue(&r)) + Expect(r).To(DeepEqual(d1)) + }) + }) +}) diff --git a/pkg/contexts/ocm/valuemergehandler/handlers/simplemapmerge/suite_test.go b/api/ocm/valuemergehandler/handlers/simplemapmerge/suite_test.go similarity index 100% rename from pkg/contexts/ocm/valuemergehandler/handlers/simplemapmerge/suite_test.go rename to api/ocm/valuemergehandler/handlers/simplemapmerge/suite_test.go diff --git a/api/ocm/valuemergehandler/hpi/interface.go b/api/ocm/valuemergehandler/hpi/interface.go new file mode 100644 index 000000000..9a0250322 --- /dev/null +++ b/api/ocm/valuemergehandler/hpi/interface.go @@ -0,0 +1,86 @@ +// Package hpi contains the Handler Programming Interface for +// value merge handlers +package hpi + +import ( + "ocm.software/ocm/api/datacontext" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/valuemergehandler/internal" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" +) + +// resolve package cycle among default merge handler and +// labelmergehandler by separating commonly used types +// into this package + +// same problem for the embedding into the OCM environment +// required for the ocm.Context access. +// Because of this cycle, the registry implementation and the +// required types have to be placed into the internal package of +// ocm and forwarded to the cpi packages. From there it can be consumed +// here to break the dependency cycle. + +type ( + Context = internal.Context + Handler = internal.Handler + Config = internal.Config + Registry = internal.Registry + Specification = internal.Specification + Value = internal.Value + Hint = internal.Hint +) + +const KIND_VALUE_MERGE_ALGORITHM = metav1.KIND_VALUE_MERGE_ALGORITHM + +func Register(h Handler) { + internal.Register(h) +} + +func Assign(hint Hint, spec *Specification) { + internal.Assign(hint, spec) +} + +func NewSpecification(algo string, cfg ...Config) (*Specification, error) { + raw, err := runtime.AsRawMessage(utils.Optional(cfg...)) + if err != nil { + return nil, err + } + return &Specification{ + Algorithm: algo, + Config: raw, + }, nil +} + +func NewRegistry(base ...Registry) Registry { + return internal.NewRegistry(base...) +} + +func LabelHint(name string, optversion ...string) Hint { + hint := "label:" + name + v := utils.Optional(optversion...) + if v != "" { + hint += "@" + v + } + return Hint(hint) +} + +//////////////////////////////////////////////////////////////////////////////// + +const ATTR_MERGE_HANDLERS = "ocm.software/ocm/api/ocm/valuemergehandlers" + +func For(ctx cpi.ContextProvider) Registry { + if ctx == nil { + return internal.DefaultRegistry + } + return ctx.OCMContext().GetAttributes().GetOrCreateAttribute(ATTR_MERGE_HANDLERS, create).(Registry) +} + +func create(datacontext.Context) interface{} { + return NewRegistry(internal.DefaultRegistry) +} + +func SetFor(ctx datacontext.Context, registry Registry) { + ctx.GetAttributes().SetAttribute(ATTR_MERGE_HANDLERS, registry) +} diff --git a/api/ocm/valuemergehandler/hpi/logging.go b/api/ocm/valuemergehandler/hpi/logging.go new file mode 100644 index 000000000..72c60ae5c --- /dev/null +++ b/api/ocm/valuemergehandler/hpi/logging.go @@ -0,0 +1,9 @@ +package hpi + +import ( + ocmlog "ocm.software/ocm/api/utils/logging" +) + +var REALM = ocmlog.DefineSubRealm("value marge handling", "valuemerge") + +var Log = ocmlog.DynamicLogger(REALM) diff --git a/api/ocm/valuemergehandler/hpi/merge.go b/api/ocm/valuemergehandler/hpi/merge.go new file mode 100644 index 000000000..5ffaa3deb --- /dev/null +++ b/api/ocm/valuemergehandler/hpi/merge.go @@ -0,0 +1,97 @@ +package hpi + +import ( + "strings" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/ocm/valuemergehandler/handlers/defaultmerge" +) + +func AsValue(v interface{}) (*Value, error) { + if v == nil { + return nil, nil + } + var r Value + err := r.SetValue(v) + if err != nil { + return nil, err + } + return &r, nil +} + +func GenericMerge[T any](ctx Context, m *Specification, hint string, local T, inbound T) (bool, T, error) { + var Nil T + + l, err := AsValue(local) + if err != nil { + return false, Nil, err + } + t, err := AsValue(inbound) + if err != nil { + return false, Nil, err + } + mod, err := Merge(ctx, m, "", *l, t) + if err != nil { + return false, Nil, err + } + if mod { + inbound = Nil + err = t.GetValue(&inbound) + if err != nil { + return false, Nil, errors.Wrapf(err, "cannot value merge result") + } + } + return mod, inbound, nil +} + +// Merge merges two value using the given merge specification. +// The hint describes a merge hint if no algorithm is specified. +// It used the format [@]. If used the is looks +// for an assignment for this hint, first with version and the without version. +func Merge(ctx Context, m *Specification, hint Hint, local Value, inbound *Value) (bool, error) { + var err error + + reg := For(ctx) + + if m == nil { + m = &Specification{} + } else { + t := *m + m = &t + } + if m.Algorithm == "" && hint != "" { + spec := reg.GetAssignment(hint) + if spec == nil { + idx := strings.LastIndex(string(hint), "@") + if idx > 1 { + hint = hint[:idx] + } + spec = reg.GetAssignment(hint) + } + if spec != nil { + m = spec + } + } + if m.Algorithm == "" { + m.Algorithm = defaultmerge.ALGORITHM + } + + h := reg.GetHandler(m.Algorithm) + if h == nil { + return false, errors.ErrUnknown(KIND_VALUE_MERGE_ALGORITHM, m.Algorithm) + } + + Log.Trace("merge handler", "handler", m.Algorithm, "config", m.Config) + var cfg Config + if len(m.Config) != 0 { + cfg, err = h.DecodeConfig(m.Config) + if err == nil { + err = cfg.Complete(ctx) + } + if err != nil { + return false, errors.Wrapf(err, "invalid merge config for algorithm %q", m.Algorithm) + } + } + return h.Merge(ctx, local, inbound, cfg) +} diff --git a/api/ocm/valuemergehandler/hpi/setup.go b/api/ocm/valuemergehandler/hpi/setup.go new file mode 100644 index 000000000..b2025ce82 --- /dev/null +++ b/api/ocm/valuemergehandler/hpi/setup.go @@ -0,0 +1,28 @@ +package hpi + +import ( + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/valuemergehandler/internal" +) + +func init() { + datacontext.RegisterSetupHandler(datacontext.SetupHandlerFunction(setupContext)) +} + +func setupContext(mode datacontext.BuilderMode, ctx datacontext.Context) { + if octx, ok := ctx.(cpi.Context); ok { + switch mode { + case datacontext.MODE_SHARED: + fallthrough + case datacontext.MODE_DEFAULTED: + // do nothing, fallback to the default attribute lookup + case datacontext.MODE_EXTENDED: + SetFor(octx, NewRegistry(internal.DefaultRegistry)) + case datacontext.MODE_CONFIGURED: + SetFor(octx, internal.DefaultRegistry.Copy()) + case datacontext.MODE_INITIAL: + SetFor(octx, NewRegistry()) + } + } +} diff --git a/api/ocm/valuemergehandler/hpi/support.go b/api/ocm/valuemergehandler/hpi/support.go new file mode 100644 index 000000000..befbd8d08 --- /dev/null +++ b/api/ocm/valuemergehandler/hpi/support.go @@ -0,0 +1,19 @@ +package hpi + +import ( + "ocm.software/ocm/api/ocm/valuemergehandler/internal" +) + +type EmptyConfig struct{} + +var _ Config = (*EmptyConfig)(nil) + +func (c *EmptyConfig) Complete(ctx Context) error { + return nil +} + +type Merger[C, T any] func(ctx Context, cfg C, local T, target *T) (bool, error) + +func New[C any, L any, P internal.ConfigPointer[C]](algo string, desc string, merger internal.Merger[P, L]) Handler { + return internal.New[C, L, P](algo, desc, merger) +} diff --git a/api/ocm/valuemergehandler/interface.go b/api/ocm/valuemergehandler/interface.go new file mode 100644 index 000000000..2f399e8eb --- /dev/null +++ b/api/ocm/valuemergehandler/interface.go @@ -0,0 +1,42 @@ +package valuemergehandler + +import ( + _ "ocm.software/ocm/api/ocm/valuemergehandler/config" + _ "ocm.software/ocm/api/ocm/valuemergehandler/handlers" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/valuemergehandler/hpi" + "ocm.software/ocm/api/ocm/valuemergehandler/internal" +) + +type ( + Context = internal.Context + Handler = internal.Handler + Handlers = internal.Handlers + Config = internal.Config + Registry = internal.Registry + Specification = internal.Specification + Value = internal.Value +) + +const ( + KIND_VALUE_MERGE_ALGORITHM = hpi.KIND_VALUE_MERGE_ALGORITHM + KIND_VALUESET = "value set" +) + +func NewSpecification(algo string, cfg Config) (*Specification, error) { + return hpi.NewSpecification(algo, cfg) +} + +func NewRegistry(base ...Registry) Registry { + return internal.NewRegistry(base...) +} + +func For(ctx cpi.ContextProvider) Registry { + return hpi.For(ctx) +} + +func SetFor(ctx datacontext.Context, registry Registry) { + hpi.SetFor(ctx, registry) +} diff --git a/api/ocm/valuemergehandler/internal/interface.go b/api/ocm/valuemergehandler/internal/interface.go new file mode 100644 index 000000000..19a870bdf --- /dev/null +++ b/api/ocm/valuemergehandler/internal/interface.go @@ -0,0 +1,33 @@ +package internal + +import ( + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils/runtime" +) + +// resolve package cycle among default merge handler and +// labelmergehandler by separating commonly used types +// into this package + +// same problem for the embedding into the OCM environment +// required for the ocm.Context access. +// Because of this cycle, the registry implementation and the +// required types have to be placed into the internal package of +// ocm and forwarded to the cpi packages. From there it can be consumed +// here to break the dependency cycle. + +type ( + Context = cpi.Context + Specification = metav1.MergeAlgorithmSpecification + Value = runtime.RawValue + Hint string +) + +func Register(h Handler) { + DefaultRegistry.RegisterHandler(h) +} + +func Assign(hint Hint, spec *Specification) { + DefaultRegistry.AssignHandler(hint, spec) +} diff --git a/api/ocm/valuemergehandler/internal/support.go b/api/ocm/valuemergehandler/internal/support.go new file mode 100644 index 000000000..fbfe68c37 --- /dev/null +++ b/api/ocm/valuemergehandler/internal/support.go @@ -0,0 +1,92 @@ +package internal + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/utils/runtime" +) + +type ConfigPointer[T any] interface { + Config + *T +} + +type Merger[C, T any] func(ctx Context, cfg C, local T, target *T) (bool, error) + +func New[C any, L any, P ConfigPointer[C]](algo string, desc string, merger Merger[P, L]) Handler { + return &HandlerSupport[C, L, P]{ + algorithm: algo, + description: desc, + merger: merger, + } +} + +// HandlerSupport is a basic support for label merge handlers. +type HandlerSupport[C any, L any, P ConfigPointer[C]] struct { + algorithm string + description string + merger Merger[P, L] +} + +func (h *HandlerSupport[C, L, P]) Algorithm() string { + return h.algorithm +} + +func (h *HandlerSupport[C, L, P]) Description() string { + return h.description +} + +func (h HandlerSupport[C, L, P]) DecodeConfig(data []byte) (Config, error) { + var cfg C + err := runtime.DefaultYAMLEncoding.Unmarshal(data, &cfg) + if err != nil { + return nil, err + } + var p P = &cfg + return p, nil +} + +func (h *HandlerSupport[C, L, P]) Merge(ctx Context, local Value, inbound *Value, cfg Config) (bool, error) { + var c P + + if cfg == nil { + var zero C + c = &zero + err := c.Complete(ctx) + if err != nil { + return false, errors.Wrapf(err, "[%s] invalid initial config") + } + } else { + var ok bool + + c, ok = cfg.(P) + if !ok { + return false, errors.ErrInvalid("[%s] value merge config type", h.algorithm, fmt.Sprintf("%T", cfg)) + } + } + + var lv L + if err := local.GetValue(&lv); err != nil { + return false, errors.Wrapf(err, "[%s] local value is not valid", h.algorithm) + } + + var tv L + if err := inbound.GetValue(&tv); err != nil { + return false, errors.Wrapf(err, "[%s] inbound value is not valid", h.algorithm) + } + + modified, err := h.merger(ctx, c, lv, &tv) + if err != nil { + return false, errors.Wrapf(err, "[%s]", h.algorithm) + } + + if modified { + err := inbound.SetValue(tv) + if err != nil { + return false, err + } + } + return modified, nil +} diff --git a/pkg/contexts/ocm/valuemergehandler/internal/valuemergehandler.go b/api/ocm/valuemergehandler/internal/valuemergehandler.go similarity index 96% rename from pkg/contexts/ocm/valuemergehandler/internal/valuemergehandler.go rename to api/ocm/valuemergehandler/internal/valuemergehandler.go index 764821f20..cdd5d47fa 100644 --- a/pkg/contexts/ocm/valuemergehandler/internal/valuemergehandler.go +++ b/api/ocm/valuemergehandler/internal/valuemergehandler.go @@ -5,8 +5,8 @@ import ( "golang.org/x/exp/maps" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" ) type Config interface { diff --git a/api/ocm/valuemergehandler/merge.go b/api/ocm/valuemergehandler/merge.go new file mode 100644 index 000000000..7c2ffe0fa --- /dev/null +++ b/api/ocm/valuemergehandler/merge.go @@ -0,0 +1,14 @@ +package valuemergehandler + +import ( + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/valuemergehandler/hpi" +) + +func Merge(ctx cpi.Context, m *Specification, hint hpi.Hint, local Value, inbound *Value) (bool, error) { + return hpi.Merge(ctx, m, hint, local, inbound) +} + +func LabelHint(name string, optversion ...string) hpi.Hint { + return hpi.LabelHint(name, optversion...) +} diff --git a/api/ocm/valuemergehandler/usage.go b/api/ocm/valuemergehandler/usage.go new file mode 100644 index 000000000..671f7a6f5 --- /dev/null +++ b/api/ocm/valuemergehandler/usage.go @@ -0,0 +1,18 @@ +package valuemergehandler + +import ( + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/utils/listformat" +) + +func Usage(ctx ocm.Context) string { + usage := listformat.FormatMapElements("default", For(ctx).GetHandlers()) + ` +` + list := For(ctx).GetAssignments() + if len(list) > 0 { + usage += ` +The following label assignments are configured: +` + listformat.FormatMapElements("", list) + } + return usage +} diff --git a/pkg/docker/README.md b/api/tech/docker/README.md similarity index 100% rename from pkg/docker/README.md rename to api/tech/docker/README.md diff --git a/pkg/docker/errors/errors.go b/api/tech/docker/errors/errors.go similarity index 100% rename from pkg/docker/errors/errors.go rename to api/tech/docker/errors/errors.go diff --git a/pkg/docker/fetcher.go b/api/tech/docker/fetcher.go similarity index 98% rename from pkg/docker/fetcher.go rename to api/tech/docker/fetcher.go index 6afe94630..4a2eec584 100644 --- a/pkg/docker/fetcher.go +++ b/api/tech/docker/fetcher.go @@ -15,7 +15,7 @@ import ( ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" - "github.com/open-component-model/ocm/pkg/common/accessio" + "ocm.software/ocm/api/utils/accessio" ) type dockerFetcher struct { diff --git a/pkg/docker/handler.go b/api/tech/docker/handler.go similarity index 100% rename from pkg/docker/handler.go rename to api/tech/docker/handler.go diff --git a/pkg/docker/httpreadseeker.go b/api/tech/docker/httpreadseeker.go similarity index 100% rename from pkg/docker/httpreadseeker.go rename to api/tech/docker/httpreadseeker.go diff --git a/pkg/docker/lister.go b/api/tech/docker/lister.go similarity index 98% rename from pkg/docker/lister.go rename to api/tech/docker/lister.go index 22e9122c3..efd3b8e1e 100644 --- a/pkg/docker/lister.go +++ b/api/tech/docker/lister.go @@ -10,7 +10,7 @@ import ( "github.com/containerd/containerd/log" "github.com/pkg/errors" - "github.com/open-component-model/ocm/pkg/docker/resolve" + "ocm.software/ocm/api/tech/docker/resolve" ) var ErrObjectNotRequired = errors.New("object not required") diff --git a/pkg/docker/orig.go b/api/tech/docker/orig.go similarity index 100% rename from pkg/docker/orig.go rename to api/tech/docker/orig.go diff --git a/pkg/docker/pusher.go b/api/tech/docker/pusher.go similarity index 98% rename from pkg/docker/pusher.go rename to api/tech/docker/pusher.go index 7d31c59f7..708ad0f34 100644 --- a/pkg/docker/pusher.go +++ b/api/tech/docker/pusher.go @@ -18,9 +18,9 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/open-component-model/ocm/pkg/common/accessio" - remoteserrors "github.com/open-component-model/ocm/pkg/docker/errors" - "github.com/open-component-model/ocm/pkg/docker/resolve" + remoteserrors "ocm.software/ocm/api/tech/docker/errors" + "ocm.software/ocm/api/tech/docker/resolve" + "ocm.software/ocm/api/utils/accessio" ) func init() { diff --git a/pkg/docker/registry.go b/api/tech/docker/registry.go similarity index 100% rename from pkg/docker/registry.go rename to api/tech/docker/registry.go diff --git a/pkg/docker/resolve/interface.go b/api/tech/docker/resolve/interface.go similarity index 100% rename from pkg/docker/resolve/interface.go rename to api/tech/docker/resolve/interface.go diff --git a/api/tech/docker/resolver.go b/api/tech/docker/resolver.go new file mode 100644 index 000000000..292df03ae --- /dev/null +++ b/api/tech/docker/resolver.go @@ -0,0 +1,656 @@ +package docker + +import ( + "context" + "fmt" + "io" + "net/http" + "net/url" + "path" + "strings" + + "github.com/containerd/containerd/errdefs" + "github.com/containerd/containerd/images" + "github.com/containerd/containerd/log" + "github.com/containerd/containerd/reference" + "github.com/containerd/containerd/remotes/docker/schema1" + "github.com/containerd/containerd/version" + "github.com/opencontainers/go-digest" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "golang.org/x/net/context/ctxhttp" + + "ocm.software/ocm/api/tech/docker/resolve" + "ocm.software/ocm/api/utils/accessio" +) + +var ( + // ErrInvalidAuthorization is used when credentials are passed to a server but + // those credentials are rejected. + ErrInvalidAuthorization = errors.New("authorization failed") + + // MaxManifestSize represents the largest size accepted from a registry + // during resolution. Larger manifests may be accepted using a + // resolution method other than the registry. + // + // NOTE: The max supported layers by some runtimes is 128 and individual + // layers will not contribute more than 256 bytes, making a + // reasonable limit for a large image manifests of 32K bytes. + // 4M bytes represents a much larger upper bound for images which may + // contain large annotations or be non-images. A proper manifest + // design puts large metadata in subobjects, as is consistent the + // intent of the manifest design. + MaxManifestSize int64 = 4 * 1048 * 1048 +) + +// Authorizer is used to authorize HTTP requests based on 401 HTTP responses. +// An Authorizer is responsible for caching tokens or credentials used by +// requests. +type Authorizer interface { + // Authorize sets the appropriate `Authorization` header on the given + // request. + // + // If no authorization is found for the request, the request remains + // unmodified. It may also add an `Authorization` header as + // "bearer " + // "basic " + Authorize(context.Context, *http.Request) error + + // AddResponses adds a 401 response for the authorizer to consider when + // authorizing requests. The last response should be unauthorized and + // the previous requests are used to consider redirects and retries + // that may have led to the 401. + // + // If response is not handled, returns `ErrNotImplemented` + AddResponses(context.Context, []*http.Response) error +} + +// ResolverOptions are used to configured a new Docker register resolver. +type ResolverOptions struct { + // Hosts returns registry host configurations for a namespace. + Hosts RegistryHosts + + // Headers are the HTTP request header fields sent by the resolver + Headers http.Header + + // Tracker is used to track uploads to the registry. This is used + // since the registry does not have upload tracking and the existing + // mechanism for getting blob upload status is expensive. + Tracker StatusTracker + + // Authorizer is used to authorize registry requests + // Deprecated: use Hosts + Authorizer Authorizer + + // Credentials provides username and secret given a host. + // If username is empty but a secret is given, that secret + // is interpreted as a long lived token. + // Deprecated: use Hosts + Credentials func(string) (string, string, error) + + // Host provides the hostname given a namespace. + // Deprecated: use Hosts + Host func(string) (string, error) + + // PlainHTTP specifies to use plain http and not https + // Deprecated: use Hosts + PlainHTTP bool + + // Client is the http client to used when making registry requests + // Deprecated: use Hosts + Client *http.Client +} + +// DefaultHost is the default host function. +func DefaultHost(ns string) (string, error) { + if ns == "docker.io" { + return "registry-1.docker.io", nil + } + return ns, nil +} + +type dockerResolver struct { + hosts RegistryHosts + header http.Header + resolveHeader http.Header + tracker StatusTracker +} + +// NewResolver returns a new resolver to a Docker registry. +func NewResolver(options ResolverOptions) resolve.Resolver { + if options.Tracker == nil { + options.Tracker = NewInMemoryTracker() + } + + if options.Headers == nil { + options.Headers = make(http.Header) + } + if _, ok := options.Headers["User-Agent"]; !ok { + options.Headers.Set("User-Agent", "containerd/"+version.Version) + } + + resolveHeader := http.Header{} + if _, ok := options.Headers["Accept"]; !ok { + // set headers for all the types we support for resolution. + resolveHeader.Set("Accept", strings.Join([]string{ + images.MediaTypeDockerSchema2Manifest, + images.MediaTypeDockerSchema2ManifestList, + ocispec.MediaTypeImageManifest, + ocispec.MediaTypeImageIndex, "*/*", + }, ", ")) + } else { + resolveHeader["Accept"] = options.Headers["Accept"] + delete(options.Headers, "Accept") + } + + if options.Hosts == nil { + opts := []RegistryOpt{} + if options.Host != nil { + opts = append(opts, WithHostTranslator(options.Host)) + } + + if options.Authorizer == nil { + options.Authorizer = NewDockerAuthorizer( + WithAuthClient(options.Client), + WithAuthHeader(options.Headers), + WithAuthCreds(options.Credentials)) + } + opts = append(opts, WithAuthorizer(options.Authorizer)) + + if options.Client != nil { + opts = append(opts, WithClient(options.Client)) + } + if options.PlainHTTP { + opts = append(opts, WithPlainHTTP(MatchAllHosts)) + } else { + opts = append(opts, WithPlainHTTP(MatchLocalhost)) + } + options.Hosts = ConfigureDefaultRegistries(opts...) + } + return &dockerResolver{ + hosts: options.Hosts, + header: options.Headers, + resolveHeader: resolveHeader, + tracker: options.Tracker, + } +} + +func getManifestMediaType(resp *http.Response) string { + // Strip encoding data (manifests should always be ascii JSON) + contentType := resp.Header.Get("Content-Type") + if sp := strings.IndexByte(contentType, ';'); sp != -1 { + contentType = contentType[0:sp] + } + + // As of Apr 30 2019 the registry.access.redhat.com registry does not specify + // the content type of any data but uses schema1 manifests. + if contentType == "text/plain" { + contentType = images.MediaTypeDockerSchema1Manifest + } + return contentType +} + +type countingReader struct { + reader io.Reader + bytesRead int64 +} + +func (r *countingReader) Read(p []byte) (int, error) { + n, err := r.reader.Read(p) + r.bytesRead += int64(n) + return n, err +} + +var _ resolve.Resolver = &dockerResolver{} + +func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocispec.Descriptor, error) { + base, err := r.resolveDockerBase(ref) + if err != nil { + return "", ocispec.Descriptor{}, err + } + refspec := base.refspec + if refspec.Object == "" { + return "", ocispec.Descriptor{}, reference.ErrObjectRequired + } + + var ( + firstErr error + paths [][]string + dgst = refspec.Digest() + caps = HostCapabilityPull + ) + + if dgst != "" { + if err := dgst.Validate(); err != nil { + // need to fail here, since we can't actually resolve the invalid + // digest. + return "", ocispec.Descriptor{}, err + } + + // turns out, we have a valid digest, make a url. + paths = append(paths, []string{"manifests", dgst.String()}) + + // fallback to blobs on not found. + paths = append(paths, []string{"blobs", dgst.String()}) + } else { + // Add + paths = append(paths, []string{"manifests", refspec.Object}) + caps |= HostCapabilityResolve + } + + hosts := base.filterHosts(caps) + if len(hosts) == 0 { + return "", ocispec.Descriptor{}, errors.Wrap(errdefs.ErrNotFound, "no resolve hosts") + } + + ctx, err = ContextWithRepositoryScope(ctx, refspec, false) + if err != nil { + return "", ocispec.Descriptor{}, err + } + + for _, u := range paths { + for _, host := range hosts { + ctxWithLogger := log.WithLogger(ctx, log.G(ctx).WithField("host", host.Host)) + + req := base.request(host, http.MethodHead, u...) + if err := req.addNamespace(base.refspec.Hostname()); err != nil { + return "", ocispec.Descriptor{}, err + } + + for key, value := range r.resolveHeader { + req.header[key] = append(req.header[key], value...) + } + + log.G(ctxWithLogger).Debug("resolving") + resp, err := req.doWithRetries(ctxWithLogger, nil) + if err != nil { + if errors.Is(err, ErrInvalidAuthorization) { + err = errors.Wrapf(err, "pull access denied, repository does not exist or may require authorization") + } else { + err = accessio.RetriableError(err) + } + // Store the error for referencing later + if firstErr == nil { + firstErr = err + } + log.G(ctxWithLogger).WithError(err).Info("trying next host") + continue // try another host + } + resp.Body.Close() // don't care about body contents. + + if resp.StatusCode > 299 { + if resp.StatusCode == http.StatusNotFound { + // log.G(ctxWithLogger).Info("trying next host - response was http.StatusNotFound") + continue + } + if resp.StatusCode > 399 { + // Set firstErr when encountering the first non-404 status code. + if firstErr == nil { + firstErr = errors.Errorf("pulling from host %s failed with status code %v: %v", host.Host, u, resp.Status) + } + continue // try another host + } + return "", ocispec.Descriptor{}, errors.Errorf("pulling from host %s failed with unexpected status code %v: %v", host.Host, u, resp.Status) + } + size := resp.ContentLength + contentType := getManifestMediaType(resp) + + // if no digest was provided, then only a resolve + // trusted registry was contacted, in this case use + // the digest header (or content from GET) + if dgst == "" { + // this is the only point at which we trust the registry. we use the + // content headers to assemble a descriptor for the name. when this becomes + // more robust, we mostly get this information from a secure trust store. + dgstHeader := digest.Digest(resp.Header.Get("Docker-Content-Digest")) + + if dgstHeader != "" && size != -1 { + if err := dgstHeader.Validate(); err != nil { + return "", ocispec.Descriptor{}, errors.Wrapf(err, "%q in header not a valid digest", dgstHeader) + } + dgst = dgstHeader + } + } + if dgst == "" || size == -1 { + log.G(ctxWithLogger).Debug("no Docker-Content-Digest header, fetching manifest instead") + + req = base.request(host, http.MethodGet, u...) + if err := req.addNamespace(base.refspec.Hostname()); err != nil { + return "", ocispec.Descriptor{}, err + } + + for key, value := range r.resolveHeader { + req.header[key] = append(req.header[key], value...) + } + + resp, err := req.doWithRetries(ctxWithLogger, nil) + if err != nil { + return "", ocispec.Descriptor{}, accessio.RetriableError(err) + } + defer resp.Body.Close() + + bodyReader := countingReader{reader: resp.Body} + + contentType = getManifestMediaType(resp) + if dgst == "" { + if contentType == images.MediaTypeDockerSchema1Manifest { + b, err := schema1.ReadStripSignature(&bodyReader) + if err != nil { + return "", ocispec.Descriptor{}, accessio.RetriableError(err) + } + + dgst = digest.FromBytes(b) + } else { + dgst, err = digest.FromReader(&bodyReader) + if err != nil { + return "", ocispec.Descriptor{}, accessio.RetriableError(err) + } + } + } else if _, err := io.Copy(io.Discard, &bodyReader); err != nil { + return "", ocispec.Descriptor{}, accessio.RetriableError(err) + } + size = bodyReader.bytesRead + } + // Prevent resolving to excessively large manifests + if size > MaxManifestSize { + if firstErr == nil { + firstErr = errors.Wrapf(errdefs.ErrNotFound, "rejecting %d byte manifest for %s", size, ref) + } + continue + } + + desc := ocispec.Descriptor{ + Digest: dgst, + MediaType: contentType, + Size: size, + } + + log.G(ctxWithLogger).WithField("desc.digest", desc.Digest).Debug("resolved") + return ref, desc, nil + } + } + + // If above loop terminates without return, then there was an error. + // "firstErr" contains the first non-404 error. That is, "firstErr == nil" + // means that either no registries were given or each registry returned 404. + + if firstErr == nil { + firstErr = errors.Wrap(errdefs.ErrNotFound, ref) + } + + return "", ocispec.Descriptor{}, firstErr +} + +func (r *dockerResolver) Fetcher(ctx context.Context, ref string) (resolve.Fetcher, error) { + base, err := r.resolveDockerBase(ref) + if err != nil { + return nil, err + } + + return dockerFetcher{ + dockerBase: base, + }, nil +} + +func (r *dockerResolver) Pusher(ctx context.Context, ref string) (resolve.Pusher, error) { + base, err := r.resolveDockerBase(ref) + if err != nil { + return nil, err + } + + return dockerPusher{ + dockerBase: base, + object: base.refspec.Object, + tracker: r.tracker, + }, nil +} + +func (r *dockerResolver) resolveDockerBase(ref string) (*dockerBase, error) { + refspec, err := reference.Parse(ref) + if err != nil { + return nil, err + } + + return r.base(refspec) +} + +type dockerBase struct { + refspec reference.Spec + repository string + hosts []RegistryHost + header http.Header +} + +func (r *dockerResolver) base(refspec reference.Spec) (*dockerBase, error) { + host := refspec.Hostname() + hosts, err := r.hosts(host) + if err != nil { + return nil, err + } + return &dockerBase{ + refspec: refspec, + repository: strings.TrimPrefix(refspec.Locator, host+"/"), + hosts: hosts, + header: r.header, + }, nil +} + +func (r *dockerBase) filterHosts(caps HostCapabilities) (hosts []RegistryHost) { + for _, host := range r.hosts { + if host.Capabilities.Has(caps) { + hosts = append(hosts, host) + } + } + return +} + +func (r *dockerBase) request(host RegistryHost, method string, ps ...string) *request { + header := r.header.Clone() + if header == nil { + header = http.Header{} + } + + for key, value := range host.Header { + header[key] = append(header[key], value...) + } + parts := append([]string{"/", host.Path, r.repository}, ps...) + p := path.Join(parts...) + // Join strips trailing slash, re-add ending "/" if included + if len(parts) > 0 && strings.HasSuffix(parts[len(parts)-1], "/") { + p += "/" + } + return &request{ + method: method, + path: p, + header: header, + host: host, + } +} + +func (r *request) authorize(ctx context.Context, req *http.Request) error { + // Check if has header for host + if r.host.Authorizer != nil { + if err := r.host.Authorizer.Authorize(ctx, req); err != nil { + return err + } + } + + return nil +} + +func (r *request) addNamespace(ns string) (err error) { + if !r.host.isProxy(ns) { + return nil + } + var q url.Values + // Parse query + if i := strings.IndexByte(r.path, '?'); i > 0 { + r.path = r.path[:i+1] + q, err = url.ParseQuery(r.path[i+1:]) + if err != nil { + return + } + } else { + r.path += "?" + q = url.Values{} + } + q.Add("ns", ns) + + r.path += q.Encode() + + return +} + +type request struct { + method string + path string + header http.Header + host RegistryHost + body func() (io.ReadCloser, error) + size int64 +} + +func (r *request) do(ctx context.Context) (*http.Response, error) { + u := r.host.Scheme + "://" + r.host.Host + r.path + req, err := http.NewRequestWithContext(ctx, r.method, u, nil) + if err != nil { + return nil, err + } + req.Header = http.Header{} // headers need to be copied to avoid concurrent map access + for k, v := range r.header { + req.Header[k] = v + } + if r.body != nil { + body, err := r.body() + if err != nil { + return nil, err + } + req.Body = body + req.GetBody = r.body + if r.size > 0 { + req.ContentLength = r.size + } + defer body.Close() + } + + ctx = log.WithLogger(ctx, log.G(ctx).WithField("url", u)) + log.G(ctx).WithFields(sanitizedRequestFields(req)).Debug("do request") + if err := r.authorize(ctx, req); err != nil { + return nil, errors.Wrap(err, "failed to authorize") + } + + client := &http.Client{} + if r.host.Client != nil { + *client = *r.host.Client + } + if client.CheckRedirect == nil { + client.CheckRedirect = func(req *http.Request, via []*http.Request) error { + if len(via) >= 10 { + return errors.New("stopped after 10 redirects") + } + return errors.Wrap(r.authorize(ctx, req), "failed to authorize redirect") + } + } + + resp, err := ctxhttp.Do(ctx, client, req) + if err != nil { + return nil, errors.Wrap(err, "failed to do request") + } + log.G(ctx).WithFields(responseFields(resp)).Debug("fetch response received") + return resp, nil +} + +func (r *request) doWithRetries(ctx context.Context, responses []*http.Response) (*http.Response, error) { + resp, err := r.do(ctx) + if err != nil { + return nil, err + } + + responses = append(responses, resp) + retry, err := r.retryRequest(ctx, responses) + if err != nil { + resp.Body.Close() + return nil, err + } + if retry { + resp.Body.Close() + return r.doWithRetries(ctx, responses) + } + return resp, err +} + +func (r *request) retryRequest(ctx context.Context, responses []*http.Response) (bool, error) { + if len(responses) > 5 { + return false, nil + } + last := responses[len(responses)-1] + switch last.StatusCode { + case http.StatusUnauthorized: + log.G(ctx).WithField("header", last.Header.Get("WWW-Authenticate")).Debug("Unauthorized") + if r.host.Authorizer != nil { + if err := r.host.Authorizer.AddResponses(ctx, responses); err == nil { + return true, nil + } else if !errdefs.IsNotImplemented(err) { + return false, err + } + } + + return false, nil + case http.StatusMethodNotAllowed: + // Support registries which have not properly implemented the HEAD method for + // manifests endpoint + if r.method == http.MethodHead && strings.Contains(r.path, "/manifests/") { + r.method = http.MethodGet + return true, nil + } + case http.StatusRequestTimeout, http.StatusTooManyRequests: + return true, nil + } + + // TODO: Handle 50x errors accounting for attempt history + return false, nil +} + +func (r *request) String() string { + return r.host.Scheme + "://" + r.host.Host + r.path +} + +func sanitizedRequestFields(req *http.Request) logrus.Fields { + fields := map[string]interface{}{ + "request.method": req.Method, + } + for k, vals := range req.Header { + k = strings.ToLower(k) + if k == "authorization" { + continue + } + for i, v := range vals { + field := "request.header." + k + if i > 0 { + field = fmt.Sprintf("%s.%d", field, i) + } + fields[field] = v + } + } + + return logrus.Fields(fields) +} + +func responseFields(resp *http.Response) logrus.Fields { + fields := map[string]interface{}{ + "response.status": resp.Status, + } + for k, vals := range resp.Header { + k = strings.ToLower(k) + for i, v := range vals { + field := "response.header." + k + if i > 0 { + field = fmt.Sprintf("%s.%d", field, i) + } + fields[field] = v + } + } + + return logrus.Fields(fields) +} diff --git a/pkg/helm/chartaccess.go b/api/tech/helm/chartaccess.go similarity index 89% rename from pkg/helm/chartaccess.go rename to api/tech/helm/chartaccess.go index 60200d8f9..33a863bf4 100644 --- a/pkg/helm/chartaccess.go +++ b/api/tech/helm/chartaccess.go @@ -9,11 +9,11 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" "helm.sh/helm/v3/pkg/registry" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/blobaccess/file" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/refmgmt" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/file" + "ocm.software/ocm/api/utils/refmgmt" ) const ( diff --git a/api/tech/helm/downloader.go b/api/tech/helm/downloader.go new file mode 100644 index 000000000..e1462bf54 --- /dev/null +++ b/api/tech/helm/downloader.go @@ -0,0 +1,198 @@ +package helm + +import ( + "fmt" + "strings" + + "github.com/mandelsoft/filepath/pkg/filepath" + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/vfs/pkg/osfs" + "github.com/mandelsoft/vfs/pkg/vfs" + "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/downloader" + "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v3/pkg/registry" + "helm.sh/helm/v3/pkg/repo" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/builtin/helm/identity" + "ocm.software/ocm/api/credentials/extensions/repositories/directcreds" + "ocm.software/ocm/api/oci" + ocihelm "ocm.software/ocm/api/ocm/extensions/download/handlers/helm" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" +) + +type chartDownloader struct { + *downloader.ChartDownloader + *chartAccess + creds common.Properties + keyring []byte +} + +func DownloadChart(out common.Printer, ctx oci.ContextProvider, ref, version, repourl string, opts ...Option) (ChartAccess, error) { + if version == "" { + return nil, fmt.Errorf("version required") + } + repourl = strings.TrimSuffix(repourl, "/") + + acc, err := newTempChartAccess(osfs.New()) + if err != nil { + return nil, err + } + + defer func() { + if err != nil { + acc.Close() + } + }() + + s := cli.EnvSettings{} + + dl := &chartDownloader{ + ChartDownloader: &downloader.ChartDownloader{ + Out: out, + Getters: getter.All(&s), + }, + chartAccess: acc, + } + for _, o := range opts { + err = o.apply(dl) + if err != nil { + return nil, err + } + } + + err = dl.complete(ctx, ref, repourl) + if err != nil { + return nil, err + } + + chart := "" + prov := "" + aset := "" + if registry.IsOCI(repourl) { + fs := osfs.New() + chart = vfs.Join(fs, dl.root, filepath.Base(ref)+".tgz") + + var creds credentials.CredentialsSource + if dl.creds != nil { + creds = directcreds.NewCredentials(dl.creds) + } + + chart, prov, aset, err = ocihelm.Download2(out, ctx.OCIContext(), identity.OCIRepoURL(repourl, ref)+":"+version, chart, osfs.New(), true, creds) + if prov != "" && dl.Verify > downloader.VerifyNever && dl.Verify != downloader.VerifyLater { + _, err = downloader.VerifyChart(chart, dl.Keyring) + if err != nil { + // Fail always in this case, since it means the verification step + // failed. + return nil, err + } + } + } else { + chart, _, err = dl.DownloadTo("repo/"+ref, version, dl.root) + prov = chart + ".prov" + } + if err != nil { + return nil, err + } + if prov != "" && filepath.Exists(prov) { + dl.prov = prov + } + dl.chart = chart + dl.aset = aset + return dl.chartAccess, nil +} + +func (d *chartDownloader) complete(ctx oci.ContextProvider, ref, repourl string) error { + rf := repo.NewFile() + + if d.creds == nil { + d.creds = identity.GetCredentials(ctx.OCIContext(), repourl, ref) + } + creds := d.creds + if creds == nil { + creds = common.Properties{} + } + + config := vfs.Join(d.fs, d.root, ".config") + err := d.fs.MkdirAll(config, 0o700) + if err != nil { + return err + } + if len(d.keyring) != 0 { + err = d.writeFile("keyring", config, &d.Keyring, d.keyring, "keyring file") + if err != nil { + return err + } + d.Verify = downloader.VerifyIfPossible + } + + if registry.IsOCI(repourl) { + return nil + } + entry := repo.Entry{ + Name: "repo", + URL: repourl, + Username: creds[identity.ATTR_USERNAME], + Password: creds[identity.ATTR_PASSWORD], + } + + cache := vfs.Join(d.fs, d.root, ".cache") + err = d.fs.MkdirAll(cache, 0o700) + if err != nil { + return err + } + + if len(creds[identity.ATTR_CERTIFICATE_AUTHORITY]) != 0 { + err = d.writeFile("cacert", config, &entry.CAFile, []byte(creds[identity.ATTR_CERTIFICATE_AUTHORITY]), "CA file") + if err != nil { + return err + } + } + if len(creds[identity.ATTR_CERTIFICATE]) != 0 { + err = d.writeFile("cert", config, &entry.CertFile, []byte(creds[identity.ATTR_CERTIFICATE]), "certificate file") + if err != nil { + return err + } + } + if len(creds[identity.ATTR_PRIVATE_KEY]) != 0 { + err = d.writeFile("private-key", config, &entry.KeyFile, []byte(creds[identity.ATTR_PRIVATE_KEY]), "private key file") + if err != nil { + return err + } + } + rf.Add(&entry) + + cr, err := repo.NewChartRepository(&entry, d.Getters) + if err != nil { + return errors.Wrapf(err, "cannot get chart repository %q", repourl) + } + + d.RepositoryCache, cr.CachePath = cache, cache + + _, err = cr.DownloadIndexFile() + if err != nil { + return errors.Wrapf(err, "cannot download repository index for %q", repourl) + } + + data, err := runtime.DefaultYAMLEncoding.Marshal(rf) + if err != nil { + return errors.Wrapf(err, "cannot marshal repository file") + } + err = d.writeFile("repository", config, &d.RepositoryConfig, data, "repository config") + if err != nil { + return err + } + + return nil +} + +func (d *chartDownloader) writeFile(name, root string, path *string, data []byte, desc string) error { + *path = vfs.Join(d.fs, root, name) + err := vfs.WriteFile(d.fs, *path, data, 0o600) + if err != nil { + return errors.Wrapf(err, "cannot write %s %q", desc, *path) + } + return nil +} diff --git a/api/tech/helm/loader/access.go b/api/tech/helm/loader/access.go new file mode 100644 index 000000000..3a667b0b9 --- /dev/null +++ b/api/tech/helm/loader/access.go @@ -0,0 +1,52 @@ +package loader + +import ( + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chart/loader" + + "ocm.software/ocm/api/tech/helm" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +type accessLoader struct { + access helm.ChartAccess +} + +func AccessLoader(acc helm.ChartAccess) Loader { + return &accessLoader{access: acc} +} + +func (l *accessLoader) Close() error { + return l.access.Close() +} + +func (l *accessLoader) ChartArchive() (blobaccess.BlobAccess, error) { + return l.access.Chart() +} + +func (l *accessLoader) ChartArtefactSet() (blobaccess.BlobAccess, error) { + return l.access.ArtefactSet() +} + +func (l *accessLoader) Chart() (*chart.Chart, error) { + acc, err := l.access.Chart() + if err != nil { + return nil, err + } + defer acc.Close() + r, err := acc.Reader() + if err != nil { + return nil, err + } + defer r.Close() + return loader.LoadArchive(r) +} + +func (l *accessLoader) Provenance() ([]byte, error) { + prov, err := l.access.Prov() + if prov == nil || err != nil { + return nil, err + } + defer prov.Close() + return prov.Get() +} diff --git a/pkg/helm/loader/directory.go b/api/tech/helm/loader/directory.go similarity index 93% rename from pkg/helm/loader/directory.go rename to api/tech/helm/loader/directory.go index 46b99f54f..7d3cf5d39 100644 --- a/pkg/helm/loader/directory.go +++ b/api/tech/helm/loader/directory.go @@ -12,8 +12,8 @@ import ( "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" - "github.com/open-component-model/ocm/pkg/contexts/oci/ociutils/helm/ignore" - "github.com/open-component-model/ocm/pkg/contexts/oci/ociutils/helm/sympath" + "ocm.software/ocm/api/oci/ociutils/helm/ignore" + "ocm.software/ocm/api/oci/ociutils/helm/sympath" ) var utf8bom = []byte{0xEF, 0xBB, 0xBF} diff --git a/pkg/helm/loader/forward.go b/api/tech/helm/loader/forward.go similarity index 100% rename from pkg/helm/loader/forward.go rename to api/tech/helm/loader/forward.go diff --git a/pkg/helm/loader/loader.go b/api/tech/helm/loader/loader.go similarity index 86% rename from pkg/helm/loader/loader.go rename to api/tech/helm/loader/loader.go index c6304e4dd..487595f29 100644 --- a/pkg/helm/loader/loader.go +++ b/api/tech/helm/loader/loader.go @@ -6,11 +6,11 @@ import ( "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/blobaccess/file" - "github.com/open-component-model/ocm/pkg/helm" - "github.com/open-component-model/ocm/pkg/iotools" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/tech/helm" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/file" + "ocm.software/ocm/api/utils/iotools" ) type Loader interface { diff --git a/api/tech/helm/options.go b/api/tech/helm/options.go new file mode 100644 index 000000000..d5b05bfc9 --- /dev/null +++ b/api/tech/helm/options.go @@ -0,0 +1,105 @@ +package helm + +import ( + "ocm.software/ocm/api/credentials/builtin/helm/identity" + common "ocm.software/ocm/api/utils/misc" +) + +type Option interface { + apply(dl *chartDownloader) error +} + +//////////////////////////////////////////////////////////////////////////////// + +type credOption struct { + creds common.Properties +} + +func (c *credOption) apply(dl *chartDownloader) error { + if c.creds != nil { + dl.creds = c.creds + } + return nil +} + +func WithCredentials(creds common.Properties) Option { + return &credOption{creds} +} + +//////////////////////////////////////////////////////////////////////////////// + +type authOption struct { + user, password string +} + +func (c *authOption) apply(dl *chartDownloader) error { + if dl.creds == nil { + dl.creds = common.Properties{} + } + dl.creds[identity.ATTR_USERNAME] = c.user + dl.creds[identity.ATTR_PASSWORD] = c.password + return nil +} + +func WithBasicAuth(user, password string) Option { + return &authOption{user, password} +} + +//////////////////////////////////////////////////////////////////////////////// + +type certOption struct { + cert []byte + privkey []byte +} + +func (c *certOption) apply(dl *chartDownloader) error { + if len(c.privkey) != 0 { + if dl.creds == nil { + dl.creds = common.Properties{} + } + dl.creds[identity.ATTR_CERTIFICATE] = string(c.cert) + dl.creds[identity.ATTR_PRIVATE_KEY] = string(c.privkey) + } + return nil +} + +func WithCert(cert []byte, privkey []byte) Option { + return &certOption{cert, privkey} +} + +//////////////////////////////////////////////////////////////////////////////// + +type cacertOption struct { + data []byte +} + +func (c *cacertOption) apply(dl *chartDownloader) error { + if len(c.data) > 0 { + if dl.creds == nil { + dl.creds = common.Properties{} + } + dl.creds[identity.ATTR_CERTIFICATE_AUTHORITY] = string(c.data) + } + return nil +} + +func WithRootCert(data []byte) Option { + return &cacertOption{data} +} + +//////////////////////////////////////////////////////////////////////////////// + +type keyringOption struct { + data []byte +} + +func (c *keyringOption) apply(dl *chartDownloader) error { + if len(c.data) > 0 { + dl.keyring = c.data + } + return nil +} + +func WithKeyring(data []byte) Option { + return &keyringOption{data} +} diff --git a/api/tech/maven/access.go b/api/tech/maven/access.go new file mode 100644 index 000000000..33afb0629 --- /dev/null +++ b/api/tech/maven/access.go @@ -0,0 +1,411 @@ +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/goutils/ioutils" + "github.com/mandelsoft/vfs/pkg/vfs" + "golang.org/x/exp/maps" + "golang.org/x/net/html" + + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/iotools" + "ocm.software/ocm/api/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.ParseRequestURI(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 ioutils.DupReadCloser, 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 + } + reader, err := reader.Dup() + if rerr != nil { + return err + } + 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/api/tech/maven/access_test.go b/api/tech/maven/access_test.go new file mode 100644 index 000000000..4f8635935 --- /dev/null +++ b/api/tech/maven/access_test.go @@ -0,0 +1,132 @@ +package maven_test + +import ( + "crypto" + "io" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + + "github.com/mandelsoft/goutils/optionutils" + + me "ocm.software/ocm/api/tech/maven" + "ocm.software/ocm/api/tech/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/api/tech/maven/coordinates.go similarity index 99% rename from pkg/maven/coordinates.go rename to api/tech/maven/coordinates.go index 2213cd836..921642aae 100644 --- a/pkg/maven/coordinates.go +++ b/api/tech/maven/coordinates.go @@ -13,7 +13,7 @@ import ( "github.com/mandelsoft/goutils/generics" "github.com/mandelsoft/goutils/optionutils" - ocmmime "github.com/open-component-model/ocm/pkg/mime" + ocmmime "ocm.software/ocm/api/utils/mime" ) type CoordinateOption = optionutils.Option[*Coordinates] diff --git a/pkg/maven/coordinates_test.go b/api/tech/maven/coordinates_test.go similarity index 97% rename from pkg/maven/coordinates_test.go rename to api/tech/maven/coordinates_test.go index 796420f35..2e69d575f 100644 --- a/pkg/maven/coordinates_test.go +++ b/api/tech/maven/coordinates_test.go @@ -7,7 +7,7 @@ import ( "github.com/mandelsoft/goutils/optionutils" - me "github.com/open-component-model/ocm/pkg/maven" + me "ocm.software/ocm/api/tech/maven" ) var _ = Describe("Maven Test Environment", func() { diff --git a/api/tech/maven/logging.go b/api/tech/maven/logging.go new file mode 100644 index 000000000..8bbf7f439 --- /dev/null +++ b/api/tech/maven/logging.go @@ -0,0 +1,7 @@ +package maven + +import "ocm.software/ocm/api/utils/logging" + +var REALM = logging.DefineSubRealm("Maven repository", "maven") + +var Log = logging.DynamicLogger(REALM) diff --git a/pkg/maven/maventest/const.go b/api/tech/maven/maventest/const.go similarity index 100% rename from pkg/maven/maventest/const.go rename to api/tech/maven/maventest/const.go diff --git a/api/tech/maven/maventest/testdata.go b/api/tech/maven/maventest/testdata.go new file mode 100644 index 000000000..1edc05f77 --- /dev/null +++ b/api/tech/maven/maventest/testdata.go @@ -0,0 +1,13 @@ +package maventest + +import ( + "ocm.software/ocm/api/helper/env" +) + +func TestData(dest ...string) env.Option { + return env.ProjectTestDataForCaller(dest...) +} + +func ModifiableTestData(dest ...string) env.Option { + return env.ModifiableProjectTestDataForCaller(dest...) +} diff --git a/pkg/maven/maventest/testdata/.m2/fail/test/repository/42/repository-42.pom b/api/tech/maven/maventest/testdata/.m2/fail/test/repository/42/repository-42.pom similarity index 100% rename from pkg/maven/maventest/testdata/.m2/fail/test/repository/42/repository-42.pom rename to api/tech/maven/maventest/testdata/.m2/fail/test/repository/42/repository-42.pom diff --git a/pkg/maven/maventest/testdata/.m2/fail/test/repository/42/repository-42.pom.sha1 b/api/tech/maven/maventest/testdata/.m2/fail/test/repository/42/repository-42.pom.sha1 similarity index 100% rename from pkg/maven/maventest/testdata/.m2/fail/test/repository/42/repository-42.pom.sha1 rename to api/tech/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/api/tech/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-random-content.json similarity index 100% rename from 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 rename to api/tech/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-random-content.json 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/api/tech/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 similarity index 100% rename from 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 rename to api/tech/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 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/api/tech/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-random-content.txt similarity index 100% rename from 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 rename to api/tech/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-random-content.txt 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/api/tech/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 similarity index 100% rename from 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 rename to api/tech/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 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/api/tech/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-sources.jar similarity index 100% rename from pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-sources.jar rename to api/tech/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-sources.jar 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.sha1 b/api/tech/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-sources.jar.sha1 similarity index 100% rename from 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 rename to api/tech/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0-sources.jar.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.jar b/api/tech/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0.jar similarity index 100% rename from pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0.jar rename to api/tech/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0.jar 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.jar.sha1 b/api/tech/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0.jar.sha1 similarity index 100% rename from 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 to api/tech/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0.jar.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.pom b/api/tech/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0.pom similarity index 100% rename from pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0.pom rename to api/tech/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0.pom 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.pom.sha1 b/api/tech/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0.pom.sha1 similarity index 100% rename from pkg/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0.pom.sha1 rename to api/tech/maven/maventest/testdata/.m2/repository/com/sap/cloud/sdk/sdk-modules-bom/5.7.0/sdk-modules-bom-5.7.0.pom.sha1 diff --git a/pkg/maven/suite_test.go b/api/tech/maven/suite_test.go similarity index 100% rename from pkg/maven/suite_test.go rename to api/tech/maven/suite_test.go diff --git a/pkg/maven/utils.go b/api/tech/maven/utils.go similarity index 100% rename from pkg/maven/utils.go rename to api/tech/maven/utils.go diff --git a/pkg/npm/login.go b/api/tech/npm/login.go similarity index 95% rename from pkg/npm/login.go rename to api/tech/npm/login.go index 6eaa94b2e..cf1e8d05c 100644 --- a/pkg/npm/login.go +++ b/api/tech/npm/login.go @@ -9,9 +9,9 @@ import ( "net/http" "net/url" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/npm/identity" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/logging" + "ocm.software/ocm/api/credentials/builtin/npm/identity" + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/utils/logging" ) var REALM = identity.REALM diff --git a/pkg/npm/structs.go b/api/tech/npm/structs.go similarity index 100% rename from pkg/npm/structs.go rename to api/tech/npm/structs.go diff --git a/pkg/signing/cert.go b/api/tech/signing/cert.go similarity index 95% rename from pkg/signing/cert.go rename to api/tech/signing/cert.go index ae82b474d..35e83baed 100644 --- a/pkg/signing/cert.go +++ b/api/tech/signing/cert.go @@ -6,7 +6,7 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/signing/signutils" + "ocm.software/ocm/api/tech/signing/signutils" ) func VerifyCert(intermediate signutils.GenericCertificateChain, root signutils.GenericCertificatePool, name string, cert *x509.Certificate) error { diff --git a/pkg/signing/cert_test.go b/api/tech/signing/cert_test.go similarity index 93% rename from pkg/signing/cert_test.go rename to api/tech/signing/cert_test.go index 38110f7fb..ba7e8f351 100644 --- a/pkg/signing/cert_test.go +++ b/api/tech/signing/cert_test.go @@ -8,9 +8,9 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" - "github.com/open-component-model/ocm/pkg/signing/signutils" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/api/tech/signing/signutils" ) // CreateCertificate creates a pem encoded certificate. diff --git a/api/tech/signing/deprecated.go b/api/tech/signing/deprecated.go new file mode 100644 index 000000000..63a9cb069 --- /dev/null +++ b/api/tech/signing/deprecated.go @@ -0,0 +1,53 @@ +package signing + +import ( + "crypto/x509" + "crypto/x509/pkix" + "time" + + parse "github.com/mandelsoft/spiff/dynaml/x509" + + "ocm.software/ocm/api/tech/signing/signutils" +) + +// Deprecated: use signutils.GetCertificate. +func GetCertificate(in interface{}) (*x509.Certificate, error) { + c, _, err := signutils.GetCertificate(in, false) + return c, err +} + +// Deprecated: use signutils.ParsePublicKey. +func ParsePublicKey(data []byte) (interface{}, error) { + return parse.ParsePublicKey(string(data)) +} + +// Deprecated: use signutils.ParsePrivateKey. +func ParsePrivateKey(data []byte) (interface{}, error) { + return parse.ParsePrivateKey(string(data)) +} + +// Deprecated: use signutils.SystemCertPool. +func BaseRootPool() (*x509.CertPool, error) { + return signutils.SystemCertPool() +} + +// Deprecated: use signutils.CreateCertificate. +func CreateCertificate(subject pkix.Name, validFrom *time.Time, + validity time.Duration, pub interface{}, + ca *x509.Certificate, priv interface{}, isCA bool, names ...string, +) ([]byte, error) { + spec := &signutils.Specification{ + RootCAs: ca, + IsCA: isCA, + PublicKey: pub, + CAPrivateKey: priv, + CAChain: ca, + Subject: subject, + Usages: signutils.Usages{x509.ExtKeyUsageCodeSigning}, + Validity: validity, + NotBefore: validFrom, + Hosts: names, + } + _, data, err := signutils.CreateCertificate(spec) + return data, err +} diff --git a/api/tech/signing/encrypt.go b/api/tech/signing/encrypt.go new file mode 100644 index 000000000..66b982594 --- /dev/null +++ b/api/tech/signing/encrypt.go @@ -0,0 +1,59 @@ +package signing + +import ( + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/utils/encrypt" +) + +const DECRYPTION_PREFIX = "decrypt:" + +const KIND_DECRYPTION_KEY = "decryption key" + +func DecryptionKeyName(name string) string { + return DECRYPTION_PREFIX + name +} + +func ResolvePrivateKey(reg KeyRegistryFuncs, name string) (interface{}, error) { + key := reg.GetPrivateKey(name) + if key == nil { + return nil, nil + } + + data, ok := key.([]byte) + if !ok { + if str, ok := key.(string); ok { + data = []byte(str) + } + } + if data == nil { + return key, nil + } + + data, algo := encrypt.GetEncyptedData(data) + if data == nil { + return key, nil + } + + encryptionKey, err := ResolvePrivateKey(reg, DecryptionKeyName(name)) + if err != nil { + return nil, err + } + + if encryptionKey == nil { + return nil, errors.ErrNotFound(KIND_DECRYPTION_KEY, DecryptionKeyName(name)) + } + var keyData []byte + if raw, ok := encryptionKey.([]byte); ok { + keyData, err = encrypt.KeyFromPem(raw) + if err != nil { + keyData = raw + } + } else { + return nil, errors.ErrInvalid(KIND_DECRYPTION_KEY, DecryptionKeyName(name)) + } + if err := algo.CheckKey(keyData); err != nil { + return nil, err + } + return encrypt.Decrypt(keyData, data) +} diff --git a/api/tech/signing/handlers/init.go b/api/tech/signing/handlers/init.go new file mode 100644 index 000000000..e00d5fc85 --- /dev/null +++ b/api/tech/signing/handlers/init.go @@ -0,0 +1,10 @@ +package handlers + +import ( + _ "github.com/sigstore/cosign/v2/pkg/providers/all" + _ "ocm.software/ocm/api/tech/signing/handlers/rsa" + _ "ocm.software/ocm/api/tech/signing/handlers/rsa-pss" + _ "ocm.software/ocm/api/tech/signing/handlers/rsa-pss-signingservice" + _ "ocm.software/ocm/api/tech/signing/handlers/rsa-signingservice" + _ "ocm.software/ocm/api/tech/signing/handlers/sigstore" +) diff --git a/api/tech/signing/handlers/rsa-pss-signingservice/handler.go b/api/tech/signing/handlers/rsa-pss-signingservice/handler.go new file mode 100644 index 000000000..a08f2b243 --- /dev/null +++ b/api/tech/signing/handlers/rsa-pss-signingservice/handler.go @@ -0,0 +1,30 @@ +package rsa_pss_signingservice + +import ( + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/handlers/rsa-pss" + rsa_signingservice "ocm.software/ocm/api/tech/signing/handlers/rsa-signingservice" +) + +// Algorithm defines the type for the RSA PKCS #1 v1.5 signature algorithm. +const ( + Algorithm = rsa_pss.Algorithm + Name = "rsapss-signingservice" +) + +// SignaturePEMBlockAlgorithmHeader defines the header in a signature pem block where the signature algorithm is defined. +const SignaturePEMBlockAlgorithmHeader = rsa_signingservice.SignaturePEMBlockAlgorithmHeader + +func init() { + signing.DefaultHandlerRegistry().RegisterSigner(Name, NewHandler()) +} + +func NewHandler() signing.Signer { + return rsa_signingservice.NewHandlerFor(Algorithm) +} + +type Key = rsa_signingservice.Key + +func PrivateKey(k interface{}) (*Key, error) { + return rsa_signingservice.PrivateKey(k) +} diff --git a/api/tech/signing/handlers/rsa-pss/handler.go b/api/tech/signing/handlers/rsa-pss/handler.go new file mode 100644 index 000000000..2717af187 --- /dev/null +++ b/api/tech/signing/handlers/rsa-pss/handler.go @@ -0,0 +1,43 @@ +package rsa_pss + +import ( + "crypto" + "crypto/rsa" + "io" + + "ocm.software/ocm/api/tech/signing" + rsahandler "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/api/tech/signing/signutils" +) + +// Algorithm defines the type for the RSA PKCS #1 v1.5 signature algorithm. +const Algorithm = "RSASSA-PSS" + +// MediaType defines the media type for a plain RSA-PSS signature. +const MediaType = "application/vnd.ocm.signature.rsa.pss" + +// MediaTypePEM is used if the signature contains the public key certificate chain. +const MediaTypePEM = signutils.MediaTypePEM + +func init() { + signing.DefaultHandlerRegistry().RegisterSigner(Algorithm, NewHandler()) +} + +func NewHandler() signing.SignatureHandler { + return rsahandler.NewHandlerFor(RSASSA_PSS) +} + +var RSASSA_PSS = &rsahandler.Method{ + Algorithm: Algorithm, + MediaType: MediaType, + Sign: sign, + Verify: verify, +} + +func sign(rand io.Reader, priv *rsa.PrivateKey, hash crypto.Hash, digest []byte) ([]byte, error) { + return rsa.SignPSS(rand, priv, hash, digest, nil) +} + +func verify(pub *rsa.PublicKey, hash crypto.Hash, digest []byte, sig []byte) error { + return rsa.VerifyPSS(pub, hash, digest, sig, nil) +} diff --git a/pkg/signing/handlers/rsa-signingservice/README.md b/api/tech/signing/handlers/rsa-signingservice/README.md similarity index 100% rename from pkg/signing/handlers/rsa-signingservice/README.md rename to api/tech/signing/handlers/rsa-signingservice/README.md diff --git a/pkg/signing/handlers/rsa-signingservice/client.go b/api/tech/signing/handlers/rsa-signingservice/client.go similarity index 95% rename from pkg/signing/handlers/rsa-signingservice/client.go rename to api/tech/signing/handlers/rsa-signingservice/client.go index 025447a0a..7d05fc4bf 100644 --- a/pkg/signing/handlers/rsa-signingservice/client.go +++ b/api/tech/signing/handlers/rsa-signingservice/client.go @@ -15,10 +15,10 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/identity/hostpath" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/signutils" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/identity/hostpath" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/signutils" ) const ( diff --git a/api/tech/signing/handlers/rsa-signingservice/credentials.go b/api/tech/signing/handlers/rsa-signingservice/credentials.go new file mode 100644 index 000000000..b1a99c854 --- /dev/null +++ b/api/tech/signing/handlers/rsa-signingservice/credentials.go @@ -0,0 +1,57 @@ +// Copyright 2022 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the LICENSE file +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rsa_signingservice + +import ( + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/credentials/identity/hostpath" + "ocm.software/ocm/api/utils/listformat" +) + +const ( + CONSUMER_TYPE = "Signingserver.gardener.cloud" + + ID_HOSTNAME = hostpath.ID_HOSTNAME + ID_PORT = hostpath.ID_PORT + ID_PATHPREFIX = hostpath.ID_PATHPREFIX + ID_SCHEME = hostpath.ID_SCHEME + + ATTR_CLIENT_CERT = "clientCert" + ATTR_PRIVATE_KEY = "privateKey" + ATTR_CA_CERTS = "caCerts" +) + +var identityMatcher = hostpath.IdentityMatcher(CONSUMER_TYPE) + +func init() { + attrs := listformat.FormatListElements("", listformat.StringElementDescriptionList{ + ATTR_CLIENT_CERT, "client certificate for authentication", + ATTR_PRIVATE_KEY, "private key for client certificate", + ATTR_CA_CERTS, "root certificate for signing server", + }) + ids := listformat.FormatListElements("", listformat.StringElementDescriptionList{ + ID_HOSTNAME, "signing server host", + ID_SCHEME, "(optional) URL scheme", + ID_PORT, "(optional) server port", + ID_PATHPREFIX, "path prefix for the server URL", + }) + cpi.RegisterStandardIdentity(CONSUMER_TYPE, identityMatcher, + `signing service credential matcher + +This matcher matches credentials for a Signing Service instance. +It uses the following identity attributes: +`+ids, + attrs) +} diff --git a/api/tech/signing/handlers/rsa-signingservice/handler.go b/api/tech/signing/handlers/rsa-signingservice/handler.go new file mode 100644 index 000000000..ae2e1870e --- /dev/null +++ b/api/tech/signing/handlers/rsa-signingservice/handler.go @@ -0,0 +1,75 @@ +package rsa_signingservice + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/api/utils/runtime" +) + +// Algorithm defines the type for the RSA PKCS #1 v1.5 signature algorithm. +const ( + Algorithm = rsa.Algorithm + Name = "rsa-signingservice" +) + +type Key struct { + URL string `json:"url"` +} + +// SignaturePEMBlockAlgorithmHeader defines the header in a signature pem block where the signature algorithm is defined. +const SignaturePEMBlockAlgorithmHeader = "Algorithm" + +func init() { + signing.DefaultHandlerRegistry().RegisterSigner(Name, NewHandler()) +} + +// Handler is a signatures.Signer compatible struct to sign with RSASSA-PKCS1-V1_5. +// using a signature service. +type Handler struct { + algo string +} + +func NewHandlerFor(algo string) signing.Signer { + return &Handler{algo} +} + +func NewHandler() signing.Signer { + return &Handler{Algorithm} +} + +func (h *Handler) Algorithm() string { + return h.algo +} + +func (h *Handler) Sign(cctx credentials.Context, digest string, sctx signing.SigningContext) (signature *signing.Signature, err error) { + privateKey, err := PrivateKey(sctx.GetPrivateKey()) + if err != nil { + return nil, errors.Wrapf(err, "invalid signing server access configuration") + } + server, err := NewSigningClient(privateKey.URL) + if err != nil { + return nil, err + } + return server.Sign(cctx, h.Algorithm(), sctx.GetHash(), digest, sctx) +} + +func PrivateKey(k interface{}) (*Key, error) { + switch t := k.(type) { + case *Key: + return t, nil + case []byte: + key := &Key{} + err := runtime.DefaultYAMLEncoding.Unmarshal(t, key) + if err != nil { + return nil, err + } + return key, err + default: + return nil, fmt.Errorf("unknown key specification %T", k) + } +} diff --git a/pkg/signing/handlers/rsa/certhelper.go b/api/tech/signing/handlers/rsa/certhelper.go similarity index 92% rename from pkg/signing/handlers/rsa/certhelper.go rename to api/tech/signing/handlers/rsa/certhelper.go index 480b37fac..d856b1674 100644 --- a/pkg/signing/handlers/rsa/certhelper.go +++ b/api/tech/signing/handlers/rsa/certhelper.go @@ -5,8 +5,8 @@ import ( "crypto/x509/pkix" "time" - "github.com/open-component-model/ocm/pkg/signing/signutils" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/tech/signing/signutils" + "ocm.software/ocm/api/utils" ) func CreateRootCertificate(sub *pkix.Name, validity time.Duration) (*x509.Certificate, *PrivateKey, error) { diff --git a/pkg/signing/handlers/rsa/format.go b/api/tech/signing/handlers/rsa/format.go similarity index 100% rename from pkg/signing/handlers/rsa/format.go rename to api/tech/signing/handlers/rsa/format.go diff --git a/api/tech/signing/handlers/rsa/handler.go b/api/tech/signing/handlers/rsa/handler.go new file mode 100644 index 000000000..09a1ac947 --- /dev/null +++ b/api/tech/signing/handlers/rsa/handler.go @@ -0,0 +1,182 @@ +package rsa + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "encoding/hex" + "fmt" + "io" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/signutils" +) + +// Algorithm defines the type for the RSA PKCS #1 v1.5 signature algorithm. +const Algorithm = "RSASSA-PKCS1-V1_5" + +// MediaType defines the media type for a plain RSA signature. +const MediaType = "application/vnd.ocm.signature.rsa" + +// MediaTypePEM is used if the signature contains the public key certificate chain. +const MediaTypePEM = signutils.MediaTypePEM + +func init() { + signing.DefaultHandlerRegistry().RegisterSigner(Algorithm, NewHandler()) +} + +type ( + PrivateKey = rsa.PrivateKey + PublicKey = rsa.PublicKey +) + +type Method struct { + Algorithm string + MediaType string + Sign func(random io.Reader, priv *PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) + Verify func(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte) error +} + +// Handler is a signatures.Signer compatible struct to sign with RSASSA-PKCS1-V1_5. +// and a signatures.Verifier compatible struct to verify RSASSA-PKCS1-V1_5 signatures. +type Handler struct { + method *Method +} + +func NewHandler() signing.SignatureHandler { + return NewHandlerFor(PKCS1v15) +} + +func NewHandlerFor(m *Method) signing.SignatureHandler { + return &Handler{method: m} +} + +var PKCS1v15 = &Method{ + Algorithm: Algorithm, + MediaType: MediaType, + Sign: rsa.SignPKCS1v15, + Verify: rsa.VerifyPKCS1v15, +} + +func (h *Handler) Algorithm() string { + return h.method.Algorithm +} + +func (h *Handler) Sign(cctx credentials.Context, digest string, sctx signing.SigningContext) (signature *signing.Signature, err error) { + privateKey, err := GetPrivateKey(sctx.GetPrivateKey()) + if err != nil { + return nil, errors.Wrapf(err, "invalid rsa private key") + } + decodedHash, err := hex.DecodeString(digest) + if err != nil { + return nil, fmt.Errorf("failed decoding hash to bytes") + } + sig, err := h.method.Sign(rand.Reader, privateKey, sctx.GetHash(), decodedHash) + if err != nil { + return nil, fmt.Errorf("failed signing hash, %w", err) + } + + media := h.method.MediaType + value := hex.EncodeToString(sig) + + var iss string + pub := sctx.GetPublicKey() + if pub != nil { + var pubKey signutils.GenericPublicKey + certs, err := signutils.GetCertificateChain(pub, false) + if err == nil && len(certs) > 0 { + pubKey, _, err = GetPublicKey(certs[0].PublicKey) + if err != nil { + return nil, errors.ErrInvalidWrap(err, "public key") + } + err = signutils.VerifyCertificate(certs[0], certs[1:], sctx.GetRootCerts(), sctx.GetIssuer()) + if err != nil { + return nil, errors.Wrapf(err, "public key certificate") + } + media = MediaTypePEM + value = string(signutils.SignatureBytesToPem(h.Algorithm(), sig, certs...)) + iss = certs[0].Subject.String() + } else { + pubKey, _, err = GetPublicKey(pub) + if err != nil { + return nil, errors.ErrInvalidWrap(err, "public key") + } + } + if !privateKey.PublicKey.Equal(pubKey) { + return nil, fmt.Errorf("invalid public key for private key") + } + } + + return &signing.Signature{ + Value: value, + MediaType: media, + Algorithm: h.Algorithm(), + Issuer: iss, + }, nil +} + +// Verify checks the signature, returns an error on verification failure. +func (h *Handler) Verify(digest string, signature *signing.Signature, sctx signing.SigningContext) (err error) { + var signatureBytes []byte + + publicKey, name, err := GetPublicKey(sctx.GetPublicKey()) + if err != nil { + return fmt.Errorf("failed to get public key: %w", err) + } + + switch signature.MediaType { + case MediaType: + signatureBytes, err = hex.DecodeString(signature.Value) + if err != nil { + return fmt.Errorf("unable to get signature value: failed decoding hash %s: %w", digest, err) + } + case signutils.MediaTypePEM: + sig, algo, _, err := signutils.GetSignatureFromPem([]byte(signature.Value)) + if err != nil { + return fmt.Errorf("unable to get signature from pem: %w", err) + } + if algo != "" && algo != h.Algorithm() { + return errors.ErrInvalid(signutils.KIND_SIGN_ALGORITHM, algo) + } + signatureBytes = sig + default: + return fmt.Errorf("invalid signature mediaType %s", signature.MediaType) + } + + decodedHash, err := hex.DecodeString(digest) + if err != nil { + return fmt.Errorf("failed decoding hash %s: %w", digest, err) + } + + if name != nil { + if signature.Issuer != "" { + iss, err := signutils.ParseDN(signature.Issuer) + if err != nil { + return errors.Wrapf(err, "signature issuer") + } + if signutils.MatchDN(*iss, *name) != nil { + return fmt.Errorf("issuer %s does not match %s", signature.Issuer, name) + } + } + } + if err := h.method.Verify(publicKey, sctx.GetHash(), decodedHash, signatureBytes); err != nil { + return fmt.Errorf("signature verification failed, %w", err) + } + + return nil +} + +func (_ Handler) CreateKeyPair() (priv signutils.GenericPrivateKey, pub signutils.GenericPublicKey, err error) { + return CreateKeyPair() +} + +func CreateKeyPair() (priv signutils.GenericPrivateKey, pub signutils.GenericPublicKey, err error) { + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, nil, err + } + return key, &key.PublicKey, nil +} diff --git a/api/tech/signing/handlers/sigstore/attr/attr.go b/api/tech/signing/handlers/sigstore/attr/attr.go new file mode 100644 index 000000000..98cd9bb31 --- /dev/null +++ b/api/tech/signing/handlers/sigstore/attr/attr.go @@ -0,0 +1,101 @@ +package attr + +import ( + "errors" + "fmt" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ATTR_KEY = "ocm.software/signing/sigstore" + ATTR_SHORT = "sigstore" +) + +var defaultAttr = Attribute{ + FulcioURL: "https://v1.fulcio.sigstore.dev", + RekorURL: "https://rekor.sigstore.dev", + OIDCIssuer: "https://oauth2.sigstore.dev/auth", + OIDCClientID: "sigstore", +} + +func init() { + datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) +} + +// AttributeType represents the attribute functionality. +type AttributeType struct{} + +// Attribute represents the attribute data. +type Attribute struct { + FulcioURL string `json:"fulcioURL"` + RekorURL string `json:"rekorURL"` + OIDCIssuer string `json:"OIDCIssuer"` + OIDCClientID string `json:"OIDCClientID"` +} + +// Name returns the attribute name. +func (a AttributeType) Name() string { + return ATTR_KEY +} + +// Description returns a description of the attribute. +func (a AttributeType) Description() string { + return ` +*sigstore config* Configuration to use for sigstore based signing. +The following fields are used. +- *fulcioURL* *string* default is https://v1.fulcio.sigstore.dev +- *rekorURL* *string* default is https://rekor.sigstore.dev +- *OIDCIssuer* *string* default is https://oauth2.sigstore.dev/auth +- *OIDCClientID* *string* default is sigstore +` +} + +// Encode marshals an attribute. +func (AttributeType) Encode(v interface{}, marshaler runtime.Marshaler) ([]byte, error) { + if marshaler == nil { + marshaler = runtime.DefaultJSONEncoding + } + + result, ok := v.(*Attribute) + if !ok { + return nil, errors.New("sigstore attribute required") + } + + return marshaler.Marshal(result) +} + +// Decode unmarshals an attribute. +func (a AttributeType) Decode(data []byte, unmarshaler runtime.Unmarshaler) (interface{}, error) { + if unmarshaler == nil { + unmarshaler = runtime.DefaultJSONEncoding + } + + attr := &Attribute{} + err := unmarshaler.Unmarshal(data, attr) + if err != nil { + return nil, fmt.Errorf("invalud attribute value for %s: %w", ATTR_KEY, err) + } + + return attr, nil +} + +// Get returns the attributes. +func Get(ctx datacontext.Context) *Attribute { + v := ctx.GetAttributes().GetAttribute(ATTR_KEY) + if v == nil { + return &defaultAttr + } + a, ok := v.(*Attribute) + if !ok { + return &defaultAttr + } + return a +} + +// Set sets the attributes. +func Set(ctx datacontext.Context, a *Attribute) error { + attrs := ctx.GetAttributes() + return attrs.SetAttribute(ATTR_KEY, a) +} diff --git a/api/tech/signing/handlers/sigstore/handler.go b/api/tech/signing/handlers/sigstore/handler.go new file mode 100644 index 000000000..15f7b42bd --- /dev/null +++ b/api/tech/signing/handlers/sigstore/handler.go @@ -0,0 +1,297 @@ +package sigstore + +import ( + "context" + "crypto" + "crypto/ecdsa" + "crypto/x509" + "encoding/base64" + "encoding/hex" + "encoding/json" + "encoding/pem" + "fmt" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/mandelsoft/goutils/errors" + "github.com/sigstore/cosign/v2/cmd/cosign/cli/fulcio" + "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v2/pkg/cosign" + "github.com/sigstore/rekor/pkg/client" + "github.com/sigstore/rekor/pkg/generated/client/entries" + "github.com/sigstore/rekor/pkg/generated/models" + hashedrekord_v001 "github.com/sigstore/rekor/pkg/types/hashedrekord/v0.0.1" + "github.com/sigstore/rekor/pkg/verify" + "github.com/sigstore/sigstore/pkg/signature" + signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/handlers/sigstore/attr" +) + +// Algorithm defines the type for the RSA PKCS #1 v1.5 signature algorithm. +const Algorithm = "sigstore" + +// MediaType defines the media type for a plain RSA signature. +const MediaType = "application/vnd.ocm.signature.sigstore" + +// SignaturePEMBlockAlgorithmHeader defines the header in a signature pem block where the signature algorithm is defined. +const SignaturePEMBlockAlgorithmHeader = "Algorithm" + +func init() { + signing.DefaultHandlerRegistry().RegisterSigner(Algorithm, Handler{}) +} + +// Handler is a signatures.Signer compatible struct to sign using sigstore +// and a signatures.Verifier compatible struct to verify using sigstore. +type Handler struct{} + +// Algorithm specifies the name of the signing algorithm. +func (h Handler) Algorithm() string { + return Algorithm +} + +// Sign implements the signing functionality. +func (h Handler) Sign(cctx credentials.Context, digest string, sctx signing.SigningContext) (*signing.Signature, error) { + hash := sctx.GetHash() + // exit immediately if hash alg is not SHA-256, rekor doesn't currently support other hash functions + if hash != crypto.SHA256 { + return nil, fmt.Errorf("cannot sign using sigstore. rekor only supports SHA-256 digests: %s provided", hash.String()) + } + + ctx := context.Background() + + // generate a private key + priv, err := cosign.GeneratePrivateKey() + if err != nil { + return nil, fmt.Errorf("error generating keypair: %w", err) + } + + // create an ECDSA signer + signer, err := signature.LoadECDSASignerVerifier(priv, hash) + if err != nil { + return nil, fmt.Errorf("error loading sigstore signer: %w", err) + } + + // get the attributes for the sigstore signer + cfg := attr.Get(cctx) + + // create a fulcio signing client + fs, err := fulcio.NewSigner(ctx, options.KeyOpts{ + FulcioURL: cfg.FulcioURL, + OIDCIssuer: cfg.OIDCIssuer, + OIDCClientID: cfg.OIDCClientID, + SkipConfirmation: true, + }, signer) + if err != nil { + return nil, fmt.Errorf("failed to create signer: %w", err) + } + + // decode the digest string + rawDigest, err := hex.DecodeString(digest) + if err != nil { + return nil, fmt.Errorf("failed to decode digest: %w", err) + } + + // sign the existing digest + sig, err := fs.SignMessage(nil, + signatureoptions.WithDigest(rawDigest), + signatureoptions.WithContext(ctx)) + if err != nil { + return nil, fmt.Errorf("failed to sign: %w", err) + } + + // get the public key for certificate transparency log + pubKeys, err := cosign.GetCTLogPubs(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get cosign CT Log Public Keys: %w", err) + } + + // verify the signed certificate timestamp + if err := cosign.VerifySCT(ctx, fs.Cert, fs.Chain, fs.SCT, pubKeys); err != nil { + return nil, fmt.Errorf("failed to verify signed certifcate timestamp: %w", err) + } + + // get the public key from the signing key pair + pub, err := fs.PublicKey() + if err != nil { + return nil, fmt.Errorf("failed to get public key for signing: %w", err) + } + + // marshal the public key bytes + publicKeyBytes, err := x509.MarshalPKIXPublicKey(pub) + if err != nil { + return nil, fmt.Errorf("failed to marshal public key for signing: %w", err) + } + + // encode the public key to pem format + publicKey := pem.EncodeToMemory(&pem.Block{ + Bytes: publicKeyBytes, + Type: "PUBLIC KEY", + }) + + // init the rekor client + rekorClient, err := client.GetRekorClient(cfg.RekorURL) + if err != nil { + return nil, fmt.Errorf("failed to create rekor client: %w", err) + } + + // create a rekor hashed entry + hashedEntry := prepareRekorEntry(digest, sig, publicKey) + + // valiate the rekor entry before submission + if _, err := hashedEntry.Canonicalize(ctx); err != nil { + return nil, fmt.Errorf("rekor entry is not valid: %w", err) + } + + // prepare the entry for submission + entry := &models.Hashedrekord{ + APIVersion: swag.String(hashedEntry.APIVersion()), + Spec: hashedEntry.HashedRekordObj, + } + + // prepare the create entry request parameters + params := entries.NewCreateLogEntryParams(). + WithContext(ctx). + WithProposedEntry(entry) + + // submit the create entry request + resp, err := rekorClient.Entries.CreateLogEntry(params) + if err != nil { + return nil, fmt.Errorf("failed to create rekor entry: %w", err) + } + + // extract the payload from the rekor response + data, err := json.Marshal(resp.GetPayload()) + if err != nil { + return nil, fmt.Errorf("failed to marshal rekor response: %w", err) + } + + // store the rekor response in the signature value + return &signing.Signature{ + Value: base64.StdEncoding.EncodeToString(data), + MediaType: MediaType, + Algorithm: Algorithm, + Issuer: "", + }, nil +} + +// Verify checks the signature, returns an error on verification failure. +func (h Handler) Verify(digest string, sig *signing.Signature, sctx signing.SigningContext) (err error) { + ctx := context.Background() + + data, err := base64.StdEncoding.DecodeString(sig.Value) + if err != nil { + return fmt.Errorf("failed to decode signature: %w", err) + } + + var entries models.LogEntry + if err := json.Unmarshal(data, &entries); err != nil { + return fmt.Errorf("failed to unmarshal rekor log entry from signature: %w", err) + } + + rawDigest, err := hex.DecodeString(digest) + if err != nil { + return fmt.Errorf("failed to decode digest: %w", err) + } + + for _, entry := range entries { + verifier, err := loadVerifier(ctx) + if err != nil { + return fmt.Errorf("failed to load rekor verifier: %w", err) + } + + body, err := base64.StdEncoding.DecodeString(entry.Body.(string)) + if err != nil { + return fmt.Errorf("failed to decode rekor entry body: %w", err) + } + + rekorEntry := &models.Hashedrekord{} + if err := json.Unmarshal(body, rekorEntry); err != nil { + return fmt.Errorf("failed to unmarshal rekor entry body: %w", err) + } + + if err := rekorEntry.Validate(strfmt.Default); err != nil { + return fmt.Errorf("failed to validate rekor entry: %w", err) + } + + rekorEntrySpec := rekorEntry.Spec.(map[string]any) + rekorHashValue := rekorEntrySpec["data"].(map[string]any)["hash"].(map[string]any)["value"] + + // ensure digest matches + if rekorHashValue != digest { + return errors.New("rekor hash doesn't match provided digest") + } + + // get the signature + rekorSignatureRaw := rekorEntrySpec["signature"].(map[string]any)["content"] + rekorSignature, err := base64.StdEncoding.DecodeString(rekorSignatureRaw.(string)) + if err != nil { + return fmt.Errorf("failed to decode rekor signature: %w", err) + } + + // get the public key from the signature + rekorPublicKeyContent := rekorEntrySpec["signature"].(map[string]any)["publicKey"].(map[string]any)["content"] + rekorPublicKeyRaw, err := base64.StdEncoding.DecodeString(rekorPublicKeyContent.(string)) + if err != nil { + return fmt.Errorf("failed to decode rekor public key: %w", err) + } + + block, _ := pem.Decode(rekorPublicKeyRaw) + if block == nil { + return fmt.Errorf("failed to decode public key: %w", err) + } + + rekorPublicKey, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return fmt.Errorf("failed to parse public key: %w", err) + } + + // verify signature + if err := ecdsa.VerifyASN1(rekorPublicKey.(*ecdsa.PublicKey), rawDigest, rekorSignature); !err { + return errors.New("could not verify signature using public key") + } + + // verify log entry + if err := verify.VerifyLogEntry(ctx, &entry, verifier); err != nil { + return fmt.Errorf("failed to verify log entry: %w", err) + } + } + return nil +} + +func loadVerifier(ctx context.Context) (signature.Verifier, error) { + publicKeys, err := cosign.GetRekorPubs(ctx) + if err != nil { + return nil, err + } + + for _, pubKey := range publicKeys.Keys { + return signature.LoadVerifier(pubKey.PubKey, crypto.SHA256) + } + + return nil, nil +} + +// based on: https://github.com/sigstore/cosign/blob/ff648d5fb4ed6d0d1c16eaaceff970411fa969e3/pkg/cosign/tlog.go#L233 +func prepareRekorEntry(digest string, sig, publicKey []byte) hashedrekord_v001.V001Entry { + // TODO: this should match the provided hash digest algorithm but + // rekor only supports SHA256 right now + return hashedrekord_v001.V001Entry{ + HashedRekordObj: models.HashedrekordV001Schema{ + Data: &models.HashedrekordV001SchemaData{ + Hash: &models.HashedrekordV001SchemaDataHash{ + Algorithm: swag.String(models.HashedrekordV001SchemaDataHashAlgorithmSha256), + Value: swag.String(digest), + }, + }, + Signature: &models.HashedrekordV001SchemaSignature{ + Content: strfmt.Base64(sig), + PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{ + Content: strfmt.Base64(publicKey), + }, + }, + }, + } +} diff --git a/pkg/signing/hasher/hashfuncs.go b/api/tech/signing/hasher/hashfuncs.go similarity index 100% rename from pkg/signing/hasher/hashfuncs.go rename to api/tech/signing/hasher/hashfuncs.go diff --git a/api/tech/signing/hasher/init.go b/api/tech/signing/hasher/init.go new file mode 100644 index 000000000..adc6bcf65 --- /dev/null +++ b/api/tech/signing/hasher/init.go @@ -0,0 +1,7 @@ +package hasher + +import ( + _ "ocm.software/ocm/api/tech/signing/hasher/nodigest" + _ "ocm.software/ocm/api/tech/signing/hasher/sha256" + _ "ocm.software/ocm/api/tech/signing/hasher/sha512" +) diff --git a/api/tech/signing/hasher/nodigest/hasher.go b/api/tech/signing/hasher/nodigest/hasher.go new file mode 100644 index 000000000..73b0f13b3 --- /dev/null +++ b/api/tech/signing/hasher/nodigest/hasher.go @@ -0,0 +1,33 @@ +package nodigest + +import ( + "crypto" + "hash" + + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/tech/signing" +) + +const Algorithm = metav1.NoDigest + +func init() { + signing.DefaultHandlerRegistry().RegisterHasher(Handler{}) +} + +// Handler is a signatures.Hasher compatible struct to hash with sha256. +type Handler struct{} + +var _ signing.Hasher = Handler{} + +func (h Handler) Algorithm() string { + return Algorithm +} + +// Create creates a Hasher instance for sha256. +func (_ Handler) Create() hash.Hash { + return nil +} + +func (_ Handler) Crypto() crypto.Hash { + return 0 +} diff --git a/api/tech/signing/hasher/sha256/hasher.go b/api/tech/signing/hasher/sha256/hasher.go new file mode 100644 index 000000000..16ba64323 --- /dev/null +++ b/api/tech/signing/hasher/sha256/hasher.go @@ -0,0 +1,33 @@ +package sha256 + +import ( + "crypto" + "crypto/sha256" + "hash" + + "ocm.software/ocm/api/tech/signing" +) + +var Algorithm = crypto.SHA256.String() + +func init() { + signing.DefaultHandlerRegistry().RegisterHasher(Handler{}) +} + +// Handler is a signatures.Hasher compatible struct to hash with sha256. +type Handler struct{} + +var _ signing.Hasher = Handler{} + +func (_ Handler) Algorithm() string { + return Algorithm +} + +// Create creates a Hasher instance for no digest. +func (_ Handler) Create() hash.Hash { + return sha256.New() +} + +func (_ Handler) Crypto() crypto.Hash { + return crypto.SHA256 +} diff --git a/api/tech/signing/hasher/sha512/hasher.go b/api/tech/signing/hasher/sha512/hasher.go new file mode 100644 index 000000000..d9e2c92e5 --- /dev/null +++ b/api/tech/signing/hasher/sha512/hasher.go @@ -0,0 +1,33 @@ +package sha512 + +import ( + "crypto" + "crypto/sha512" + "hash" + + "ocm.software/ocm/api/tech/signing" +) + +var Algorithm = crypto.SHA512.String() + +func init() { + signing.DefaultHandlerRegistry().RegisterHasher(Handler{}) +} + +// Handler is a signatures.Hasher compatible struct to hash with sha256. +type Handler struct{} + +var _ signing.Hasher = Handler{} + +func (_ Handler) Algorithm() string { + return Algorithm +} + +// Create creates a Hasher instance for no digest. +func (_ Handler) Create() hash.Hash { + return sha512.New() +} + +func (_ Handler) Crypto() crypto.Hash { + return crypto.SHA512 +} diff --git a/pkg/signing/hashfuncs.go b/api/tech/signing/hashfuncs.go similarity index 100% rename from pkg/signing/hashfuncs.go rename to api/tech/signing/hashfuncs.go diff --git a/api/tech/signing/norm/entry/norm.go b/api/tech/signing/norm/entry/norm.go new file mode 100644 index 000000000..53968a0d9 --- /dev/null +++ b/api/tech/signing/norm/entry/norm.go @@ -0,0 +1,191 @@ +package entry + +import ( + "bytes" + "encoding/json" + "fmt" + "sort" + "strconv" + + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/utils" +) + +var Type = normalization{} + +type normalization struct{} + +func New() signing.Normalization { + return normalization{} +} + +func (_ normalization) NewArray() signing.Normalized { + return &normalized{[]interface{}{}} +} + +func (_ normalization) NewMap() signing.Normalized { + return &normalized{Entries{}} +} + +func (_ normalization) NewValue(v interface{}) signing.Normalized { + return &normalized{v} +} + +func (_ normalization) String() string { + return "entry normalization" +} + +type normalized struct { + value interface{} +} + +func (n *normalized) Value() interface{} { + return n.value +} + +func (n *normalized) IsEmpty() bool { + switch v := n.value.(type) { + case Entries: + return len(v) == 0 + case []interface{}: + return len(v) == 0 + default: + return false + } +} + +func (n *normalized) Append(normalized signing.Normalized) { + n.value = append(n.value.([]interface{}), normalized.Value()) +} + +func (n *normalized) SetField(name string, value signing.Normalized) { + v := n.value.(Entries) + v = append(v, Entry{ + key: name, + value: value.Value(), + }) + // sort the entries based on the key + sort.SliceStable(v, func(i, j int) bool { + return v[i].Key() < v[j].Key() + }) + n.value = v +} + +func (n *normalized) ToString(gap string) string { + return toString(n.value, gap) +} + +func (l *normalized) String() string { + return string(utils.Must(json.Marshal(l.value))) +} + +func (l *normalized) Formatted() string { + return string(utils.Must(json.MarshalIndent(l.value, "", " "))) +} + +func (n *normalized) Marshal(gap string) ([]byte, error) { + byteBuffer := bytes.NewBuffer([]byte{}) + encoder := json.NewEncoder(byteBuffer) + encoder.SetEscapeHTML(false) + encoder.SetIndent("", gap) + + if err := encoder.Encode(n.value); err != nil { + return nil, err + } + + normalizedJson := byteBuffer.Bytes() + + // encoder.Encode appends a newline that we do not want + if normalizedJson[len(normalizedJson)-1] == 10 { + normalizedJson = normalizedJson[:len(normalizedJson)-1] + } + return normalizedJson, nil +} + +// Entry is used to keep exactly one key/value pair. +type Entry struct { + key string + value interface{} +} + +func (e Entry) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]interface{}{ + e.key: e.value, + }) +} + +func NewEntry(key string, value interface{}) Entry { + return Entry{key: key, value: value} +} + +func (e Entry) Get() (string, interface{}) { + return e.key, e.value +} + +func (e Entry) Key() string { + return e.key +} + +func (e Entry) Value() interface{} { + return e.value +} + +func (e Entry) ToString(gap string) string { + return fmt.Sprintf("%s%s: %s", gap, e.Key(), toString(e.Value(), gap)) +} + +type Entries []Entry + +func (l *Entries) Add(key string, value interface{}) { + *l = append(*l, NewEntry(key, value)) +} + +func (l Entries) String() string { + return string(utils.Must(json.Marshal(l))) +} + +func (l Entries) Formatted() string { + return string(utils.Must(json.MarshalIndent(l, "", " "))) +} + +func (l Entries) ToString(gap string) string { + ngap := gap + " " + s := "{" + sep := "" + for _, v := range l { + s = fmt.Sprintf("%s\n%s", s, v.ToString(ngap)) + sep = "\n" + gap + } + s += sep + "}" + return s +} + +func toString(v interface{}, gap string) string { + if v == nil || v == signing.Null { + return "null" + } + switch castIn := v.(type) { + case Entries: + return castIn.ToString(gap) + case []Entry: + return Entries(castIn).ToString(gap) + case Entry: + return castIn.ToString(gap) + case []interface{}: + ngap := gap + " " + s := "[" + sep := "" + for _, v := range castIn { + s = fmt.Sprintf("%s\n%s%s", s, ngap, toString(v, ngap)) + sep = "\n" + gap + } + s += sep + "]" + return s + case string: + return castIn + case bool: + return strconv.FormatBool(castIn) + default: + panic(fmt.Sprintf("unknown type %T in toString. This should not happen", v)) + } +} diff --git a/api/tech/signing/norm/jcs/norm.go b/api/tech/signing/norm/jcs/norm.go new file mode 100644 index 000000000..35c73a873 --- /dev/null +++ b/api/tech/signing/norm/jcs/norm.go @@ -0,0 +1,135 @@ +package jcs + +import ( + "bytes" + "encoding/json" + "fmt" + "strconv" + + "github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer" + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/general" + "github.com/mandelsoft/goutils/maputils" + + "ocm.software/ocm/api/tech/signing" +) + +var Type = normalization{} + +type normalization struct{} + +func New() signing.Normalization { + return normalization{} +} + +func (_ normalization) NewArray() signing.Normalized { + return &normalized{[]interface{}{}} +} + +func (_ normalization) NewMap() signing.Normalized { + return &normalized{map[string]interface{}{}} +} + +func (_ normalization) NewValue(v interface{}) signing.Normalized { + return &normalized{v} +} + +func (_ normalization) String() string { + return "JCS(rfc8785) normalization" +} + +type normalized struct { + value interface{} +} + +func (n *normalized) Value() interface{} { + return n.value +} + +func (n *normalized) IsEmpty() bool { + switch v := n.value.(type) { + case map[string]interface{}: + return len(v) == 0 + case []interface{}: + return len(v) == 0 + default: + return false + } +} + +func (n *normalized) Append(normalized signing.Normalized) { + n.value = append(n.value.([]interface{}), normalized.Value()) +} + +func (n *normalized) SetField(name string, value signing.Normalized) { + v := n.value.(map[string]interface{}) + v[name] = value.Value() +} + +func (n *normalized) ToString(gap string) string { + return toString(n.value, gap) +} + +func (l *normalized) String() string { + return string(general.Must(json.Marshal(l.value))) +} + +func (l *normalized) Formatted() string { + return string(general.Must(json.MarshalIndent(l.value, "", " "))) +} + +func (n *normalized) Marshal(gap string) ([]byte, error) { + byteBuffer := bytes.NewBuffer([]byte{}) + encoder := json.NewEncoder(byteBuffer) + encoder.SetEscapeHTML(false) + encoder.SetIndent("", gap) + + err := encoder.Encode(n.Value()) + if err != nil { + return nil, err + } + if gap != "" { + return byteBuffer.Bytes(), nil + } + data, err := jsoncanonicalizer.Transform(byteBuffer.Bytes()) + if err != nil { + return nil, errors.Wrapf(err, "cannot canonicalize json") + } + return data, nil +} + +func toString(v interface{}, gap string) string { + if v == nil || v == signing.Null { + return "null" + } + switch castIn := v.(type) { + case map[string]interface{}: + ngap := gap + " " + s := "{" + sep := "" + keys := maputils.OrderedKeys(castIn) + for _, n := range keys { + v := castIn[n] + sep = "\n" + gap + s = fmt.Sprintf("%s%s %s: %s", s, sep, n, toString(v, ngap)) + } + s += sep + "}" + return s + case []interface{}: + ngap := gap + " " + s := "[" + sep := "" + for _, v := range castIn { + s = fmt.Sprintf("%s\n%s%s", s, ngap, toString(v, ngap)) + sep = "\n" + gap + } + s += sep + "]" + return s + case string: + return castIn + case bool: + return strconv.FormatBool(castIn) + default: + panic(fmt.Sprintf("unknown type %T in toString. This should not happen", v)) + } +} diff --git a/pkg/signing/normalization.go b/api/tech/signing/normalization.go similarity index 100% rename from pkg/signing/normalization.go rename to api/tech/signing/normalization.go diff --git a/pkg/signing/normalization_test.go b/api/tech/signing/normalization_test.go similarity index 97% rename from pkg/signing/normalization_test.go rename to api/tech/signing/normalization_test.go index 10c513eb6..047ef4df9 100644 --- a/pkg/signing/normalization_test.go +++ b/api/tech/signing/normalization_test.go @@ -8,16 +8,16 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "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/contexts/ocm/compdesc/normalizations/rules" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/norm/entry" - "github.com/open-component-model/ocm/pkg/signing/norm/jcs" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/compdesc/normalizations/rules" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/norm/entry" + "ocm.software/ocm/api/tech/signing/norm/jcs" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/api/utils/runtime" ) var CDExcludes = signing.MapExcludes{ diff --git a/api/tech/signing/registry.go b/api/tech/signing/registry.go new file mode 100644 index 000000000..1480eb05c --- /dev/null +++ b/api/tech/signing/registry.go @@ -0,0 +1,563 @@ +package signing + +import ( + "crypto/x509/pkix" + "sort" + "sync" + + "github.com/mandelsoft/goutils/set" + "github.com/mandelsoft/goutils/sliceutils" + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" + + "ocm.software/ocm/api/tech/signing/signutils" +) + +const DEFAULT_TSA_URL = "http://timestamp.digicert.com" + +type Registry interface { + RegistryFuncs + + Copy() Registry +} + +type RegistryFuncs interface { + HandlerRegistryFuncs + KeyRegistryFuncs + + HandlerRegistryProvider + KeyRegistryProvider + + TSAUrl() string + SetTSAUrl(url string) +} + +type HasherProvider interface { + GetHasher(name string) Hasher +} + +type HasherRegistryFuncs interface { + HasherProvider + + RegisterHasher(hasher Hasher) + HasherNames() []string +} + +type HasherRegistry interface { + HasherRegistryFuncs + + Copy() HasherRegistry +} + +type HasherRegistryProvider interface { + HasherRegistry() HasherRegistry +} + +type SignerRegistryFuncs interface { + RegisterSignatureHandler(handler SignatureHandler) + RegisterSigner(algo string, signer Signer) + RegisterVerifier(algo string, verifier Verifier) + GetSigner(name string) Signer + GetVerifier(name string) Verifier + SignerNames() []string +} + +type SignerRegistry interface { + SignerRegistryFuncs + + Copy() SignerRegistry +} + +type SignerRegistryProvider interface { + SignerRegistry() SignerRegistry +} + +type HandlerRegistryFuncs interface { + SignerRegistryFuncs + HasherRegistryFuncs + + SignerRegistryProvider + HasherRegistryProvider +} + +type HandlerRegistry interface { + HandlerRegistryFuncs + Copy() HandlerRegistry +} + +type KeyRegistryFuncs interface { + RegisterPublicKey(name string, key interface{}) + RegisterPrivateKey(name string, key interface{}) + GetPublicKey(name string) interface{} + GetPrivateKey(name string) interface{} + HasKeys() bool + + RegisterIssuer(name string, is *pkix.Name) + GetIssuer(name string) *pkix.Name + HasIssuers() bool +} + +type HandlerRegistryProvider interface { + HandlerRegistry() HandlerRegistry +} + +type KeyRegistry interface { + KeyRegistryFuncs + Copy() KeyRegistry +} + +type KeyRegistryProvider interface { + KeyRegistry() KeyRegistry +} + +//////////////////////////////////////////////////////////////////////////////// + +type ( + _hasherRegistry = HasherRegistry + _signerRegistry = SignerRegistry +) + +type handlerRegistry struct { + _hasherRegistry + _signerRegistry +} + +var _ HandlerRegistry = (*handlerRegistry)(nil) + +func NewHandlerRegistry(parents ...HandlerRegistry) HandlerRegistry { + return &handlerRegistry{ + _hasherRegistry: NewHasherRegistry(sliceutils.ConvertWith(parents, toHasherRegistry)...), + _signerRegistry: NewSignerRegistry(sliceutils.ConvertWith(parents, toSignerRegistry)...), + } +} + +func toHasherRegistry(o HasherRegistryProvider) HasherRegistry { + if o == nil { + return nil + } + return o.HasherRegistry() +} + +func toSignerRegistry(o SignerRegistryProvider) SignerRegistry { + if o == nil { + return nil + } + return o.SignerRegistry() +} + +func (r *handlerRegistry) Copy() HandlerRegistry { + return &handlerRegistry{ + _hasherRegistry: r._hasherRegistry.Copy(), + _signerRegistry: r._signerRegistry.Copy(), + } +} + +func (r *handlerRegistry) HasherRegistry() HasherRegistry { + return r._hasherRegistry +} + +func (r *handlerRegistry) SignerRegistry() SignerRegistry { + return r._signerRegistry +} + +//////////////////////////////////////////////////////////////////////////////// + +type signerRegistry struct { + lock sync.RWMutex + parents []SignerRegistry + signers map[string]Signer + verifiers map[string]Verifier +} + +var _ SignerRegistry = (*signerRegistry)(nil) + +func NewSignerRegistry(parents ...SignerRegistry) SignerRegistry { + return &signerRegistry{ + parents: slices.Clone(parents), + signers: map[string]Signer{}, + verifiers: map[string]Verifier{}, + } +} + +func (r *signerRegistry) Copy() SignerRegistry { + return &signerRegistry{ + parents: r.parents, + signers: maps.Clone(r.signers), + verifiers: maps.Clone(r.verifiers), + } +} + +func (r *signerRegistry) RegisterSignatureHandler(handler SignatureHandler) { + r.lock.Lock() + defer r.lock.Unlock() + r.signers[handler.Algorithm()] = handler + r.verifiers[handler.Algorithm()] = handler +} + +func (r *signerRegistry) RegisterSigner(algo string, signer Signer) { + r.lock.Lock() + defer r.lock.Unlock() + r.signers[algo] = signer + if v, ok := signer.(Verifier); ok && r.verifiers[algo] == nil { + r.verifiers[algo] = v + } +} + +func (r *signerRegistry) SignerNames() []string { + r.lock.Lock() + defer r.lock.Unlock() + + names := set.Set[string]{} + for n := range r.signers { + names.Add(n) + } + for _, p := range r.parents { + if p == nil { + continue + } + names.Add(p.SignerNames()...) + } + result := names.AsArray() + sort.Strings(result) + return result +} + +func (r *signerRegistry) RegisterVerifier(algo string, verifier Verifier) { + r.lock.Lock() + defer r.lock.Unlock() + r.verifiers[algo] = verifier + if v, ok := verifier.(Signer); ok && r.signers[algo] == nil { + r.signers[algo] = v + } +} + +func (r *signerRegistry) GetSigner(name string) Signer { + r.lock.RLock() + defer r.lock.RUnlock() + + s := r.signers[name] + if s != nil { + return s + } + for _, p := range r.parents { + if p == nil { + continue + } + s = p.GetSigner(name) + if s != nil { + return s + } + } + return nil +} + +func (r *signerRegistry) GetVerifier(name string) Verifier { + r.lock.RLock() + defer r.lock.RUnlock() + + v := r.verifiers[name] + if v != nil { + return v + } + for _, p := range r.parents { + if p == nil { + continue + } + v = p.GetVerifier(name) + if v != nil { + return v + } + } + return nil +} + +//////////////////////////////////////////////////////////////////////////////// + +type hasherRegistry struct { + lock sync.RWMutex + parents []HasherRegistry + hasher map[string]Hasher +} + +var _ HasherRegistry = (*hasherRegistry)(nil) + +func NewHasherRegistry(parents ...HasherRegistry) HasherRegistry { + return &hasherRegistry{ + parents: slices.Clone(parents), + hasher: map[string]Hasher{}, + } +} + +func (r *hasherRegistry) Copy() HasherRegistry { + return &hasherRegistry{ + parents: r.parents, + hasher: maps.Clone(r.hasher), + } +} + +func (r *hasherRegistry) RegisterHasher(hasher Hasher) { + r.lock.Lock() + defer r.lock.Unlock() + r.hasher[hasher.Algorithm()] = hasher +} + +func (r *hasherRegistry) HasherNames() []string { + r.lock.Lock() + defer r.lock.Unlock() + + names := set.New[string]() + for n := range r.hasher { + names.Add(n) + } + for _, p := range r.parents { + if p == nil { + continue + } + names.Add(p.HasherNames()...) + } + result := names.AsArray() + sort.Strings(result) + return result +} + +func (r *hasherRegistry) GetHasher(name string) Hasher { + r.lock.RLock() + defer r.lock.RUnlock() + + h := r.hasher[NormalizeHashAlgorithm(name)] + if h != nil { + return h + } + for _, p := range r.parents { + if p == nil { + continue + } + h = p.GetHasher(name) + if h != nil { + return h + } + } + return nil +} + +//////////////////////////////////////////////////////////////////////////////// + +var defaultHandlerRegistry = NewHandlerRegistry() + +func DefaultHandlerRegistry() HandlerRegistry { + return defaultHandlerRegistry +} + +//////////////////////////////////////////////////////////////////////////////// + +type keyRegistry struct { + lock sync.RWMutex + parents []KeyRegistry + publicKeys map[string]interface{} + privateKeys map[string]interface{} + issuers map[string]*pkix.Name +} + +var _ KeyRegistry = (*keyRegistry)(nil) + +func NewKeyRegistry(parents ...KeyRegistry) KeyRegistry { + return &keyRegistry{ + parents: slices.Clone(parents), + publicKeys: map[string]interface{}{}, + privateKeys: map[string]interface{}{}, + issuers: map[string]*pkix.Name{}, + } +} + +func (r *keyRegistry) Copy() KeyRegistry { + return &keyRegistry{ + parents: r.parents, + publicKeys: maps.Clone(r.publicKeys), + privateKeys: maps.Clone(r.privateKeys), + } +} + +func (r *keyRegistry) HasKeys() bool { + r.lock.Lock() + defer r.lock.Unlock() + if len(r.publicKeys) > 0 || len(r.privateKeys) > 0 { + return true + } + for _, p := range r.parents { + if p == nil { + continue + } + if p.HasKeys() { + return true + } + } + return false +} + +func (r *keyRegistry) RegisterPublicKey(name string, key interface{}) { + r.lock.Lock() + defer r.lock.Unlock() + r.publicKeys[name] = key +} + +func (r *keyRegistry) RegisterPrivateKey(name string, key interface{}) { + r.lock.Lock() + defer r.lock.Unlock() + r.privateKeys[name] = key +} + +func (r *keyRegistry) GetPublicKey(name string) interface{} { + r.lock.RLock() + defer r.lock.RUnlock() + k := r.publicKeys[name] + if k != nil { + return k + } + for _, p := range r.parents { + if p == nil { + continue + } + k = p.GetPublicKey(name) + if k != nil { + return k + } + } + return nil +} + +func (r *keyRegistry) GetPrivateKey(name string) interface{} { + r.lock.RLock() + defer r.lock.RUnlock() + + k := r.privateKeys[name] + if k != nil { + return k + } + for _, p := range r.parents { + if p == nil { + continue + } + k = p.GetPrivateKey(name) + if k != nil { + return k + } + } + return nil +} + +func (r *keyRegistry) RegisterIssuer(name string, is *pkix.Name) { + r.lock.Lock() + defer r.lock.Unlock() + r.issuers[name] = is +} + +func (r *keyRegistry) GetIssuer(name string) *pkix.Name { + r.lock.Lock() + defer r.lock.Unlock() + i := r.issuers[name] + if i != nil { + return i + } + for _, p := range r.parents { + i := p.GetIssuer(name) + if i != nil { + return i + } + } + // if not explicitly overwritten, the signature name + // is interpreted as expected distinguished name for the issuer. + dn, err := signutils.ParseDN(name) + if err == nil { + return dn + } + return nil +} + +func (r *keyRegistry) HasIssuers() bool { + r.lock.Lock() + defer r.lock.Unlock() + if len(r.issuers) > 0 { + return true + } + for _, p := range r.parents { + if p == nil { + continue + } + if p.HasIssuers() { + return true + } + } + return false +} + +var defaultKeyRegistry = NewKeyRegistry() + +func DefaultKeyRegistry() KeyRegistry { + return defaultKeyRegistry +} + +//////////////////////////////////////////////////////////////////////////////// + +type ( + _HandlerRegistry = HandlerRegistry + _KeyRegistry = KeyRegistry +) + +type registry struct { + _HandlerRegistry + _KeyRegistry + + tsaUrl string +} + +var _ Registry = (*registry)(nil) + +func NewRegistry(h HandlerRegistry, k KeyRegistry) Registry { + return ®istry{ + _HandlerRegistry: NewHandlerRegistry(h), + _KeyRegistry: NewKeyRegistry(k), + } +} + +func (r *registry) HandlerRegistry() HandlerRegistry { + return r._HandlerRegistry +} + +func (r *registry) KeyRegistry() KeyRegistry { + return r._KeyRegistry +} + +func (r *registry) Copy() Registry { + return ®istry{ + _HandlerRegistry: r.HandlerRegistry().Copy(), + _KeyRegistry: r.KeyRegistry().Copy(), + tsaUrl: r.tsaUrl, + } +} + +func (r *registry) TSAUrl() string { + if r.tsaUrl == "" { + return DEFAULT_TSA_URL + } + return r.tsaUrl +} + +func (r *registry) SetTSAUrl(url string) { + r.tsaUrl = url +} + +func RegistryWithPreferredKeys(reg Registry, keys KeyRegistry) Registry { + if keys == nil { + return reg + } + return ®istry{ + _HandlerRegistry: reg.HandlerRegistry(), + _KeyRegistry: NewKeyRegistry(keys, reg.KeyRegistry()), + } +} + +var defaultRegistry = NewRegistry(DefaultHandlerRegistry(), DefaultKeyRegistry()) + +func DefaultRegistry() Registry { + return defaultRegistry +} diff --git a/pkg/signing/rules.go b/api/tech/signing/rules.go similarity index 100% rename from pkg/signing/rules.go rename to api/tech/signing/rules.go diff --git a/api/tech/signing/signing_test.go b/api/tech/signing/signing_test.go new file mode 100644 index 000000000..727a239d9 --- /dev/null +++ b/api/tech/signing/signing_test.go @@ -0,0 +1,55 @@ +package signing_test + +import ( + "crypto/x509/pkix" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/api/tech/signing/hasher/sha256" +) + +var registry = signing.DefaultRegistry() + +const NAME = "testsignature" + +var ISSUER = &pkix.Name{CommonName: "mandelsoft"} + +var _ = Describe("signing", func() { + var defaultContext credentials.Context + + BeforeEach(func() { + defaultContext = credentials.New() + }) + + It("uses rsa signer", func() { + hasher := registry.GetHasher(sha256.Algorithm) + hash, _ := signing.Hash(hasher.Create(), []byte("test")) + + priv, pub, err := rsa.Handler{}.CreateKeyPair() + Expect(err).To(Succeed()) + + registry.RegisterPublicKey(NAME, pub) + registry.RegisterPrivateKey(NAME, priv) + + sctx := &signing.DefaultSigningContext{ + Hash: hasher.Crypto(), + PrivateKey: registry.GetPrivateKey(NAME), + PublicKey: pub, + RootCerts: nil, + Issuer: ISSUER, + } + sig, err := registry.GetSigner(rsa.Algorithm).Sign(defaultContext, hash, sctx) + + Expect(err).To(Succeed()) + Expect(sig.MediaType).To(Equal(rsa.MediaType)) + + sctx.PublicKey = registry.GetPublicKey(NAME) + Expect(registry.GetVerifier(rsa.Algorithm).Verify(hash, sig, sctx)).To(Succeed()) + hash = "A" + hash[1:] + Expect(registry.GetVerifier(rsa.Algorithm).Verify(hash, sig, sctx)).To(HaveOccurred()) + }) +}) diff --git a/pkg/signing/signutils/certs.go b/api/tech/signing/signutils/certs.go similarity index 99% rename from pkg/signing/signutils/certs.go rename to api/tech/signing/signutils/certs.go index e64cabaad..4f14e7161 100644 --- a/pkg/signing/signutils/certs.go +++ b/api/tech/signing/signutils/certs.go @@ -15,7 +15,7 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/modern-go/reflect2" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" ) type Usages []interface{} diff --git a/pkg/signing/signutils/certs_test.go b/api/tech/signing/signutils/certs_test.go similarity index 90% rename from pkg/signing/signutils/certs_test.go rename to api/tech/signing/signutils/certs_test.go index a6d831ba0..d6633a532 100644 --- a/pkg/signing/signutils/certs_test.go +++ b/api/tech/signing/signutils/certs_test.go @@ -10,10 +10,10 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/blobaccess" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" - "github.com/open-component-model/ocm/pkg/signing/signutils" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/api/tech/signing/signutils" + "ocm.software/ocm/api/utils/blobaccess" ) var _ = Describe("normalization", func() { diff --git a/pkg/signing/signutils/names.go b/api/tech/signing/signutils/names.go similarity index 100% rename from pkg/signing/signutils/names.go rename to api/tech/signing/signutils/names.go diff --git a/pkg/signing/signutils/names_test.go b/api/tech/signing/signutils/names_test.go similarity index 97% rename from pkg/signing/signutils/names_test.go rename to api/tech/signing/signutils/names_test.go index 40e2d4daf..f39ee5347 100644 --- a/pkg/signing/signutils/names_test.go +++ b/api/tech/signing/signutils/names_test.go @@ -7,7 +7,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/signing/signutils" + "ocm.software/ocm/api/tech/signing/signutils" ) var _ = Describe("normalization", func() { diff --git a/pkg/signing/signutils/signature.go b/api/tech/signing/signutils/signature.go similarity index 100% rename from pkg/signing/signutils/signature.go rename to api/tech/signing/signutils/signature.go diff --git a/pkg/signing/signutils/suite_test.go b/api/tech/signing/signutils/suite_test.go similarity index 100% rename from pkg/signing/signutils/suite_test.go rename to api/tech/signing/signutils/suite_test.go diff --git a/pkg/signing/signutils/types.go b/api/tech/signing/signutils/types.go similarity index 100% rename from pkg/signing/signutils/types.go rename to api/tech/signing/signutils/types.go diff --git a/api/tech/signing/signutils/utils.go b/api/tech/signing/signutils/utils.go new file mode 100644 index 000000000..b961351e8 --- /dev/null +++ b/api/tech/signing/signutils/utils.go @@ -0,0 +1,590 @@ +package signutils + +import ( + "bytes" + "crypto" + "crypto/dsa" //nolint: staticcheck // yes + "crypto/ecdsa" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "fmt" + "os" + "runtime" + "strings" + "time" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/vfs/pkg/vfs" + "github.com/modern-go/reflect2" + "golang.org/x/exp/slices" + + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +func privateKey(block *pem.Block) (interface{}, error) { + x509Encoded := block.Bytes + switch block.Type { + case "RSA PRIVATE KEY": + return x509.ParsePKCS1PrivateKey(x509Encoded) + case "EC PRIVATE KEY": + return x509.ParseECPrivateKey(x509Encoded) + default: + return nil, fmt.Errorf("invalid pem block type %q", block.Type) + } +} + +func PemBlockForPrivateKey(priv interface{}) *pem.Block { + switch k := priv.(type) { + case *rsa.PrivateKey: + return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)} + case *ecdsa.PrivateKey: + b, err := x509.MarshalECPrivateKey(k) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to marshal ECDSA private key: %v", err) + os.Exit(2) + } + return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b} + default: + return nil + } +} + +func PemBlockForPublicKey(priv interface{}, gen ...bool) *pem.Block { + switch k := priv.(type) { + case *rsa.PublicKey: + if len(gen) > 0 && gen[0] { + bytes, err := x509.MarshalPKIXPublicKey(k) + if err != nil { + panic(err) + } + return &pem.Block{Type: "PUBLIC KEY", Bytes: bytes} + } + return &pem.Block{Type: "RSA PUBLIC KEY", Bytes: x509.MarshalPKCS1PublicKey(k)} + case *ecdsa.PublicKey: + b, err := x509.MarshalPKIXPublicKey(k) + if err != nil { + return nil + } + return &pem.Block{Type: "ECDSA PUBLIC KEY", Bytes: b} + default: + return nil + } +} + +func ParsePublicKey(data []byte) (interface{}, error) { + block, _ := pem.Decode(data) + if block == nil { + return nil, fmt.Errorf("invalid public key format (expected pem block)") + } + pub, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + pub, err = x509.ParsePKCS1PublicKey(block.Bytes) + if err != nil { + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse DER encoded public key") + } + pub = cert.PublicKey + } else { + return pub, nil + } + } + switch pub := pub.(type) { + case *rsa.PublicKey: + return pub, nil + case *dsa.PublicKey: + return pub, nil + case *ecdsa.PublicKey: + return pub, nil + default: + return nil, fmt.Errorf("unknown type of public key") + } +} + +func ParsePrivateKey(data []byte) (interface{}, error) { + block, _ := pem.Decode(data) + if block == nil { + return nil, fmt.Errorf("invalid private key format (expected pem block)") + } + return privateKey(block) +} + +func ParseCertificate(data []byte) (*x509.Certificate, error) { + block, _ := pem.Decode(data) + if block != nil { + if block.Type != CertificatePEMBlockType { + return nil, fmt.Errorf("unexpected pem block type for certificate: %q", block.Type) + } + return x509.ParseCertificate(block.Bytes) + } + return nil, fmt.Errorf("invalid certificate format (expected %s pem block)", CertificatePEMBlockType) +} + +func ParseCertificateChain(data []byte, filter bool) ([]*x509.Certificate, error) { + var chain []*x509.Certificate + for { + block, rest := pem.Decode(data) + if block == nil { + break + } + if block.Type != CertificatePEMBlockType { + if !filter { + return nil, fmt.Errorf("unexpected pem block type for certificate: %q", block.Type) + } + } else { + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, err + } + chain = append(chain, cert) + } + data = rest + } + + if len(chain) == 0 { + if !filter { + return nil, fmt.Errorf("invalid certificate format (expected CERTIFICATE pem block)") + } + } + return chain, nil +} + +func PemBlockForCertificate(cert interface{}) *pem.Block { + switch k := cert.(type) { + case *x509.Certificate: + return &pem.Block{Type: CertificatePEMBlockType, Bytes: k.Raw} + default: + return nil + } +} + +type PublicKeySource interface { + Public() crypto.PublicKey +} + +func getGenericData(in blobaccess.GenericData) (blobaccess.GenericData, error) { + data, err := blobaccess.GetGenericData(in) + if err != nil { + if !errors.IsErrInvalidKind(err, blobaccess.KIND_DATASOURCE) { + return nil, err + } + return in, nil + } else { + return data, nil + } +} + +func GetPrivateKey(key GenericPrivateKey) (interface{}, error) { + key, err := getGenericData(key) + if err != nil { + return nil, errors.Wrapf(err, "cannot evaluate private key") + } + switch k := key.(type) { + case []byte: + return ParsePrivateKey(k) + case *rsa.PrivateKey: + return k, nil + case *ecdsa.PrivateKey: + return k, nil + default: + return nil, errors.ErrInvalidType(KIND_PRIVATE_KEY, k) + } +} + +func GetPublicKey(key GenericPublicKey) (interface{}, error) { + key, err := getGenericData(key) + if err != nil { + return nil, errors.Wrapf(err, "cannot evaluate public key") + } + switch k := key.(type) { + case []byte: + return ParsePublicKey(k) + case *rsa.PublicKey: + return k, nil + case *dsa.PublicKey: + return k, nil + case *ecdsa.PublicKey: + return k, nil + case *x509.Certificate: + return k.PublicKey, nil + case PublicKeySource: + return k.Public(), nil + default: + return nil, errors.ErrInvalidType(KIND_PUBLIC_KEY, k) + } +} + +func GetCertificateChain(in GenericCertificateChain, filter bool) ([]*x509.Certificate, error) { + // unfortunately it is not possible to get certificates from a x509.CertPool + + in, err := getGenericData(in) + if err != nil { + return nil, errors.Wrapf(err, "cannot evaluate certificate chain") + } + switch k := in.(type) { + case []byte: + return ParseCertificateChain(k, filter) + case *x509.Certificate: + return []*x509.Certificate{k}, nil + case []*x509.Certificate: + return k, nil + default: + return nil, errors.ErrInvalidType(KIND_CERTIFICATE, k) + } +} + +//////////////////////////////////////////////////////////////////////////////// + +func SystemCertPool() (*x509.CertPool, error) { + pool := x509.NewCertPool() + sys, err := x509.SystemCertPool() + if err != nil { + if runtime.GOOS != "windows" { + return nil, errors.Wrapf(err, "cannot get system cert pool") + } + } else { + pool = sys + } + return pool, nil +} + +func RootPoolFromFile(pemfile string, useOS bool, fss ...vfs.FileSystem) (*x509.CertPool, error) { + fs := utils.FileSystem(fss...) + pemdata, err := utils.ReadFile(pemfile, fs) + if err != nil { + return nil, errors.Wrapf(err, "cannot read cert pem file %q", pemfile) + } + pool := x509.NewCertPool() + if useOS { + sys, err := SystemCertPool() + if err != nil { + return nil, err + } + pool = sys + } + ok := pool.AppendCertsFromPEM(pemdata) + if !ok { + return nil, errors.Newf("cannot add cert pem file to cert pool") + } + return pool, err +} + +func GetCertPool(in GenericCertificatePool, filter bool) (*x509.CertPool, error) { + var certs []*x509.Certificate + var err error + + if reflect2.IsNil(in) { + return nil, nil + } + in, err = getGenericData(in) + if err != nil { + return nil, errors.Wrapf(err, "cannot evaluate certificate pool") + } + switch k := in.(type) { + case []byte: + certs, err = ParseCertificateChain(k, filter) + case *x509.Certificate: + certs = []*x509.Certificate{k} + case []*x509.Certificate: + certs = k + case *x509.CertPool: + return k, nil + default: + err = errors.ErrInvalidType(KIND_CERTPOOL, k) + } + + if err != nil { + return nil, err + } + + pool := x509.NewCertPool() + for _, c := range certs { + pool.AddCert(c) + } + return pool, nil +} + +func AddCertificateToPool(in GenericCertificatePool, chain ...GenericCertificateChain) (GenericCertificatePool, error) { + var certs []*x509.Certificate + var pool *x509.CertPool + var err error + + if !reflect2.IsNil(in) { + switch k := in.(type) { + case []byte: + certs, err = ParseCertificateChain(k, false) + case string: + certs, err = ParseCertificateChain([]byte(k), false) + case *x509.Certificate: + certs = []*x509.Certificate{k} + case []*x509.Certificate: + certs = slices.Clone(k) + case *x509.CertPool: + pool = k + default: + err = errors.ErrInvalidType(KIND_CERTPOOL, k) + } + } + if err != nil { + return in, err + } + + for _, c := range chain { + sub, err := GetCertificateChain(c, false) + if err != nil { + return in, err + } + if pool != nil { + for _, cert := range sub { + pool.AddCert(cert) + } + } else { + certs = append(certs, sub...) + } + } + if pool != nil { + return pool, nil + } + return certs, nil +} + +func GetCertificate(in GenericCertificate, filter bool) (*x509.Certificate, *x509.CertPool, error) { + var certs []*x509.Certificate + var err error + + in, err = getGenericData(in) + if err != nil { + return nil, nil, errors.Wrapf(err, "cannot evaluate certificate") + } + + switch k := in.(type) { + case []byte: + certs, err = ParseCertificateChain(k, filter) + case *x509.Certificate: + certs = []*x509.Certificate{k} + case []*x509.Certificate: + certs = k + default: + err = errors.ErrInvalidType(KIND_CERTIFICATE, k) + } + + if err != nil { + return nil, nil, err + } + + if len(certs) == 0 { + return nil, nil, fmt.Errorf("no certificate") + } + + pool := x509.NewCertPool() + for _, c := range certs { + pool.AddCert(c) + } + return certs[0], pool, nil +} + +func IsSelfSigned(cert *x509.Certificate) bool { + if cert.AuthorityKeyId == nil { + return true + } + return bytes.Equal(cert.SubjectKeyId, cert.AuthorityKeyId) +} + +func GetTime(in interface{}) (time.Time, error) { + switch t := in.(type) { + case time.Time: + return t, nil + case *time.Time: + return *t, nil + case string: + notBefore, err := time.Parse("Jan 2 15:04:05 2006", t) + if err != nil { + return time.Time{}, errors.Wrapf(err, "invalid time specification") + } + return notBefore, nil + default: + return time.Time{}, errors.ErrInvalidType("time specification", in) + } +} + +//////////////////////////////////////////////////////////////////////////////// + +type KeyUsage interface { + String() string + AddTo(*x509.Certificate) +} + +type _keyUsage x509.KeyUsage + +func (this _keyUsage) AddTo(cert *x509.Certificate) { + cert.KeyUsage |= x509.KeyUsage(this) +} + +func (this _keyUsage) String() string { + switch x509.KeyUsage(this) { + case x509.KeyUsageDigitalSignature: + return "Signature" + case x509.KeyUsageContentCommitment: + return "ContentCommitment" + case x509.KeyUsageKeyEncipherment: + return "KeyEncipherment" + case x509.KeyUsageDataEncipherment: + return "DataEncipherment" + case x509.KeyUsageKeyAgreement: + return "KeyAgreement" + case x509.KeyUsageCertSign: + return "CertSign" + case x509.KeyUsageCRLSign: + return "CRLSign" + case x509.KeyUsageEncipherOnly: + return "EncipherOnly" + case x509.KeyUsageDecipherOnly: + return "DecipherOnly" + default: + return "UnknownKeyUsage" + } +} + +var _keyUsages = []x509.KeyUsage{ + x509.KeyUsageDigitalSignature, + x509.KeyUsageContentCommitment, + x509.KeyUsageKeyEncipherment, + x509.KeyUsageDataEncipherment, + x509.KeyUsageKeyAgreement, + x509.KeyUsageCertSign, + x509.KeyUsageCRLSign, + x509.KeyUsageEncipherOnly, + x509.KeyUsageDecipherOnly, +} + +func KeyUsages(usages x509.KeyUsage) []string { + result := []string{} + for _, u := range _keyUsages { + if usages&u != 0 { + result = append(result, (_keyUsage(u)).String()) + } + } + return result +} + +type _extKeyUsage x509.ExtKeyUsage + +func (this _extKeyUsage) AddTo(cert *x509.Certificate) { + for _, k := range cert.ExtKeyUsage { + if k == x509.ExtKeyUsage(this) { + return + } + } + cert.ExtKeyUsage = append(cert.ExtKeyUsage, x509.ExtKeyUsage(this)) +} + +func (this _extKeyUsage) String() string { + switch x509.ExtKeyUsage(this) { + case x509.ExtKeyUsageAny: + return "Any" + case x509.ExtKeyUsageServerAuth: + return "ServerAuth" + case x509.ExtKeyUsageClientAuth: + return "ClientAuth" + case x509.ExtKeyUsageCodeSigning: + return "CodeSigning" + case x509.ExtKeyUsageEmailProtection: + return "EmailProtection" + case x509.ExtKeyUsageIPSECEndSystem: + return "IPSECEndSystem" + case x509.ExtKeyUsageIPSECTunnel: + return "IPSECTunnel" + case x509.ExtKeyUsageIPSECUser: + return "IPSECUser" + case x509.ExtKeyUsageTimeStamping: + return "TimeStamping" + case x509.ExtKeyUsageOCSPSigning: + return "OCSPSigning" + case x509.ExtKeyUsageMicrosoftServerGatedCrypto: + return "MicrosoftServerGatedCrypto" + case x509.ExtKeyUsageNetscapeServerGatedCrypto: + return "NetscapeServerGatedCrypto" + case x509.ExtKeyUsageMicrosoftCommercialCodeSigning: + return "MicrosoftCommercialCodeSigning" + case x509.ExtKeyUsageMicrosoftKernelCodeSigning: + return "MicrosoftKernelCodeSigning" + default: + return "UnknownExtKeyUsage" + } +} + +func ExtKeyUsages(usages []x509.ExtKeyUsage) []string { + result := []string{} + for _, u := range usages { + result = append(result, (_extKeyUsage(u)).String()) + } + return result +} + +func ParseKeyUsage(name string) KeyUsage { + switch strings.ToLower(name) { + case "signature": + return _keyUsage(x509.KeyUsageDigitalSignature) + case "commitment": + return _keyUsage(x509.KeyUsageContentCommitment) + case "keyencipherment": + return _keyUsage(x509.KeyUsageKeyEncipherment) + case "dataencipherment": + return _keyUsage(x509.KeyUsageDataEncipherment) + case "keyagreement": + return _keyUsage(x509.KeyUsageKeyAgreement) + case "certsign": + return _keyUsage(x509.KeyUsageCertSign) + case "crlsign": + return _keyUsage(x509.KeyUsageCRLSign) + case "encipheronly": + return _keyUsage(x509.KeyUsageEncipherOnly) + case "decipheronly": + return _keyUsage(x509.KeyUsageDecipherOnly) + + case "any": + return _extKeyUsage(x509.ExtKeyUsageAny) + case "serverauth": + return _extKeyUsage(x509.ExtKeyUsageServerAuth) + case "clientauth": + return _extKeyUsage(x509.ExtKeyUsageClientAuth) + case "codesigning": + return _extKeyUsage(x509.ExtKeyUsageCodeSigning) + case "emailprotection": + return _extKeyUsage(x509.ExtKeyUsageEmailProtection) + case "ipsecendsystem": + return _extKeyUsage(x509.ExtKeyUsageIPSECEndSystem) + case "ipsectunnel": + return _extKeyUsage(x509.ExtKeyUsageIPSECTunnel) + case "ipsecuser": + return _extKeyUsage(x509.ExtKeyUsageIPSECUser) + case "timestamping": + return _extKeyUsage(x509.ExtKeyUsageTimeStamping) + case "ocspsigning": + return _extKeyUsage(x509.ExtKeyUsageOCSPSigning) + case "microsoftservergatedcrypto": + return _extKeyUsage(x509.ExtKeyUsageMicrosoftServerGatedCrypto) + case "netscapeservergatedcrypto": + return _extKeyUsage(x509.ExtKeyUsageNetscapeServerGatedCrypto) + case "microsoftcommercialcodesigning": + return _extKeyUsage(x509.ExtKeyUsageMicrosoftCommercialCodeSigning) + case "microsoftkernelcodesigning": + return _extKeyUsage(x509.ExtKeyUsageMicrosoftKernelCodeSigning) + } + return nil +} + +func GetKeyUsage(opt interface{}) KeyUsage { + switch o := opt.(type) { + case x509.ExtKeyUsage: + return _extKeyUsage(o) + case x509.KeyUsage: + return _keyUsage(o) + case string: + return ParseKeyUsage(o) + default: + return nil + } +} diff --git a/pkg/signing/suite_test.go b/api/tech/signing/suite_test.go similarity index 100% rename from pkg/signing/suite_test.go rename to api/tech/signing/suite_test.go diff --git a/pkg/signing/tsa/pem.go b/api/tech/signing/tsa/pem.go similarity index 100% rename from pkg/signing/tsa/pem.go rename to api/tech/signing/tsa/pem.go diff --git a/pkg/signing/tsa/tsa.go b/api/tech/signing/tsa/tsa.go similarity index 96% rename from pkg/signing/tsa/tsa.go rename to api/tech/signing/tsa/tsa.go index 7684bb95f..f01475dbc 100644 --- a/pkg/signing/tsa/tsa.go +++ b/api/tech/signing/tsa/tsa.go @@ -12,8 +12,8 @@ import ( "github.com/go-test/deep" "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/signing/signutils" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/tech/signing/signutils" + "ocm.software/ocm/api/utils" ) // NewMessageImprint creates a new MessageImprint using hash and digest. diff --git a/pkg/signing/tsa/types.go b/api/tech/signing/tsa/types.go similarity index 100% rename from pkg/signing/tsa/types.go rename to api/tech/signing/tsa/types.go diff --git a/api/tech/signing/types.go b/api/tech/signing/types.go new file mode 100644 index 000000000..438a75d5c --- /dev/null +++ b/api/tech/signing/types.go @@ -0,0 +1,106 @@ +package signing + +import ( + "crypto" + "crypto/x509/pkix" + "encoding/json" + "hash" + + "github.com/sirupsen/logrus" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/tech/signing/signutils" +) + +type SigningContext interface { + GetHash() crypto.Hash + GetPrivateKey() signutils.GenericPrivateKey + GetPublicKey() signutils.GenericPublicKey + GetRootCerts() signutils.GenericCertificatePool + GetIssuer() *pkix.Name +} + +type DefaultSigningContext struct { + Hash crypto.Hash + PrivateKey signutils.GenericPrivateKey + PublicKey signutils.GenericPublicKey + RootCerts signutils.GenericCertificatePool + Issuer *pkix.Name +} + +var _ SigningContext = (*DefaultSigningContext)(nil) + +func (d *DefaultSigningContext) GetHash() crypto.Hash { + return d.Hash +} + +func (d *DefaultSigningContext) GetPrivateKey() signutils.GenericPrivateKey { + return d.PrivateKey +} + +func (d *DefaultSigningContext) GetPublicKey() signutils.GenericPublicKey { + return d.PublicKey +} + +func (d *DefaultSigningContext) GetRootCerts() signutils.GenericCertificatePool { + return d.RootCerts +} + +func (d *DefaultSigningContext) GetIssuer() *pkix.Name { + return d.Issuer +} + +type Signature struct { + Value string + MediaType string + Algorithm string + Issuer string +} + +func (s *Signature) String() string { + data, err := json.Marshal(s) //nolint:musttag // only for string output + if err != nil { + logrus.Error(err) + } + + return string(data) +} + +// Signer interface is used to implement different signing algorithms. +// Each Signer should have a matching Verifier. +type Signer interface { + // Sign returns the signature for the given digest. + // If known a given public key can be passed. The signer may + // decide to put a trusted public key into the signature, + // for example for public keys provided by organization validated + // certificates. + // If used the key and/or certificate must be validated, for certificates + // the distinguished name must match the issuer. + Sign(cctx credentials.Context, digest string, sctx SigningContext) (*Signature, error) + // Algorithm is the name of the finally used signature algorithm. + // A signer might be registered using a logical name, so there might + // be multiple signer registration providing the same signature algorithm + Algorithm() string +} + +// Verifier interface is used to implement different verification algorithms. +// Each Verifier should have a matching Signer. +type Verifier interface { + // Verify checks the signature, returns an error on verification failure + Verify(digest string, sig *Signature, sctx SigningContext) error + Algorithm() string +} + +// SignatureHandler can create and verify signature of a dedicated type. +type SignatureHandler interface { + Algorithm() string + Signer + Verifier +} + +// Hasher creates a new hash.Hash interface. +type Hasher interface { + Algorithm() string + Create() hash.Hash + Crypto() crypto.Hash +} diff --git a/pkg/signing/utils.go b/api/tech/signing/utils.go similarity index 100% rename from pkg/signing/utils.go rename to api/tech/signing/utils.go diff --git a/pkg/signing/x509_certificate.go b/api/tech/signing/x509_certificate.go similarity index 98% rename from pkg/signing/x509_certificate.go rename to api/tech/signing/x509_certificate.go index 0cf1e6917..2b4ad7628 100644 --- a/pkg/signing/x509_certificate.go +++ b/api/tech/signing/x509_certificate.go @@ -7,7 +7,7 @@ import ( "errors" "fmt" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" ) // CreateAndVerifyX509CertificateFromFiles creates and verifies a x509 certificate from certificate files. diff --git a/api/utils/accessio/access.go b/api/utils/accessio/access.go new file mode 100644 index 000000000..dfe33caba --- /dev/null +++ b/api/utils/accessio/access.go @@ -0,0 +1,98 @@ +package accessio + +import ( + "math/rand" + "time" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/utils/iotools" + "ocm.software/ocm/api/utils/refmgmt" +) + +var ( + ErrClosed = refmgmt.ErrClosed + ErrReadOnly = errors.ErrReadOnly() +) + +//////////////////////////////////////////////////////////////////////////////// + +type NopCloser = iotools.NopCloser + +//////////////////////////////////////////////////////////////////////////////// + +type retriableError struct { + wrapped error +} + +func IsRetriableError(err error) bool { + if err == nil { + return false + } + return errors.IsA(err, &retriableError{}) +} + +func RetriableError(err error) error { + if err == nil { + return nil + } + return &retriableError{err} +} + +func RetriableError1[T any](r T, err error) (T, error) { + if err == nil { + return r, nil + } + return r, &retriableError{err} +} + +func RetriableError2[S, T any](s S, r T, err error) (S, T, error) { + if err == nil { + return s, r, nil + } + return s, r, &retriableError{err} +} + +func (e *retriableError) Error() string { + return e.wrapped.Error() +} + +func (e *retriableError) Unwrap() error { + return e.wrapped +} + +func Retry(cnt int, d time.Duration, f func() error) error { + for { + err := f() + if err == nil || cnt <= 0 || !IsRetriableError(err) { + return err + } + jitter := time.Duration(rand.Int63n(int64(d))) //nolint: gosec // just an random number + d = 2*d + (d/2-jitter)/10 + cnt-- + } +} + +func Retry1[T any](cnt int, d time.Duration, f func() (T, error)) (T, error) { + for { + r, err := f() + if err == nil || cnt <= 0 || !IsRetriableError(err) { + return r, err + } + jitter := time.Duration(rand.Int63n(int64(d))) //nolint: gosec // just an random number + d = 2*d + (d/2-jitter)/10 + cnt-- + } +} + +func Retry2[S, T any](cnt int, d time.Duration, f func() (S, T, error)) (S, T, error) { + for { + s, t, err := f() + if err == nil || cnt <= 0 || !IsRetriableError(err) { + return s, t, err + } + jitter := time.Duration(rand.Int63n(int64(d))) //nolint: gosec // just an random number + d = 2*d + (d/2-jitter)/10 + cnt-- + } +} diff --git a/api/utils/accessio/cache.go b/api/utils/accessio/cache.go new file mode 100644 index 000000000..6d2823936 --- /dev/null +++ b/api/utils/accessio/cache.go @@ -0,0 +1,653 @@ +package accessio + +import ( + "fmt" + "io" + "os" + "strings" + "sync" + "time" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/vfs/pkg/osfs" + "github.com/mandelsoft/vfs/pkg/projectionfs" + "github.com/mandelsoft/vfs/pkg/vfs" + "github.com/marstr/guid" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/file" + "ocm.software/ocm/api/utils/iotools" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/refmgmt" +) + +type StaticAllocatable struct{} + +func (_ StaticAllocatable) Ref() error { return nil } +func (_ StaticAllocatable) Unref() error { return nil } + +type BlobSource interface { + refmgmt.Allocatable + GetBlobData(digest digest.Digest) (int64, blobaccess.DataAccess, error) +} + +type BlobSink interface { + refmgmt.Allocatable + AddBlob(blob blobaccess.BlobAccess) (int64, digest.Digest, error) +} + +type RootedCache interface { + Root() (string, vfs.FileSystem) +} + +type CleanupCache interface { + // Cleanup can be implemented to offer a cache reorg. + // It returns the number and size of + // - handled entries (cnt, size) + // - not handled entries (ncnt, nsize) + // - failing entries (fcnt, fsize) + Cleanup(p common.Printer, before *time.Time, dryrun bool) (cnt int, ncnt int, fcnt int, size int64, nsize int64, fsize int64, err error) +} + +type BlobCache interface { + BlobSource + BlobSink + AddData(data blobaccess.DataAccess) (int64, digest.Digest, error) +} + +type blobCache struct { + refmgmt.Allocatable + lock sync.RWMutex + cache vfs.FileSystem +} + +var ( + _ sync.Locker = (*blobCache)(nil) + _ RootedCache = (*blobCache)(nil) +) + +// ACCESS_SUFFIX is the suffix of an additional blob related +// file used to track the last access time by its modification time, +// because Go does not support a platform independent way to access the +// last access time attribute of a filesystem. +const ACCESS_SUFFIX = ".acc" + +func NewDefaultBlobCache(fss ...vfs.FileSystem) (BlobCache, error) { + var err error + fs := DefaultedFileSystem(nil, fss...) + if fs == nil { + fs, err = osfs.NewTempFileSystem() + if err != nil { + return nil, err + } + } + c := &blobCache{ + cache: fs, + } + c.Allocatable = refmgmt.NewAllocatable(c.cleanup) + return c, nil +} + +func NewStaticBlobCache(path string, fss ...vfs.FileSystem) (BlobCache, error) { + fs := FileSystem(fss...) + err := fs.MkdirAll(path, 0o700) + if err != nil { + return nil, err + } + fs, err = projectionfs.New(fs, path) + if err != nil { + return nil, err + } + return NewDefaultBlobCache(fs) +} + +func (c *blobCache) Root() (string, vfs.FileSystem) { + return vfs.PathSeparatorString, c.cache +} + +func (c *blobCache) Lock() { + c.lock.Lock() +} + +func (c *blobCache) Unlock() { + c.lock.Unlock() +} + +func (c *blobCache) Cleanup(p common.Printer, before *time.Time, dryrun bool) (cnt int, ncnt int, fcnt int, size int64, nsize int64, fsize int64, err error) { + c.Lock() + defer c.Unlock() + + if p == nil { + p = common.NewPrinter(nil) + } + path, fs := c.Root() + + entries, err := vfs.ReadDir(fs, path) + if err != nil { + return 0, 0, 0, 0, 0, 0, err + } + for _, e := range entries { + if strings.HasSuffix(e.Name(), ACCESS_SUFFIX) { + continue + } + base := vfs.Join(fs, path, e.Name()) + if before != nil && !before.IsZero() { + fi, err := fs.Stat(base + ACCESS_SUFFIX) + if err != nil { + if !vfs.IsErrNotExist(err) { + if p != nil { + p.Printf("cannot stat %q: %s", e.Name(), err) + } + fcnt++ + fsize += e.Size() + continue + } + } else { + if fi.ModTime().After(*before) { + ncnt++ + nsize += e.Size() + continue + } + } + } + if !dryrun { + err := fs.RemoveAll(base) + if err != nil { + if p != nil { + p.Printf("cannot delete %q: %s", e.Name(), err) + } + fcnt++ + fsize += e.Size() + continue + } + fs.RemoveAll(base + ACCESS_SUFFIX) + } + cnt++ + size += e.Size() + } + return cnt, ncnt, fcnt, size, nsize, fsize, nil +} + +func (c *blobCache) cleanup() error { + return vfs.Cleanup(c.cache) +} + +func (c *blobCache) GetBlobData(digest digest.Digest) (int64, blobaccess.DataAccess, error) { + err := c.Ref() + if err == nil { + defer c.Unref() + c.lock.RLock() + defer c.lock.RUnlock() + + path := common.DigestToFileName(digest) + fi, err := c.cache.Stat(path) + if err == nil { + vfs.WriteFile(c.cache, path+ACCESS_SUFFIX, []byte{}, 0o600) + // now := time.Now() + // c.cache.Chtimes(path+ACCESS_SUFFIX, now, now) + return fi.Size(), file.DataAccess(c.cache, path), nil + } + if os.IsNotExist(err) { + return -1, nil, blobaccess.ErrBlobNotFound(digest) + } + } + return blobaccess.BLOB_UNKNOWN_SIZE, nil, err +} + +func (c *blobCache) AddBlob(blob blobaccess.BlobAccess) (int64, digest.Digest, error) { + err := c.Ref() + if err != nil { + return blobaccess.BLOB_UNKNOWN_SIZE, blobaccess.BLOB_UNKNOWN_DIGEST, err + } + defer c.Unref() + + var digester *iotools.DigestReader + + if blob.DigestKnown() { + c.lock.RLock() + path := common.DigestToFileName(blob.Digest()) + if ok, err := vfs.Exists(c.cache, path); ok || err != nil { + c.lock.RUnlock() + return blob.Size(), blob.Digest(), err + } + c.lock.RUnlock() + } + + tmp := "TMP" + guid.NewGUID().String() + + br, err := blob.Reader() + if err != nil { + return blobaccess.BLOB_UNKNOWN_SIZE, "", errors.Wrapf(err, "cannot get blob content") + } + defer br.Close() + + reader := io.Reader(br) + if !blob.DigestKnown() { + digester = iotools.NewDefaultDigestReader(reader) + reader = digester + } + + writer, err := c.cache.Create(tmp) + if err != nil { + return blobaccess.BLOB_UNKNOWN_SIZE, "", errors.Wrapf(err, "cannot create blob file in cache") + } + size, err := io.Copy(writer, reader) + writer.Close() + if err != nil { + c.cache.Remove(tmp) + return blobaccess.BLOB_UNKNOWN_SIZE, "", err + } + + var digest digest.Digest + var ok bool + if digester != nil { + digest = digester.Digest() + } else { + digest = blob.Digest() + } + target := common.DigestToFileName(digest) + + c.lock.Lock() + defer c.lock.Unlock() + if ok, err = vfs.Exists(c.cache, target); err != nil || !ok { + err = c.cache.Rename(tmp, target) + } + c.cache.Remove(tmp) + vfs.WriteFile(c.cache, target+ACCESS_SUFFIX, []byte{}, 0o600) + return size, digest, err +} + +func (c *blobCache) AddData(data blobaccess.DataAccess) (int64, digest.Digest, error) { + return c.AddBlob(blobaccess.ForDataAccess(blobaccess.BLOB_UNKNOWN_DIGEST, blobaccess.BLOB_UNKNOWN_SIZE, "", data)) +} + +//////////////////////////////////////////////////////////////////////////////// + +type cascadedCache struct { + refmgmt.Allocatable + lock sync.RWMutex + parent BlobSource + source BlobSource + sink BlobSink +} + +var _ BlobCache = (*cascadedCache)(nil) + +func NewCascadedBlobCache(parent BlobCache) (BlobCache, error) { + if parent != nil { + err := parent.Ref() + if err != nil { + return nil, err + } + } + c := &cascadedCache{ + parent: parent, + } + c.Allocatable = refmgmt.NewAllocatable(c.cleanup) + return c, nil +} + +func NewCascadedBlobCacheForSource(parent BlobSource, src BlobSource) (BlobCache, error) { + if parent != nil { + err := parent.Ref() + if err != nil { + return nil, err + } + } + if src != nil { + err := src.Ref() + if err != nil { + return nil, err + } + } + c := &cascadedCache{ + parent: parent, + source: src, + } + c.Allocatable = refmgmt.NewAllocatable(c.cleanup) + return c, nil +} + +func NewCascadedBlobCacheForCache(parent BlobSource, src BlobCache) (BlobCache, error) { + if parent != nil { + err := parent.Ref() + if err != nil { + return nil, err + } + } + if src != nil { + err := src.Ref() + if err != nil { + return nil, err + } + } + c := &cascadedCache{ + parent: parent, + source: src, + sink: src, + } + c.Allocatable = refmgmt.NewAllocatable(c.cleanup) + return c, nil +} + +func (c *cascadedCache) cleanup() error { + list := errors.ErrListf("closing cascaded blob cache") + if c.source != nil { + list.Add(c.source.Unref()) + } + if c.parent != nil { + list.Add(c.parent.Unref()) + } + return list.Result() +} + +func (c *cascadedCache) GetBlobData(digest digest.Digest) (int64, blobaccess.DataAccess, error) { + err := c.Ref() + if err != nil { + return blobaccess.BLOB_UNKNOWN_SIZE, nil, err + } + defer c.Unref() + + c.lock.RLock() + defer c.lock.RUnlock() + + if c.source != nil { + size, acc, err := c.source.GetBlobData(digest) + if err == nil { + return size, acc, err + } + if !blobaccess.IsErrBlobNotFound(err) { + return blobaccess.BLOB_UNKNOWN_SIZE, nil, err + } + } + if c.parent != nil { + return c.parent.GetBlobData(digest) + } + return blobaccess.BLOB_UNKNOWN_SIZE, nil, blobaccess.ErrBlobNotFound(digest) +} + +func (c *cascadedCache) AddData(data blobaccess.DataAccess) (int64, digest.Digest, error) { + return c.AddBlob(blobaccess.ForDataAccess(blobaccess.BLOB_UNKNOWN_DIGEST, blobaccess.BLOB_UNKNOWN_SIZE, "", data)) +} + +func (c *cascadedCache) AddBlob(blob blobaccess.BlobAccess) (int64, digest.Digest, error) { + err := c.Ref() + if err == nil { + defer c.Unref() + c.lock.Lock() + defer c.lock.Unlock() + + if c.source == nil { + cache, err := NewDefaultBlobCache() + if err != nil { + return blobaccess.BLOB_UNKNOWN_SIZE, blobaccess.BLOB_UNKNOWN_DIGEST, err + } + c.source = cache + c.sink = cache + } + if c.sink != nil { + return c.sink.AddBlob(blob) + } + if c.parent != nil { + if sink, ok := c.parent.(BlobSink); ok { + return sink.AddBlob(blob) + } + } + } + return blobaccess.BLOB_UNKNOWN_SIZE, blobaccess.BLOB_UNKNOWN_DIGEST, err +} + +//////////////////////////////////////////////////////////////////////////////// + +type cached struct { + refmgmt.Allocatable + lock sync.RWMutex + source BlobSource + sink BlobSink + cache BlobCache +} + +var _ BlobCache = (*cached)(nil) + +func (c *cached) cleanup() error { + list := errors.ErrListf("closing cached blob store") + if c.sink != nil { + list.Add(c.sink.Unref()) + } + if c.source != nil { + list.Add(c.source.Unref()) + } + c.cache.Unref() + return list.Result() +} + +func (a *cached) GetBlobData(digest digest.Digest) (int64, blobaccess.DataAccess, error) { + err := a.Ref() + if err != nil { + return blobaccess.BLOB_UNKNOWN_SIZE, nil, err + } + defer a.Unref() + + size, acc, err := a.cache.GetBlobData(digest) + if err != nil { + if !blobaccess.IsErrBlobNotFound(err) { + return blobaccess.BLOB_UNKNOWN_SIZE, nil, err + } + size, acc, err = a.source.GetBlobData(digest) + if err == nil { + acc = newCachedAccess(a, acc, size, digest) + } + } + return size, acc, err +} + +func (a *cached) AddBlob(blob blobaccess.BlobAccess) (int64, digest.Digest, error) { + err := a.Ref() + if err != nil { + return blobaccess.BLOB_UNKNOWN_SIZE, blobaccess.BLOB_UNKNOWN_DIGEST, err + } + defer a.Unref() + + if a.sink == nil { + return blobaccess.BLOB_UNKNOWN_SIZE, blobaccess.BLOB_UNKNOWN_DIGEST, fmt.Errorf("no blob sink") + } + size, digest, err := a.cache.AddBlob(blob) + if err != nil { + return blobaccess.BLOB_UNKNOWN_SIZE, blobaccess.BLOB_UNKNOWN_DIGEST, err + } + _, acc, err := a.cache.GetBlobData(digest) + if err != nil { + return blobaccess.BLOB_UNKNOWN_SIZE, blobaccess.BLOB_UNKNOWN_DIGEST, err + } + size, digest, err = a.sink.AddBlob(blobaccess.ForDataAccess(digest, size, blob.MimeType(), acc)) + if err != nil { + return blobaccess.BLOB_UNKNOWN_SIZE, blobaccess.BLOB_UNKNOWN_DIGEST, err + } + return size, digest, err +} + +func (c *cached) AddData(data blobaccess.DataAccess) (int64, digest.Digest, error) { + return c.AddBlob(blobaccess.ForDataAccess(blobaccess.BLOB_UNKNOWN_DIGEST, blobaccess.BLOB_UNKNOWN_SIZE, "", data)) +} + +///////////////////////////////////////// + +type cachedAccess struct { + lock sync.Mutex + cache *cached + access blobaccess.DataAccess + digest digest.Digest + size int64 + orig blobaccess.DataAccess +} + +var _ blobaccess.DataAccess = (*cachedAccess)(nil) + +func CachedAccess(src BlobSource, dst BlobSink, cache BlobCache) (BlobCache, error) { + var err error + if cache == nil { + cache, err = NewDefaultBlobCache() + if err != nil { + return nil, err + } + } else { + err = cache.Ref() + if err != nil { + return nil, err + } + } + if src != nil { + err = src.Ref() + if err != nil { + return nil, err + } + } + if dst != nil { + err = dst.Ref() + if err != nil { + return nil, err + } + } + c := &cached{source: src, sink: dst, cache: cache} + c.Allocatable = refmgmt.NewAllocatable(c.cleanup) + return c, nil +} + +func newCachedAccess(cache *cached, blob blobaccess.DataAccess, size int64, digest digest.Digest) blobaccess.DataAccess { + return &cachedAccess{ + cache: cache, + size: size, + digest: digest, + orig: blob, + } +} + +func (c *cachedAccess) Get() ([]byte, error) { + var err error + + c.lock.Lock() + defer c.lock.Unlock() + if c.access == nil && c.digest != "" { + c.size, c.access, _ = c.cache.cache.GetBlobData(c.digest) + } + if c.access == nil { + c.cache.lock.Lock() + defer c.cache.lock.Unlock() + + if c.digest != "" { + c.size, c.access, err = c.cache.cache.GetBlobData(c.digest) + if err != nil && !blobaccess.IsErrBlobNotFound(err) { + return nil, err + } + } + if c.access == nil { + data, err := c.orig.Get() + if err != nil { + return nil, err + } + c.size, c.digest, err = c.cache.cache.AddData(blobaccess.DataAccessForData(data)) + if err == nil { + c.orig.Close() + c.orig = nil + } + return data, err + } + } + return c.access.Get() +} + +func (c *cachedAccess) Reader() (io.ReadCloser, error) { + var err error + + c.lock.Lock() + defer c.lock.Unlock() + if c.access == nil && c.digest != "" { + c.size, c.access, _ = c.cache.cache.GetBlobData(c.digest) + } + if c.access == nil { + c.cache.lock.Lock() + defer c.cache.lock.Unlock() + + if c.digest != "" { + c.size, c.access, err = c.cache.cache.GetBlobData(c.digest) + if err != nil && !blobaccess.IsErrBlobNotFound(err) { + return nil, err + } + } + if c.access == nil { + c.size, c.digest, err = c.cache.cache.AddData(c.orig) + if err == nil { + _, c.access, err = c.cache.cache.GetBlobData(c.digest) + } + if err != nil { + return nil, err + } + c.orig.Close() + c.orig = nil + } + } + return c.access.Reader() +} + +func (c *cachedAccess) Close() error { + return nil +} + +func (c *cachedAccess) Size() int64 { + return c.size +} + +//////////////////////////////////////////////////////////////////////////////// + +type norefBlobSource struct { + BlobSource +} + +var _ BlobSource = (*norefBlobSource)(nil) + +func NoRefBlobSource(s BlobSource) BlobSource { return &norefBlobSource{s} } + +func (norefBlobSource) Ref() error { + return nil +} + +func (norefBlobSource) Unref() error { + return nil +} + +//////////////////////////////////////////////////////////////////////////////// + +type norefBlobSink struct { + BlobSink +} + +var _ BlobSink = (*norefBlobSink)(nil) + +func NoRefBlobSink(s BlobSink) BlobSink { return &norefBlobSink{s} } + +func (norefBlobSink) Ref() error { + return nil +} + +func (norefBlobSink) Unref() error { + return nil +} + +//////////////////////////////////////////////////////////////////////////////// + +type norefBlobCache struct { + BlobCache +} + +var _ BlobCache = (*norefBlobCache)(nil) + +func NoRefBlobCache(s BlobCache) BlobCache { return &norefBlobCache{s} } + +func (norefBlobCache) Ref() error { + return nil +} + +func (norefBlobCache) Unref() error { + return nil +} diff --git a/pkg/common/accessio/cache_test.go b/api/utils/accessio/cache_test.go similarity index 88% rename from pkg/common/accessio/cache_test.go rename to api/utils/accessio/cache_test.go index 07399def4..3f5b7e6c6 100644 --- a/pkg/common/accessio/cache_test.go +++ b/api/utils/accessio/cache_test.go @@ -8,9 +8,9 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" "github.com/opencontainers/go-digest" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/common/accessio" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + common "ocm.software/ocm/api/utils/misc" ) var _ = Describe("cache management", func() { diff --git a/api/utils/accessio/deprecated.go b/api/utils/accessio/deprecated.go new file mode 100644 index 000000000..332caf31d --- /dev/null +++ b/api/utils/accessio/deprecated.go @@ -0,0 +1,233 @@ +package accessio + +import ( + "crypto" + "io" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/sliceutils" + "github.com/mandelsoft/vfs/pkg/vfs" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/file" + "ocm.software/ocm/api/utils/iotools" +) + +const ( + // Deprecated: use blobaccess.BLOB_UNKNOWN_SIZE. + BLOB_UNKNOWN_SIZE = blobaccess.BLOB_UNKNOWN_SIZE + // Deprecated: use blobaccess.BLOB_UNKNOWN_DIGEST. + BLOB_UNKNOWN_DIGEST = blobaccess.BLOB_UNKNOWN_DIGEST +) + +const ( + // Deprecated: use blobaccess.KIND_BLOB. + KIND_BLOB = blobaccess.KIND_BLOB + // Deprecated: use blobaccess.KIND_MEDIATYPE. + KIND_MEDIATYPE = blobaccess.KIND_MEDIATYPE +) + +// Deprecated: use blobaccess.ErrBlobNotFound. +func ErrBlobNotFound(digest digest.Digest) error { + return errors.ErrNotFound(blobaccess.KIND_BLOB, digest.String()) +} + +// Deprecated: use blobaccess.IsErrBlobNotFound. +func IsErrBlobNotFound(err error) bool { + return errors.IsErrNotFoundKind(err, blobaccess.KIND_BLOB) +} + +//////////////////////////////////////////////////////////////////////////////// + +// DataSource describes some data plus its origin. +// Deprecated: use blobaccess.DataSource. +type DataSource = blobaccess.DataSource + +//////////////////////////////////////////////////////////////////////////////// + +// DataAccess describes the access to sequence of bytes. +// Deprecated: use blobaccess.DataAccess. +type DataAccess = blobaccess.DataAccess + +// BlobAccess describes the access to a blob. +// Deprecated: use blobaccess.BlobAccess. +type BlobAccess = blobaccess.BlobAccess + +//////////////////////////////////////////////////////////////////////////////// + +// Deprecated: use blobaccess.DataAccessForReaderFunction. +func DataAccessForReaderFunction(reader func() (io.ReadCloser, error), origin string) blobaccess.DataAccess { + return blobaccess.DataAccessForReaderFunction(reader, origin) +} + +//////////////////////////////////////////////////////////////////////////////// + +// Deprecated: use blobaccess.DataAccessForFile. +func DataAccessForFile(fs vfs.FileSystem, path string) blobaccess.DataAccess { + return file.DataAccess(fs, path) +} + +//////////////////////////////////////////////////////////////////////////////// + +// Deprecated: use blobaccess.DataAccessForBytes. +func DataAccessForBytes(data []byte, origin ...string) blobaccess.DataSource { + return blobaccess.DataAccessForData(data, origin...) +} + +// Deprecated: use blobaccess.DataAccessForString. +func DataAccessForString(data string, origin ...string) blobaccess.DataSource { + return blobaccess.DataAccessForData([]byte(data), origin...) +} + +//////////////////////////////////////////////////////////////////////////////// + +// BlobWithMimeType changes the mime type for a blob access +// by wrapping the given blob access. It does NOT provide +// a new view for the given blob access, so closing the resulting +// blob access will directly close the backing blob access. +// Deprecated: use blobaccess.WithMimeType. +func BlobWithMimeType(mimeType string, blob blobaccess.BlobAccess) blobaccess.BlobAccess { + return blobaccess.WithMimeType(mimeType, blob) +} + +//////////////////////////////////////////////////////////////////////////////// + +// AnnotatedBlobAccess provides access to the original underlying data source. +// Deprecated: use blobaccess.AnnotatedBlobAccess. +type AnnotatedBlobAccess[T blobaccess.DataAccess] interface { + blobaccess.BlobAccess + Source() T +} + +// BlobAccessForDataAccess wraps the general access object into a blob access. +// It closes the wrapped access, if closed. +// Deprecated: use blobaccess.ForDataAccess. +func BlobAccessForDataAccess[T blobaccess.DataAccess](digest digest.Digest, size int64, mimeType string, access T) blobaccess.AnnotatedBlobAccess[T] { + return blobaccess.ForDataAccess[T](digest, size, mimeType, access) +} + +// Deprecated: use blobaccess.ForString. +func BlobAccessForString(mimeType string, data string) blobaccess.BlobAccess { + return blobaccess.ForData(mimeType, []byte(data)) +} + +// Deprecated: use blobaccess.ForData. +func BlobAccessForData(mimeType string, data []byte) blobaccess.BlobAccess { + return blobaccess.ForData(mimeType, data) +} + +//////////////////////////////////////////////////////////////////////////////// + +// Deprecated: use blobaccess.ForFile. +func BlobAccessForFile(mimeType string, path string, fss ...vfs.FileSystem) blobaccess.BlobAccess { + return file.BlobAccess(mimeType, path, fss...) +} + +// Deprecated: use blobaccess.ForFileWithCloser. +func BlobAccessForFileWithCloser(closer io.Closer, mimeType string, path string, fss ...vfs.FileSystem) blobaccess.BlobAccess { + return file.BlobAccessWithCloser(closer, mimeType, path, fss...) +} + +//////////////////////////////////////////////////////////////////////////////// + +// Deprecated: use blobaccess.ForTemporaryFile. +func BlobAccessForTemporaryFile(mime string, temp vfs.File, fss ...vfs.FileSystem) blobaccess.BlobAccess { + return file.BlobAccessForTemporaryFile(mime, temp, file.WithFileSystem(fss...)) +} + +// Deprecated: use blobaccess.ForTemporaryFilePath. +func BlobAccessForTemporaryFilePath(mime string, temp string, fss ...vfs.FileSystem) blobaccess.BlobAccess { + return file.BlobAccessForTemporaryFilePath(mime, temp, file.WithFileSystem(fss...)) +} + +//////////////////////////////////////////////////////////////////////////////// + +// Deprecated: use blobaccess.NewTempFile. +func NewTempFile(fs vfs.FileSystem, dir string, pattern string) (*file.TempFile, error) { + return file.NewTempFile(dir, pattern, fs) +} + +//////////////////////////////////////////////////////////////////////////////// + +// Deprecated: use iotools.DigestReader. +type DigestReader = iotools.DigestReader + +// Deprecated: use iotools.NewDefaultDigestReader. +func NewDefaultDigestReader(r io.Reader) *iotools.DigestReader { + return iotools.NewDigestReaderWith(digest.Canonical, r) +} + +// Deprecated: use iotools.NewDigestReaderWith. +func NewDigestReaderWith(algorithm digest.Algorithm, r io.Reader) *iotools.DigestReader { + return iotools.NewDigestReaderWith(algorithm, r) +} + +// Deprecated: use iotools.NewDigestReaderWithHash. +func NewDigestReaderWithHash(hash crypto.Hash, r io.Reader) *iotools.DigestReader { + return iotools.NewDigestReaderWithHash(hash, r) +} + +// Deprecated: use iotools.VerifyingReader. +func VerifyingReader(r io.ReadCloser, digest digest.Digest) io.ReadCloser { + return iotools.VerifyingReader(r, digest) +} + +// Deprecated: use iotools.VerifyingReaderWithHash. +func VerifyingReaderWithHash(r io.ReadCloser, hash crypto.Hash, digest string) io.ReadCloser { + return iotools.VerifyingReaderWithHash(r, hash, digest) +} + +// Deprecated: use blobaccess.Digest. +func Digest(access blobaccess.DataAccess) (digest.Digest, error) { + return blobaccess.Digest(access) +} + +//////////////////////////////////////////////////////////////////////////////// + +// Deprecated: use iotools.DigestWriter. +type DigestWriter = iotools.DigestWriter + +// Deprecated: use iotools.NewDefaultDigestWriter. +func NewDefaultDigestWriter(w io.WriteCloser) *iotools.DigestWriter { + return iotools.NewDefaultDigestWriter(w) +} + +// Deprecated: use iotools.NewDigestWriterWith. +func NewDigestWriterWith(algorithm digest.Algorithm, w io.WriteCloser) *iotools.DigestWriter { + return iotools.NewDigestWriterWith(algorithm, w) +} + +//////////////////////////////////////////////////////////////////////////////// + +// Deprecated: use blobaccess.BlobData. +func BlobData(blob blobaccess.DataGetter, err error) ([]byte, error) { + if err != nil { + return nil, err + } + return blob.Get() +} + +// Deprecated: use blobaccess.BlobReader. +func BlobReader(blob blobaccess.DataReader, err error) (io.ReadCloser, error) { + if err != nil { + return nil, err + } + return blob.Reader() +} + +// Deprecated: use utils.FileSystem. +func FileSystem(fss ...vfs.FileSystem) vfs.FileSystem { + return utils.FileSystem(fss...) +} + +// Deprecated: use utils.DefaultedFileSystem. +func DefaultedFileSystem(def vfs.FileSystem, fss ...vfs.FileSystem) vfs.FileSystem { + return utils.DefaultedFileSystem(def, fss...) +} + +// Deprecated: use iotools.AddReaderCloser. +func AddCloser(reader io.ReadCloser, closer io.Closer, msg ...string) io.ReadCloser { + return iotools.AddReaderCloser(reader, closer, sliceutils.AsAny(msg)...) +} diff --git a/pkg/common/accessio/downloader/downloader.go b/api/utils/accessio/downloader/downloader.go similarity index 100% rename from pkg/common/accessio/downloader/downloader.go rename to api/utils/accessio/downloader/downloader.go diff --git a/api/utils/accessio/downloader/http/downloader.go b/api/utils/accessio/downloader/http/downloader.go new file mode 100644 index 000000000..839699d1a --- /dev/null +++ b/api/utils/accessio/downloader/http/downloader.go @@ -0,0 +1,45 @@ +package http + +import ( + "bytes" + "context" + "fmt" + "io" + "net/http" + + "ocm.software/ocm/api/utils/accessio/downloader" +) + +// Downloader simply uses the default HTTP client to download the contents of a URL. +type Downloader struct { + link string +} + +func NewDownloader(link string) downloader.Downloader { + return &Downloader{ + link: link, + } +} + +func (h *Downloader) Download(w io.WriterAt) error { + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, h.link, nil) + if err != nil { + return err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return fmt.Errorf("failed to get link: %w", err) + } + defer resp.Body.Close() + + var blob []byte + buf := bytes.NewBuffer(blob) + if _, err := io.Copy(buf, resp.Body); err != nil { + return fmt.Errorf("failed to copy response body: %w", err) + } + if _, err := w.WriteAt(buf.Bytes(), 0); err != nil { + return fmt.Errorf("failed to WriteAt to the writer: %w", err) + } + return nil +} diff --git a/pkg/common/accessio/downloader/s3/downloader.go b/api/utils/accessio/downloader/s3/downloader.go similarity index 100% rename from pkg/common/accessio/downloader/s3/downloader.go rename to api/utils/accessio/downloader/s3/downloader.go diff --git a/api/utils/accessio/format.go b/api/utils/accessio/format.go new file mode 100644 index 000000000..b382da52c --- /dev/null +++ b/api/utils/accessio/format.go @@ -0,0 +1,176 @@ +package accessio + +import ( + "archive/tar" + "compress/gzip" + "io" + "sort" + "strings" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/utils/compression" + "ocm.software/ocm/api/utils/tarutils" +) + +const KIND_FILEFORMAT = "file format" + +type FileFormat string + +func (f FileFormat) String() string { + return string(f) +} + +func (f FileFormat) Suffix() string { + return suffixes[f] +} + +func (o FileFormat) ApplyOption(options Options) error { + if o != "" { + options.SetFileFormat(o) + } + return nil +} + +const ( + FormatTar FileFormat = "tar" + FormatTGZ FileFormat = "tgz" + FormatDirectory FileFormat = "directory" + FormatNone FileFormat = "" +) + +var suffixes = map[FileFormat]string{ + FormatTar: "." + string(FormatTar), + FormatTGZ: "." + string(FormatTGZ), +} + +func ErrInvalidFileFormat(fmt string) error { + return errors.ErrInvalid(KIND_FILEFORMAT, fmt) +} + +//////////////////////////////////////////////////////////////////////////////// + +func GetFormats() []string { + return []string{string(FormatDirectory), string(FormatTar), string(FormatTGZ)} +} + +func GetFormatsFor[T any](fileFormats map[FileFormat]T) []string { + var def FileFormat + + list := []string{} + for k := range fileFormats { + // as favorite default, directory should be the first entry in the list + if k != FormatDirectory { + list = append(list, string(k)) + } else { + def = k + } + } + sort.Strings(list) + if def != "" { + return append(append(list[:0:0], string(def)), list...) + } + return list +} + +// FileFormatForTypeSpec returns the format hint provided +// by a type specification.The format hint is an optional +// suffix separated by a +. +func FileFormatForTypeSpec(t string) FileFormat { + i := strings.Index(t, "+") + if i < 0 { + return "" + } + return FileFormat(t[i+1:]) +} + +// TypeForTypeSpec returns the pure type info provided +// by a type specification.The format hint is an optional +// suffix separated by a +. +func TypeForTypeSpec(t string) string { + i := strings.Index(t, "+") + if i < 0 { + return t + } + return t[:i] +} + +//////////////////////////////////////////////////////////////////////////////// + +func CopyFileSystem(format FileFormat, srcfs vfs.FileSystem, src string, dstfs vfs.FileSystem, dst string, perm vfs.FileMode) error { + compr := compression.None + switch format { + case FormatDirectory: + return vfs.CopyDir(srcfs, src, dstfs, dst) + case FormatTGZ: + compr = compression.Gzip + fallthrough + case FormatTar: + file, err := dstfs.OpenFile(dst, vfs.O_CREATE|vfs.O_TRUNC|vfs.O_WRONLY, perm) + if err != nil { + return err + } + defer file.Close() + w, err := compr.Compressor(file, nil, nil) + if err != nil { + return err + } + return tarutils.PackFsIntoTar(srcfs, src, w, tarutils.TarFileSystemOptions{}) + default: + return errors.ErrUnknown(KIND_FILEFORMAT, format.String()) + } +} + +//////////////////////////////////////////////////////////////////////////////// + +func DetectFormat(path string, fs vfs.FileSystem) (*FileFormat, error) { + if fs == nil { + fs = _osfs + } + + fi, err := fs.Stat(path) + if err != nil { + return nil, err + } + + format := FormatDirectory + if !fi.IsDir() { + file, err := fs.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + return DetectFormatForFile(file) + } + return &format, nil +} + +func DetectFormatForFile(file vfs.File) (*FileFormat, error) { + fi, err := file.Stat() + if err != nil { + return nil, err + } + format := FormatDirectory + if !fi.IsDir() { + var r io.Reader + + defer file.Seek(0, io.SeekStart) + zip, err := gzip.NewReader(file) + if err == nil { + format = FormatTGZ + defer zip.Close() + r = zip + } else { + file.Seek(0, io.SeekStart) + format = FormatTar + r = file + } + t := tar.NewReader(r) + _, err = t.Next() + if err != nil { + return nil, err + } + } + return &format, nil +} diff --git a/pkg/common/accessio/limitwriter.go b/api/utils/accessio/limitwriter.go similarity index 100% rename from pkg/common/accessio/limitwriter.go rename to api/utils/accessio/limitwriter.go diff --git a/pkg/common/accessio/ondemandreader.go b/api/utils/accessio/ondemandreader.go similarity index 100% rename from pkg/common/accessio/ondemandreader.go rename to api/utils/accessio/ondemandreader.go diff --git a/pkg/common/accessio/opts.go b/api/utils/accessio/opts.go similarity index 99% rename from pkg/common/accessio/opts.go rename to api/utils/accessio/opts.go index c5d625bfe..2ac8e847c 100644 --- a/pkg/common/accessio/opts.go +++ b/api/utils/accessio/opts.go @@ -7,7 +7,7 @@ import ( "github.com/mandelsoft/vfs/pkg/osfs" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/common/compression" + "ocm.software/ocm/api/utils/compression" ) type Options interface { diff --git a/pkg/common/accessio/resettablereader.go b/api/utils/accessio/resettablereader.go similarity index 100% rename from pkg/common/accessio/resettablereader.go rename to api/utils/accessio/resettablereader.go diff --git a/pkg/common/accessio/resettablereader_test.go b/api/utils/accessio/resettablereader_test.go similarity index 100% rename from pkg/common/accessio/resettablereader_test.go rename to api/utils/accessio/resettablereader_test.go diff --git a/pkg/common/accessio/retry_test.go b/api/utils/accessio/retry_test.go similarity index 95% rename from pkg/common/accessio/retry_test.go rename to api/utils/accessio/retry_test.go index 470378ca0..1847317e7 100644 --- a/pkg/common/accessio/retry_test.go +++ b/api/utils/accessio/retry_test.go @@ -10,7 +10,7 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/common/accessio" + "ocm.software/ocm/api/utils/accessio" ) var ( diff --git a/pkg/common/accessio/suite_test.go b/api/utils/accessio/suite_test.go similarity index 100% rename from pkg/common/accessio/suite_test.go rename to api/utils/accessio/suite_test.go diff --git a/api/utils/accessio/utils.go b/api/utils/accessio/utils.go new file mode 100644 index 000000000..0571d8892 --- /dev/null +++ b/api/utils/accessio/utils.go @@ -0,0 +1,84 @@ +package accessio + +import ( + "fmt" + "io" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/utils/compression" +) + +type closableReader struct { + reader io.Reader +} + +func ReadCloser(r io.Reader) io.ReadCloser { return closableReader{r} } + +func (r closableReader) Read(p []byte) (n int, err error) { + return r.reader.Read(p) +} + +func (r closableReader) Close() error { + return nil +} + +//////////////////////////////////////////////////////////////////////////////// + +// NopWriteCloser returns a ReadCloser with a no-op Close method wrapping +// the provided Reader r. +func NopWriteCloser(w io.Writer) io.WriteCloser { + return compression.NopWriteCloser(w) +} + +//////////////////////////////////////////////////////////////////////////////// + +type once struct { + callbacks []CloserCallback + closer io.Closer +} + +type CloserCallback func() + +func OnceCloser(c io.Closer, callbacks ...CloserCallback) io.Closer { + return &once{callbacks, c} +} + +func (c *once) Close() error { + if c.closer == nil { + return nil + } + + t := c.closer + c.closer = nil + err := t.Close() + + for _, cb := range c.callbacks { + cb() + } + + if err != nil { + return fmt.Errorf("unable to close: %w", err) + } + + return nil +} + +func Close(closer ...io.Closer) error { + if len(closer) == 0 { + return nil + } + list := errors.ErrList() + for _, c := range closer { + if c != nil { + list.Add(c.Close()) + } + } + return list.Result() +} + +type Closer func() error + +func (c Closer) Close() error { + return c() +} diff --git a/pkg/common/accessio/wrapper.go b/api/utils/accessio/wrapper.go similarity index 92% rename from pkg/common/accessio/wrapper.go rename to api/utils/accessio/wrapper.go index c768e6efa..6c7137dcc 100644 --- a/pkg/common/accessio/wrapper.go +++ b/api/utils/accessio/wrapper.go @@ -6,8 +6,8 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/opencontainers/go-digest" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/iotools" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/iotools" ) type Writer interface { diff --git a/pkg/common/accessobj/accessobject.go b/api/utils/accessobj/accessobject.go similarity index 98% rename from pkg/common/accessobj/accessobject.go rename to api/utils/accessobj/accessobject.go index 0a1380b9c..4005dbc62 100644 --- a/pkg/common/accessobj/accessobject.go +++ b/api/utils/accessobj/accessobject.go @@ -7,7 +7,7 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/common/accessio" + "ocm.software/ocm/api/utils/accessio" ) type DescriptorHandlerFactory func(fs vfs.FileSystem) StateHandler diff --git a/pkg/common/accessobj/accessstate.go b/api/utils/accessobj/accessstate.go similarity index 97% rename from pkg/common/accessobj/accessstate.go rename to api/utils/accessobj/accessstate.go index b7abad902..3aa079059 100644 --- a/pkg/common/accessobj/accessstate.go +++ b/api/utils/accessobj/accessstate.go @@ -9,9 +9,9 @@ import ( "github.com/modern-go/reflect2" "github.com/opencontainers/go-digest" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/blobaccess/file" - "github.com/open-component-model/ocm/pkg/common/accessio" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/file" ) // These objects deal with descriptor based state descriptions diff --git a/pkg/common/accessobj/cachedblob.go b/api/utils/accessobj/cachedblob.go similarity index 87% rename from pkg/common/accessobj/cachedblob.go rename to api/utils/accessobj/cachedblob.go index cc2281a8a..7746fe648 100644 --- a/pkg/common/accessobj/cachedblob.go +++ b/api/utils/accessobj/cachedblob.go @@ -7,12 +7,12 @@ import ( "github.com/opencontainers/go-digest" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" - "github.com/open-component-model/ocm/pkg/blobaccess/file" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/tmpcache" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/datacontext/attrs/tmpcache" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/bpi" + "ocm.software/ocm/api/utils/blobaccess/file" ) type CachedBlobAccess struct { diff --git a/pkg/common/accessobj/cachedblob_test.go b/api/utils/accessobj/cachedblob_test.go similarity index 85% rename from pkg/common/accessobj/cachedblob_test.go rename to api/utils/accessobj/cachedblob_test.go index be1fbcb7d..bf819f8bd 100644 --- a/pkg/common/accessobj/cachedblob_test.go +++ b/api/utils/accessobj/cachedblob_test.go @@ -9,13 +9,13 @@ import ( "github.com/mandelsoft/vfs/pkg/osfs" "github.com/mandelsoft/vfs/pkg/vfs" - "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/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/tmpcache" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/vfsattr" - "github.com/open-component-model/ocm/pkg/mime" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/datacontext/attrs/tmpcache" + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/mime" ) type Source struct { diff --git a/api/utils/accessobj/check.go b/api/utils/accessobj/check.go new file mode 100644 index 000000000..1294f3334 --- /dev/null +++ b/api/utils/accessobj/check.go @@ -0,0 +1,81 @@ +package accessobj + +import ( + "archive/tar" + "io" + + "github.com/mandelsoft/filepath/pkg/filepath" + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/utils/compression" +) + +func mapErr(forced bool, err error) (bool, bool, error) { + if !forced { + return false, false, nil + } + return false, true, err +} + +// CheckFile returns create, acceptable, error. +func CheckFile(kind string, createHint string, forcedType bool, path string, fs vfs.FileSystem, descriptorname string) (bool, bool, error) { + info, err := fs.Stat(path) + if err != nil { + if createHint == kind { + if vfs.IsErrNotExist(err) { + return true, true, nil + } + } + return mapErr(forcedType, err) + } + accepted := false + if !info.IsDir() { + file, err := fs.Open(path) + if err != nil { + return mapErr(forcedType, err) + } + defer file.Close() + forcedType = false + r, _, err := compression.AutoDecompress(file) + if err != nil { + return mapErr(forcedType, err) + } + tr := tar.NewReader(r) + for { + header, err := tr.Next() + if err != nil { + if errors.Is(err, io.EOF) { + break + } + return mapErr(forcedType, err) + } + + switch header.Typeflag { + case tar.TypeReg: + if header.Name == descriptorname { + accepted = true + break + } + } + } + } else { + if forcedType { + entries, err := vfs.ReadDir(fs, path) + if err == nil && len(entries) > 0 { + forcedType = false + } + } + if ok, err := vfs.FileExists(fs, filepath.Join(path, descriptorname)); !ok || err != nil { + if err != nil { + return mapErr(forcedType, err) + } + } else { + accepted = ok + } + } + if !accepted { + return mapErr(forcedType, errors.Newf("%s: no %s", path, kind)) + } + return false, true, nil +} diff --git a/api/utils/accessobj/filesystemaccess.go b/api/utils/accessobj/filesystemaccess.go new file mode 100644 index 000000000..b25accb6a --- /dev/null +++ b/api/utils/accessobj/filesystemaccess.go @@ -0,0 +1,155 @@ +package accessobj + +import ( + "fmt" + "io" + "os" + "sync" + + "github.com/mandelsoft/vfs/pkg/vfs" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/file" + "ocm.software/ocm/api/utils/mime" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/tarutils" +) + +type FileSystemBlobAccess struct { + sync.RWMutex + base *AccessObject +} + +func NewFileSystemBlobAccess(access *AccessObject) *FileSystemBlobAccess { + return &FileSystemBlobAccess{ + base: access, + } +} + +func (a *FileSystemBlobAccess) Access() *AccessObject { + return a.base +} + +func (a *FileSystemBlobAccess) SetReadOnly() { + a.base.SetReadOnly() +} + +func (a *FileSystemBlobAccess) IsReadOnly() bool { + return a.base.IsReadOnly() +} + +func (a *FileSystemBlobAccess) IsClosed() bool { + return a.base.IsClosed() +} + +func (a *FileSystemBlobAccess) Write(path string, mode vfs.FileMode, opts ...accessio.Option) error { + return a.base.Write(path, mode, opts...) +} + +func (a *FileSystemBlobAccess) Update() error { + return a.base.Update() +} + +func (a *FileSystemBlobAccess) Close() error { + return a.base.Close() +} + +func (a *FileSystemBlobAccess) GetState() State { + return a.base.GetState() +} + +// DigestPath returns the path to the blob for a given name. +func (a *FileSystemBlobAccess) DigestPath(digest digest.Digest) string { + return a.BlobPath(common.DigestToFileName(digest)) +} + +// BlobPath returns the path to the blob for a given name. +func (a *FileSystemBlobAccess) BlobPath(name string) string { + return a.base.GetInfo().SubPath(name) +} + +func (a *FileSystemBlobAccess) GetBlobData(digest digest.Digest) (int64, blobaccess.DataAccess, error) { + if a.IsClosed() { + return blobaccess.BLOB_UNKNOWN_SIZE, nil, accessio.ErrClosed + } + path := a.DigestPath(digest) + if ok, err := vfs.FileExists(a.base.GetFileSystem(), path); ok { + return blobaccess.BLOB_UNKNOWN_SIZE, file.DataAccess(a.base.GetFileSystem(), path), nil + } else { + if err != nil { + return blobaccess.BLOB_UNKNOWN_SIZE, nil, err + } + return blobaccess.BLOB_UNKNOWN_SIZE, nil, blobaccess.ErrBlobNotFound(digest) + } +} + +func (a *FileSystemBlobAccess) GetBlobDataByName(name string) (blobaccess.DataAccess, error) { + if a.IsClosed() { + return nil, accessio.ErrClosed + } + + path := a.BlobPath(name) + if ok, err := vfs.IsDir(a.base.GetFileSystem(), path); ok { + tempfile, err := file.NewTempFile(os.TempDir(), "COMPARCH") + if err != nil { + return nil, err + } + err = tarutils.PackFsIntoTar(a.base.GetFileSystem(), path, tempfile.Writer(), tarutils.TarFileSystemOptions{}) + if err != nil { + return nil, err + } + return tempfile.AsBlob(mime.MIME_TAR), nil + } else { + if err != nil { + return nil, err + } + + if ok, err := vfs.FileExists(a.base.GetFileSystem(), path); ok { + return file.DataAccess(a.base.GetFileSystem(), path), nil + } else { + if err != nil { + return nil, err + } + return nil, blobaccess.ErrBlobNotFound(digest.Digest(name)) + } + } +} + +func (a *FileSystemBlobAccess) AddBlob(blob blobaccess.BlobAccess) error { + if a.base.IsClosed() { + return accessio.ErrClosed + } + + if a.base.IsReadOnly() { + return accessio.ErrReadOnly + } + + path := a.DigestPath(blob.Digest()) + + if ok, err := vfs.FileExists(a.base.GetFileSystem(), path); ok { + return nil + } else if err != nil { + return fmt.Errorf("failed to check if '%s' file exists: %w", path, err) + } + + r, err := blob.Reader() + if err != nil { + return fmt.Errorf("unable to read blob: %w", err) + } + + defer r.Close() + w, err := a.base.GetFileSystem().OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, a.base.GetMode()&0o666) + if err != nil { + return fmt.Errorf("unable to open file '%s': %w", path, err) + } + + _, err = io.Copy(w, r) + if err != nil { + w.Close() + + return fmt.Errorf("unable to copy blob content: %w", err) + } + return w.Close() +} diff --git a/pkg/common/accessobj/format-directory.go b/api/utils/accessobj/format-directory.go similarity index 98% rename from pkg/common/accessobj/format-directory.go rename to api/utils/accessobj/format-directory.go index dd99c25a3..04b55c388 100644 --- a/pkg/common/accessobj/format-directory.go +++ b/api/utils/accessobj/format-directory.go @@ -9,7 +9,7 @@ import ( "github.com/mandelsoft/vfs/pkg/projectionfs" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/common/accessio" + "ocm.software/ocm/api/utils/accessio" ) var FormatDirectory = DirectoryHandler{} diff --git a/pkg/common/accessobj/format-tar.go b/api/utils/accessobj/format-tar.go similarity index 96% rename from pkg/common/accessobj/format-tar.go rename to api/utils/accessobj/format-tar.go index d57302833..1e5e1ecd9 100644 --- a/pkg/common/accessobj/format-tar.go +++ b/api/utils/accessobj/format-tar.go @@ -9,9 +9,9 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/common/compression" - "github.com/open-component-model/ocm/pkg/utils/tarutils" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/compression" + "ocm.software/ocm/api/utils/tarutils" ) var FormatTAR = NewTarHandler() diff --git a/api/utils/accessobj/format-tgz.go b/api/utils/accessobj/format-tgz.go new file mode 100644 index 000000000..894215d48 --- /dev/null +++ b/api/utils/accessobj/format-tgz.go @@ -0,0 +1,12 @@ +package accessobj + +import ( + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/compression" +) + +var FormatTGZ = NewTarHandlerWithCompression(accessio.FormatTGZ, compression.Gzip) + +func init() { + RegisterFormat(FormatTGZ) +} diff --git a/api/utils/accessobj/format.go b/api/utils/accessobj/format.go new file mode 100644 index 000000000..9a2f76c2b --- /dev/null +++ b/api/utils/accessobj/format.go @@ -0,0 +1,174 @@ +package accessobj + +import ( + "fmt" + "io" + "sync" + "time" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/utils/accessio" +) + +const KIND_FILEFORMAT = accessio.KIND_FILEFORMAT + +const ( + DirMode = 0o755 + FileMode = 0o644 +) + +var ModTime = time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) + +type FileFormat = accessio.FileFormat + +type FormatHandler interface { + accessio.Option + + Format() accessio.FileFormat + + Open(info AccessObjectInfo, acc AccessMode, path string, opts accessio.Options) (*AccessObject, error) + Create(info AccessObjectInfo, path string, opts accessio.Options, mode vfs.FileMode) (*AccessObject, error) + Write(obj *AccessObject, path string, opts accessio.Options, mode vfs.FileMode) error +} + +//////////////////////////////////////////////////////////////////////////////// + +var ( + fileFormats = map[FileFormat]FormatHandler{} + lock sync.RWMutex +) + +func RegisterFormat(f FormatHandler) { + lock.Lock() + defer lock.Unlock() + fileFormats[f.Format()] = f +} + +func GetFormat(name FileFormat) FormatHandler { + lock.RLock() + defer lock.RUnlock() + return fileFormats[name] +} + +func GetFormats() map[accessio.FileFormat]FormatHandler { + lock.RLock() + defer lock.RUnlock() + + m := map[accessio.FileFormat]FormatHandler{} + for k, v := range fileFormats { + m[k] = v + } + return m +} + +//////////////////////////////////////////////////////////////////////////////// + +type Closer interface { + Close(*AccessObject) error +} + +type CloserFunction func(*AccessObject) error + +func (f CloserFunction) Close(obj *AccessObject) error { + return f(obj) +} + +//////////////////////////////////////////////////////////////////////////////// + +type Setup interface { + Setup(vfs.FileSystem) error +} + +type SetupFunction func(vfs.FileSystem) error + +func (f SetupFunction) Setup(fs vfs.FileSystem) error { + return f(fs) +} + +//////////////////////////////////////////////////////////////////////////////// + +type StandardReaderHandler interface { + Write(obj *AccessObject, path string, opts accessio.Options, mode vfs.FileMode) error + NewFromReader(info AccessObjectInfo, acc AccessMode, in io.Reader, opts accessio.Options, closer Closer) (*AccessObject, error) +} + +func DefaultOpenOptsFileHandling(kind string, info AccessObjectInfo, acc AccessMode, path string, opts accessio.Options, handler StandardReaderHandler) (*AccessObject, error) { + if err := opts.ValidForPath(path); err != nil { + return nil, err + } + var file vfs.File + var err error + var closer Closer + + reader := opts.GetReader() + switch { + case reader != nil: + case opts.GetFile() == nil: + // we expect that the path point to a tar + file, err = opts.GetPathFileSystem().Open(path) + if err != nil { + return nil, fmt.Errorf("unable to open %s from %s: %w", kind, path, err) + } + defer file.Close() + default: + file = opts.GetFile() + } + if file != nil { + reader = file + fi, err := file.Stat() + if err != nil { + return nil, err + } + closer = CloserFunction(func(obj *AccessObject) error { return handler.Write(obj, path, opts, fi.Mode()) }) + } + return handler.NewFromReader(info, acc, reader, opts, closer) +} + +func DefaultCreateOptsFileHandling(kind string, info AccessObjectInfo, path string, opts accessio.Options, mode vfs.FileMode, handler StandardReaderHandler) (*AccessObject, error) { + if err := opts.ValidForPath(path); err != nil { + return nil, err + } + if opts.GetReader() != nil { + return nil, errors.ErrNotSupported("reader option not supported") + } + if opts.GetFile() == nil { + ok, err := vfs.Exists(opts.GetPathFileSystem(), path) + if err != nil { + return nil, err + } + if ok { + return nil, vfs.ErrExist + } + } + + return NewAccessObject(info, ACC_CREATE, opts.GetRepresentation(), nil, CloserFunction(func(obj *AccessObject) error { return handler.Write(obj, path, opts, mode) }), DirMode) +} + +//////////////////////////////////////////////////////////////////////////////// + +// MapType maps a given type name to an effective type and a format. +func MapType(hint string, efftyp string, deffmt accessio.FileFormat, useFormats bool, alt ...string) (string, accessio.FileFormat) { + typ := accessio.TypeForTypeSpec(hint) + f := accessio.FileFormatForTypeSpec(hint) + if f != "" { + deffmt = f + } + if typ == efftyp { + return efftyp, deffmt + } + for _, t := range alt { + if typ == t { + return efftyp, deffmt + } + } + if useFormats { + for _, f := range accessio.GetFormats() { + if hint == f { + return efftyp, accessio.FileFormat(f) + } + } + } + return "", "" +} diff --git a/pkg/common/accessobj/suite_test.go b/api/utils/accessobj/suite_test.go similarity index 100% rename from pkg/common/accessobj/suite_test.go rename to api/utils/accessobj/suite_test.go diff --git a/api/utils/accessobj/utils.go b/api/utils/accessobj/utils.go new file mode 100644 index 000000000..725e1649b --- /dev/null +++ b/api/utils/accessobj/utils.go @@ -0,0 +1,58 @@ +package accessobj + +import ( + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/vfs/pkg/osfs" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/utils/accessio" +) + +type FilesystemSetup func(fs vfs.FileSystem, mode vfs.FileMode) error + +// InternalRepresentationFilesystem defaults a filesystem to temp filesystem and adapts. +func InternalRepresentationFilesystem(acc AccessMode, fs vfs.FileSystem, setup FilesystemSetup, mode vfs.FileMode) (bool, vfs.FileSystem, error) { + var err error + + tmp := false + if fs == nil { + fs, err = osfs.NewTempFileSystem() + if err != nil { + return false, nil, err + } + tmp = true + } + if !acc.IsReadonly() && setup != nil { + err = setup(fs, mode) + if err != nil { + return false, nil, err + } + } + return tmp, fs, err +} + +func HandleAccessMode(acc AccessMode, path string, opts accessio.Options, olist ...accessio.Option) (accessio.Options, bool, error) { + ok := true + o, err := accessio.AccessOptions(opts, olist...) + if err != nil { + return nil, false, err + } + if o.GetFile() == nil && o.GetReader() == nil { + ok, err = vfs.Exists(o.GetPathFileSystem(), path) + if err != nil { + return o, false, err + } + } + if !ok { + if !acc.IsCreate() { + return o, false, errors.ErrNotFoundWrap(vfs.ErrNotExist, "file", path) + } + if o.GetFileFormat() == nil { + o.SetFileFormat(accessio.FormatDirectory) + } + return o, true, nil + } + + err = o.DefaultForPath(path) + return o, false, err +} diff --git a/api/utils/blobaccess/blobaccess/access.go b/api/utils/blobaccess/blobaccess/access.go new file mode 100644 index 000000000..40a1cf35c --- /dev/null +++ b/api/utils/blobaccess/blobaccess/access.go @@ -0,0 +1,73 @@ +package blobaccess + +import ( + "bytes" + "io" + + "github.com/mandelsoft/filepath/pkg/filepath" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/utils/blobaccess/bpi" + "ocm.software/ocm/api/utils/iotools" + mimetypes "ocm.software/ocm/api/utils/mime" +) + +type bytesAccess struct { + _nopCloser + data []byte + origin string +} + +func DataAccessForData(data []byte, origin ...string) bpi.DataSource { + path := "" + if len(origin) > 0 { + path = filepath.Join(origin...) + } + return &bytesAccess{data: data, origin: path} +} + +func DataAccessForString(data string, origin ...string) bpi.DataSource { + return DataAccessForData([]byte(data), origin...) +} + +func (a *bytesAccess) Get() ([]byte, error) { + return a.data, nil +} + +func (a *bytesAccess) Reader() (io.ReadCloser, error) { + return iotools.ReadCloser(bytes.NewReader(a.data)), nil +} + +func (a *bytesAccess) Origin() string { + return a.origin +} + +//////////////////////////////////////////////////////////////////////////////// + +// ForString wraps a string into a BlobAccess, which does not need a close. +func ForString(mime string, data string) bpi.BlobAccess { + if mime == "" { + mime = mimetypes.MIME_TEXT + } + return ForData(mime, []byte(data)) +} + +func ProviderForString(mime, data string) bpi.BlobAccessProvider { + return bpi.BlobAccessProviderFunction(func() (bpi.BlobAccess, error) { + return ForString(mime, data), nil + }) +} + +// ForData wraps data into a BlobAccess, which does not need a close. +func ForData(mime string, data []byte) bpi.BlobAccess { + if mime == "" { + mime = mimetypes.MIME_OCTET + } + return bpi.ForStaticDataAccessAndMeta(mime, DataAccessForData(data), digest.FromBytes(data), int64(len(data))) +} + +func ProviderForData(mime string, data []byte) bpi.BlobAccessProvider { + return bpi.BlobAccessProviderFunction(func() (bpi.BlobAccess, error) { + return ForData(mime, data), nil + }) +} diff --git a/pkg/blobaccess/blobaccess/blobaccess_test.go b/api/utils/blobaccess/blobaccess/blobaccess_test.go similarity index 87% rename from pkg/blobaccess/blobaccess/blobaccess_test.go rename to api/utils/blobaccess/blobaccess/blobaccess_test.go index ca6b6ed54..73fdb8559 100644 --- a/pkg/blobaccess/blobaccess/blobaccess_test.go +++ b/api/utils/blobaccess/blobaccess/blobaccess_test.go @@ -10,10 +10,10 @@ import ( "github.com/mandelsoft/vfs/pkg/osfs" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" - "github.com/open-component-model/ocm/pkg/blobaccess/file" - "github.com/open-component-model/ocm/pkg/mime" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/bpi" + "ocm.software/ocm/api/utils/blobaccess/file" + "ocm.software/ocm/api/utils/mime" ) var _ = Describe("blob access ref counting", func() { diff --git a/pkg/blobaccess/blobaccess/cached.go b/api/utils/blobaccess/blobaccess/cached.go similarity index 83% rename from pkg/blobaccess/blobaccess/cached.go rename to api/utils/blobaccess/blobaccess/cached.go index c3a297c10..f83f508e6 100644 --- a/pkg/blobaccess/blobaccess/cached.go +++ b/api/utils/blobaccess/blobaccess/cached.go @@ -5,8 +5,8 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/blobaccess/file" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/blobaccess/file" ) func ForCachedBlobAccess(blob BlobAccess, fss ...vfs.FileSystem) (BlobAccess, error) { diff --git a/pkg/blobaccess/blobaccess/compress.go b/api/utils/blobaccess/blobaccess/compress.go similarity index 95% rename from pkg/blobaccess/blobaccess/compress.go rename to api/utils/blobaccess/blobaccess/compress.go index c841c292a..1747d8867 100644 --- a/pkg/blobaccess/blobaccess/compress.go +++ b/api/utils/blobaccess/blobaccess/compress.go @@ -9,9 +9,9 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/opencontainers/go-digest" - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" - "github.com/open-component-model/ocm/pkg/common/compression" - "github.com/open-component-model/ocm/pkg/mime" + "ocm.software/ocm/api/utils/blobaccess/bpi" + "ocm.software/ocm/api/utils/compression" + "ocm.software/ocm/api/utils/mime" ) //////////////////////////////////////////////////////////////////////////////// diff --git a/pkg/blobaccess/blobaccess/compress_test.go b/api/utils/blobaccess/blobaccess/compress_test.go similarity index 94% rename from pkg/blobaccess/blobaccess/compress_test.go rename to api/utils/blobaccess/blobaccess/compress_test.go index 638b3a672..928e4bf52 100644 --- a/pkg/blobaccess/blobaccess/compress_test.go +++ b/api/utils/blobaccess/blobaccess/compress_test.go @@ -9,8 +9,8 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/mime" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/mime" ) var _ = Describe("temp file management", func() { diff --git a/pkg/blobaccess/blobaccess/data.go b/api/utils/blobaccess/blobaccess/data.go similarity index 96% rename from pkg/blobaccess/blobaccess/data.go rename to api/utils/blobaccess/blobaccess/data.go index 9c2e56ddd..0f73f70a2 100644 --- a/pkg/blobaccess/blobaccess/data.go +++ b/api/utils/blobaccess/blobaccess/data.go @@ -6,7 +6,7 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" + "ocm.software/ocm/api/utils/blobaccess/bpi" ) type GenericData = interface{} diff --git a/pkg/blobaccess/blobaccess/dataaccess.go b/api/utils/blobaccess/blobaccess/dataaccess.go similarity index 100% rename from pkg/blobaccess/blobaccess/dataaccess.go rename to api/utils/blobaccess/blobaccess/dataaccess.go diff --git a/api/utils/blobaccess/blobaccess/deprecated.go b/api/utils/blobaccess/blobaccess/deprecated.go new file mode 100644 index 000000000..bb891ae53 --- /dev/null +++ b/api/utils/blobaccess/blobaccess/deprecated.go @@ -0,0 +1,21 @@ +package blobaccess + +import ( + "github.com/mandelsoft/vfs/pkg/vfs" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/utils/blobaccess/bpi" + "ocm.software/ocm/api/utils/blobaccess/file" +) + +// ForTemporaryFile wraps a temporary file into a BlobAccess, which does not need a close. +// Deprecated: ForTemporaryFile. +func ForTemporaryFileWithMeta(mime string, digest digest.Digest, size int64, temp vfs.File, fss ...vfs.FileSystem) bpi.BlobAccess { + return file.BlobAccessForTemporaryFile(mime, temp, file.WithFileSystem(fss...), file.WithDigest(digest), file.WithSize(size)) +} + +// ForTemporaryFile wraps a temporary file into a BlobAccess, which does not need a close. +// Deprecated: ForTemporaryFilePath. +func ForTemporaryFilePathWithMeta(mime string, digest digest.Digest, size int64, temp string, fss ...vfs.FileSystem) BlobAccess { + return file.BlobAccessForTemporaryFilePath(mime, temp, file.WithFileSystem(fss...), file.WithDigest(digest), file.WithSize(size)) +} diff --git a/pkg/blobaccess/blobaccess/digest.go b/api/utils/blobaccess/blobaccess/digest.go similarity index 84% rename from pkg/blobaccess/blobaccess/digest.go rename to api/utils/blobaccess/blobaccess/digest.go index 5e02a5d1b..d83dac55e 100644 --- a/pkg/blobaccess/blobaccess/digest.go +++ b/api/utils/blobaccess/blobaccess/digest.go @@ -3,7 +3,7 @@ package blobaccess import ( "github.com/opencontainers/go-digest" - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" + "ocm.software/ocm/api/utils/blobaccess/bpi" ) func Digest(access bpi.DataAccess) (digest.Digest, error) { diff --git a/pkg/blobaccess/blobaccess/doc.go b/api/utils/blobaccess/blobaccess/doc.go similarity index 100% rename from pkg/blobaccess/blobaccess/doc.go rename to api/utils/blobaccess/blobaccess/doc.go diff --git a/api/utils/blobaccess/blobaccess/interface.go b/api/utils/blobaccess/blobaccess/interface.go new file mode 100644 index 000000000..58922ca8d --- /dev/null +++ b/api/utils/blobaccess/blobaccess/interface.go @@ -0,0 +1,30 @@ +package blobaccess + +import ( + "ocm.software/ocm/api/utils/blobaccess/internal" +) + +const ( + KIND_BLOB = internal.KIND_BLOB + KIND_MEDIATYPE = internal.KIND_MEDIATYPE + + BLOB_UNKNOWN_SIZE = internal.BLOB_UNKNOWN_SIZE + BLOB_UNKNOWN_DIGEST = internal.BLOB_UNKNOWN_DIGEST +) + +type ( + DataAccess = internal.DataAccess + DataReader = internal.DataReader + DataGetter = internal.DataGetter +) + +type ( + BlobAccess = internal.BlobAccess + BlobAccessProvider = internal.BlobAccessProvider + + DataSource = internal.DataSource + DigestSource = internal.DigestSource + MimeType = internal.MimeType +) + +type FileLocation = internal.FileLocation diff --git a/pkg/blobaccess/blobaccess/other.go b/api/utils/blobaccess/blobaccess/other.go similarity index 97% rename from pkg/blobaccess/blobaccess/other.go rename to api/utils/blobaccess/blobaccess/other.go index d751891c9..3e35ef143 100644 --- a/pkg/blobaccess/blobaccess/other.go +++ b/api/utils/blobaccess/blobaccess/other.go @@ -7,8 +7,8 @@ package blobaccess import ( "github.com/opencontainers/go-digest" - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" - "github.com/open-component-model/ocm/pkg/runtimefinalizer" + "ocm.software/ocm/api/utils/blobaccess/bpi" + "ocm.software/ocm/api/utils/runtimefinalizer" ) //////////////////////////////////////////////////////////////////////////////// diff --git a/pkg/blobaccess/blobaccess/standard.go b/api/utils/blobaccess/blobaccess/standard.go similarity index 91% rename from pkg/blobaccess/blobaccess/standard.go rename to api/utils/blobaccess/blobaccess/standard.go index bba5f30fe..52024df97 100644 --- a/pkg/blobaccess/blobaccess/standard.go +++ b/api/utils/blobaccess/blobaccess/standard.go @@ -4,8 +4,8 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/opencontainers/go-digest" - "github.com/open-component-model/ocm/pkg/refmgmt" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/refmgmt" ) var ErrClosed = refmgmt.ErrClosed diff --git a/pkg/blobaccess/blobaccess/suite_test.go b/api/utils/blobaccess/blobaccess/suite_test.go similarity index 100% rename from pkg/blobaccess/blobaccess/suite_test.go rename to api/utils/blobaccess/blobaccess/suite_test.go diff --git a/api/utils/blobaccess/blobaccess/utils.go b/api/utils/blobaccess/blobaccess/utils.go new file mode 100644 index 000000000..daa69f4cf --- /dev/null +++ b/api/utils/blobaccess/blobaccess/utils.go @@ -0,0 +1,78 @@ +package blobaccess + +import ( + "io" + + "ocm.software/ocm/api/utils/blobaccess/bpi" + "ocm.software/ocm/api/utils/iotools" +) + +func Cast[I interface{}](acc BlobAccess) I { + return bpi.Cast[I](acc) +} + +//////////////////////////////////////////////////////////////////////////////// + +// BlobData can be applied directly to a function result +// providing a BlobAccess to get the data for the provided blob. +// If the blob access providing function provides an error +// result it is passed to the caller. +func BlobData(blob DataGetter, err ...error) ([]byte, error) { + if len(err) > 0 && err[0] != nil { + return nil, err[0] + } + return blob.Get() +} + +// BlobReader can be applied directly to a function result +// providing a BlobAccess to get a reader for the provided blob. +// If the blob access providing function provides an error +// result it is passed to the caller. +func BlobReader(blob DataReader, err ...error) (io.ReadCloser, error) { + if len(err) > 0 && err[0] != nil { + return nil, err[0] + } + return blob.Reader() +} + +// DataFromProvider extracts the data for a given BlobAccess provider. +func DataFromProvider(s BlobAccessProvider) ([]byte, error) { + blob, err := s.BlobAccess() + if err != nil { + return nil, err + } + defer blob.Close() + return blob.Get() +} + +// ReaderFromProvider gets a reader for a BlobAccess provided by +// a BlobAccesssProvider. Closing the Reader also closes the BlobAccess. +func ReaderFromProvider(s BlobAccessProvider) (io.ReadCloser, error) { + blob, err := s.BlobAccess() + if err != nil { + return nil, err + } + r, err := blob.Reader() + if err != nil { + blob.Close() + return nil, err + } + return iotools.AddReaderCloser(r, blob), nil +} + +// MimeReaderFromProvider gets a reader for a BlobAccess provided by +// a BlobAccesssProvider. Closing the Reader also closes the BlobAccess. +// Additionally, the mime type of the blob is returned. +func MimeReaderFromProvider(s BlobAccessProvider) (io.ReadCloser, string, error) { + blob, err := s.BlobAccess() + if err != nil { + return nil, "", err + } + mime := blob.MimeType() + r, err := blob.Reader() + if err != nil { + blob.Close() + return nil, "", err + } + return iotools.AddReaderCloser(r, blob), mime, nil +} diff --git a/api/utils/blobaccess/bpi/interface.go b/api/utils/blobaccess/bpi/interface.go new file mode 100644 index 000000000..f305cba4e --- /dev/null +++ b/api/utils/blobaccess/bpi/interface.go @@ -0,0 +1,52 @@ +package bpi + +import ( + "github.com/mandelsoft/goutils/errors" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/blobaccess/internal" + "ocm.software/ocm/api/utils/refmgmt" +) + +const ( + KIND_BLOB = internal.KIND_BLOB + KIND_MEDIATYPE = internal.KIND_MEDIATYPE + + BLOB_UNKNOWN_SIZE = internal.BLOB_UNKNOWN_SIZE + BLOB_UNKNOWN_DIGEST = internal.BLOB_UNKNOWN_DIGEST +) + +var ErrClosed = refmgmt.ErrClosed + +type DataAccess = internal.DataAccess + +type ( + BlobAccess = internal.BlobAccess + BlobAccessBase = internal.BlobAccessBase + BlobAccessProvider = internal.BlobAccessProvider + + Validatable = utils.Validatable + + DataReader = internal.DataReader + DataGetter = internal.DataGetter + DataSource = internal.DataSource + DigestSource = internal.DigestSource + MimeType = internal.MimeType +) + +type FileLocation = internal.FileLocation + +type BlobAccessProviderFunction func() (BlobAccess, error) + +func (p BlobAccessProviderFunction) BlobAccess() (BlobAccess, error) { + return p() +} + +func ErrBlobNotFound(digest digest.Digest) error { + return errors.ErrNotFound(KIND_BLOB, digest.String()) +} + +func IsErrBlobNotFound(err error) bool { + return errors.IsErrNotFoundKind(err, KIND_BLOB) +} diff --git a/api/utils/blobaccess/bpi/utils.go b/api/utils/blobaccess/bpi/utils.go new file mode 100644 index 000000000..ab2425a35 --- /dev/null +++ b/api/utils/blobaccess/bpi/utils.go @@ -0,0 +1,224 @@ +package bpi + +import ( + "io" + "sync" + "sync/atomic" + + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/utils/iotools" +) + +type _dataAccess = DataAccess + +type baseAccess interface { + base() BlobAccessBase +} + +func Cast[I interface{}](acc BlobAccess) I { + var _nil I + + var b BlobAccessBase = acc + + for b != nil { + if i, ok := b.(I); ok { + return i + } + if i, ok := b.(baseAccess); ok { + b = i.base() + } else { + b = nil + } + } + return _nil +} + +//////////////////////////////////////////////////////////////////////////////// + +type blobAccess struct { + _dataAccess + + lock sync.RWMutex + digest digest.Digest + size int64 + mimeType string +} + +func (b *blobAccess) MimeType() string { + return b.mimeType +} + +func (b *blobAccess) DigestKnown() bool { + b.lock.RLock() + defer b.lock.RUnlock() + return b.digest != "" +} + +func (b *blobAccess) Digest() digest.Digest { + b.lock.Lock() + defer b.lock.Unlock() + return b._Digest() +} + +func (b *blobAccess) _Digest() digest.Digest { + if b.digest == "" { + b.update() + } + return b.digest +} + +func (b *blobAccess) Size() int64 { + b.lock.Lock() + defer b.lock.Unlock() + return b._Size() +} + +func (b *blobAccess) _Size() int64 { + if b.size < 0 { + b.update() + } + return b.size +} + +func (b *blobAccess) update() error { + reader, err := b.Reader() + if err != nil { + return err + } + + defer reader.Close() + count := iotools.NewCountingReader(reader) + + digest, err := digest.Canonical.FromReader(count) + if err != nil { + return err + } + + b.size = count.Size() + b.digest = digest + + return nil +} + +type closableBlobAccess struct { + blobAccess + closed atomic.Bool +} + +func (b *closableBlobAccess) Close() error { + b.lock.Lock() + defer b.lock.Unlock() + if !b.closed.Load() { + tmp := b._dataAccess + b.closed.Store(true) + b._dataAccess = nil + return tmp.Close() + } + return ErrClosed +} + +func (b *closableBlobAccess) Get() ([]byte, error) { + b.lock.Lock() + defer b.lock.Unlock() + + if b.closed.Load() { + return nil, ErrClosed + } + return b.blobAccess.Get() +} + +func (b *closableBlobAccess) Reader() (io.ReadCloser, error) { + b.lock.Lock() + defer b.lock.Unlock() + + if b.closed.Load() { + return nil, ErrClosed + } + return b.blobAccess.Reader() +} + +func (b *closableBlobAccess) Digest() digest.Digest { + b.lock.Lock() + defer b.lock.Unlock() + + if b.closed.Load() { + return b.digest + } + return b.blobAccess._Digest() +} + +func (b *closableBlobAccess) Size() int64 { + b.lock.Lock() + defer b.lock.Unlock() + + if b.closed.Load() { + return b.size + } + return b.blobAccess._Size() +} + +// BaseAccessForDataAccess is used for a general data. +// It calculated the metadata on-the-fly for the content +// of the data access. The content may not be changing. +func BaseAccessForDataAccess(mime string, acc DataAccess) BlobAccessBase { + return &closableBlobAccess{ + blobAccess: blobAccess{mimeType: mime, _dataAccess: acc, digest: BLOB_UNKNOWN_DIGEST, size: BLOB_UNKNOWN_SIZE}, + } +} + +// BaseAccessForDataAccessAndMeta provides a BlobAccessBase for a DataAccess +// adding the additional blob access metadata (mime, digest, and size). +// Digest and size can be set to unknown using the constants (BLOB_UNKNOWN_DIGEST +// and BLOB_UNKNOWN_SIZE). +func BaseAccessForDataAccessAndMeta(mime string, acc DataAccess, dig digest.Digest, size int64) BlobAccessBase { + return &closableBlobAccess{ + blobAccess: blobAccess{mimeType: mime, _dataAccess: acc, digest: dig, size: size}, + } +} + +//////////////////////////////////////////////////////////////////////////////// + +// StaticBlobAccess is a BlobAccess which does not +// require finalization, therefore it can be used +// as BlobAccessProvider, also. +type StaticBlobAccess interface { + BlobAccess + BlobAccessProvider +} + +type staticBlobAccess struct { + blobAccess +} + +func (s *staticBlobAccess) Dup() (BlobAccess, error) { + return s, nil +} + +func (s *staticBlobAccess) BlobAccess() (BlobAccess, error) { + return s, nil +} + +func (s *staticBlobAccess) Close() error { + return nil +} + +// ForStaticDataAccess is used for a data access using no closer. +// They don't require a finalization and can be used +// as long as they exist. Therefore, no ref counting +// is required and they can be used as BlobAccessProvider, also. +func ForStaticDataAccess(mime string, acc DataAccess) StaticBlobAccess { + return &staticBlobAccess{ + blobAccess: blobAccess{mimeType: mime, _dataAccess: acc, digest: BLOB_UNKNOWN_DIGEST, size: BLOB_UNKNOWN_SIZE}, + } +} + +// ForStaticDataAccessAndMeta provides a StaticBlobAccess for a DataAccess +// adding the additional blob access metadata (mime, digest, and size). +// Digest and size can be set to unknown using the constants (BLOB_UNKNOWN_DIGEST +// and BLOB_UNKNOWN_SIZE). +func ForStaticDataAccessAndMeta(mime string, acc DataAccess, dig digest.Digest, size int64) StaticBlobAccess { + return &staticBlobAccess{ + blobAccess: blobAccess{mimeType: mime, _dataAccess: acc, digest: dig, size: size}, + } +} diff --git a/api/utils/blobaccess/bpi/view.go b/api/utils/blobaccess/bpi/view.go new file mode 100644 index 000000000..63632cf3b --- /dev/null +++ b/api/utils/blobaccess/bpi/view.go @@ -0,0 +1,96 @@ +package bpi + +import ( + "fmt" + "io" + + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/refmgmt" +) + +func NewBlobAccessForBase(acc BlobAccessBase, closer ...io.Closer) BlobAccess { + return refmgmt.WithView[BlobAccessBase, BlobAccess](acc, blobAccessViewCreator, closer...) +} + +func blobAccessViewCreator(blob BlobAccessBase, view *refmgmt.View[BlobAccess]) BlobAccess { + return &blobAccessView{view, blob} +} + +type blobAccessView struct { + *refmgmt.View[BlobAccess] + baseblob BlobAccessBase +} + +var ( + _ utils.Validatable = (*blobAccessView)(nil) + _ utils.Unwrappable = (*blobAccessView)(nil) +) + +func (b *blobAccessView) base() BlobAccessBase { + return b.baseblob +} + +func (b *blobAccessView) Unwrap() interface{} { + return b.baseblob +} + +func (b *blobAccessView) Close() error { + return b.View.Close() +} + +func (b *blobAccessView) Validate() error { + return utils.ValidateObject(b.baseblob) +} + +func (b *blobAccessView) Get() (result []byte, err error) { + return result, b.Execute(func() error { + result, err = b.baseblob.Get() + if err != nil { + return err + } + return nil + }) +} + +func (b *blobAccessView) Reader() (result io.ReadCloser, err error) { + return result, b.Execute(func() error { + result, err = b.baseblob.Reader() + if err != nil { + return fmt.Errorf("unable to read access: %w", err) + } + + return nil + }) +} + +func (b *blobAccessView) Digest() (result digest.Digest) { + err := b.Execute(func() error { + result = b.baseblob.Digest() + return nil + }) + if err != nil { + return BLOB_UNKNOWN_DIGEST + } + return +} + +func (b *blobAccessView) MimeType() string { + return b.baseblob.MimeType() +} + +func (b *blobAccessView) DigestKnown() bool { + return b.baseblob.DigestKnown() +} + +func (b *blobAccessView) Size() (result int64) { + err := b.Execute(func() error { + result = b.baseblob.Size() + return nil + }) + if err != nil { + return BLOB_UNKNOWN_SIZE + } + return +} diff --git a/api/utils/blobaccess/deprecated.go b/api/utils/blobaccess/deprecated.go new file mode 100644 index 000000000..cd847da4e --- /dev/null +++ b/api/utils/blobaccess/deprecated.go @@ -0,0 +1,11 @@ +package blobaccess + +import ( + "ocm.software/ocm/api/utils/blobaccess/blobaccess" +) + +// DataAccessForBytes wraps a bytes slice into a DataAccess. +// Deprecated: used DataAccessForData. +func DataAccessForBytes(data []byte, origin ...string) DataSource { + return blobaccess.DataAccessForData(data, origin...) +} diff --git a/api/utils/blobaccess/dirtree/access.go b/api/utils/blobaccess/dirtree/access.go new file mode 100644 index 000000000..989baf036 --- /dev/null +++ b/api/utils/blobaccess/dirtree/access.go @@ -0,0 +1,77 @@ +package dirtree + +import ( + "compress/gzip" + "fmt" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/optionutils" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/blobaccess/bpi" + "ocm.software/ocm/api/utils/blobaccess/file" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/api/utils/tarutils" +) + +func DataAccess(path string, opts ...Option) (bpi.DataAccess, error) { + blobAccess, err := BlobAccess(path, opts...) + if err != nil { + return nil, err + } + return blobAccess, nil +} + +func BlobAccess(path string, opts ...Option) (_ bpi.BlobAccess, rerr error) { + eff := optionutils.EvalOptions(opts...) + fs := utils.FileSystem(eff.FileSystem) + + ok, err := vfs.IsDir(fs, path) + if err != nil { + return nil, err + } + if !ok { + return nil, fmt.Errorf("%q is no directory", path) + } + + taropts := tarutils.TarFileSystemOptions{ + IncludeFiles: eff.IncludeFiles, + ExcludeFiles: eff.ExcludeFiles, + PreserveDir: utils.AsBool(eff.PreserveDir), + FollowSymlinks: utils.AsBool(eff.FollowSymlinks), + } + + temp, err := file.NewTempFile(fs.FSTempDir(), "resourceblob*.tgz", fs) + if err != nil { + return nil, err + } + defer errors.PropagateError(&rerr, temp.Close) + + if utils.AsBool(eff.CompressWithGzip) { + if eff.MimeType == "" { + eff.MimeType = mime.MIME_TGZ + } + gw := gzip.NewWriter(temp.Writer()) + if err := tarutils.PackFsIntoTar(fs, path, gw, taropts); err != nil { + return nil, fmt.Errorf("unable to tar input artifact: %w", err) + } + if err := gw.Close(); err != nil { + return nil, fmt.Errorf("unable to close gzip writer: %w", err) + } + } else { + if eff.MimeType == "" { + eff.MimeType = mime.MIME_TAR + } + if err := tarutils.PackFsIntoTar(fs, path, temp.Writer(), taropts); err != nil { + return nil, fmt.Errorf("unable to tar input artifact: %w", err) + } + } + return temp.AsBlob(eff.MimeType), nil +} + +func Provider(path string, opts ...Option) bpi.BlobAccessProvider { + return bpi.BlobAccessProviderFunction(func() (bpi.BlobAccess, error) { + return BlobAccess(path, opts...) + }) +} diff --git a/api/utils/blobaccess/dirtree/deprecated.go b/api/utils/blobaccess/dirtree/deprecated.go new file mode 100644 index 000000000..b2a30aa95 --- /dev/null +++ b/api/utils/blobaccess/dirtree/deprecated.go @@ -0,0 +1,17 @@ +package dirtree + +import ( + "ocm.software/ocm/api/utils/blobaccess/bpi" +) + +// BlobAccessForDirTree returns a BlobAccess for the given directory tree. +// Deprecated: use BlobAccess. +func BlobAccessForDirTree(path string, opts ...Option) (_ bpi.BlobAccess, rerr error) { + return BlobAccess(path, opts...) +} + +// BlobAccessProviderForDirTree returns a BlobAccessProvider for the given directory tree. +// Deprecated: use Provider. +func BlobAccessProviderForDirTree(path string, opts ...Option) bpi.BlobAccessProvider { + return Provider(path, opts...) +} diff --git a/api/utils/blobaccess/dirtree/options.go b/api/utils/blobaccess/dirtree/options.go new file mode 100644 index 000000000..95b922367 --- /dev/null +++ b/api/utils/blobaccess/dirtree/options.go @@ -0,0 +1,134 @@ +package dirtree + +import ( + "github.com/mandelsoft/goutils/optionutils" + "github.com/mandelsoft/vfs/pkg/vfs" + "golang.org/x/exp/slices" + + "ocm.software/ocm/api/utils" +) + +type Option = optionutils.Option[*Options] + +type Options struct { + // FileSystem defines the file system that contains the specified directory. + FileSystem vfs.FileSystem + MimeType string + // CompressWithGzip defines whether the specified directory should be compressed. + CompressWithGzip *bool `json:"compress,omitempty"` + // PreserveDir defines that the specified directory should be included in the blob. + PreserveDir *bool `json:"preserveDir,omitempty"` + // IncludeFiles is a list of shell file name patterns that describe the files that should be included. + // If nothing is defined, all files are included. + IncludeFiles []string `json:"includeFiles,omitempty"` + // ExcludeFiles is a list of shell file name patterns that describe the files that should be excluded from the resulting tar. + // Excluded files always overwrite included files. + ExcludeFiles []string `json:"excludeFiles,omitempty"` + // FollowSymlinks configures to follow and resolve symlinks when a directory is tarred. + // This options will include the content of the symlink directly in the tar. + // This option should be used with care. + FollowSymlinks *bool `json:"followSymlinks,omitempty"` +} + +func (o *Options) ApplyTo(opts *Options) { + if opts == nil { + return + } + if o.FileSystem != nil { + opts.FileSystem = o.FileSystem + } + if o.MimeType != "" { + opts.MimeType = o.MimeType + } + if o.CompressWithGzip != nil { + opts.CompressWithGzip = utils.BoolP(*o.CompressWithGzip) + } + if o.PreserveDir != nil { + opts.PreserveDir = utils.BoolP(*o.PreserveDir) + } + if len(o.IncludeFiles) != 0 { + opts.IncludeFiles = slices.Clone(o.IncludeFiles) + } + if len(o.ExcludeFiles) != 0 { + opts.ExcludeFiles = slices.Clone(o.ExcludeFiles) + } + if o.FollowSymlinks != nil { + opts.FollowSymlinks = utils.BoolP(*o.FollowSymlinks) + } +} + +//////////////////////////////////////////////////////////////////////////////// + +type fileSystem struct { + fs vfs.FileSystem +} + +func (o *fileSystem) ApplyTo(opts *Options) { + opts.FileSystem = o.fs +} + +func WithFileSystem(fs vfs.FileSystem) Option { + return &fileSystem{fs: fs} +} + +//////////////////////////////////////////////////////////////////////////////// + +type mimeType string + +func (o mimeType) ApplyTo(opts *Options) { + opts.MimeType = string(o) +} + +func WithMimeType(mime string) Option { + return mimeType(mime) +} + +type compressWithGzip bool + +func (o compressWithGzip) ApplyTo(opts *Options) { + opts.CompressWithGzip = utils.BoolP(o) +} + +func WithCompressWithGzip(b ...bool) Option { + return compressWithGzip(utils.OptionalDefaultedBool(true, b...)) +} + +type preserveDir bool + +func (o preserveDir) ApplyTo(opts *Options) { + opts.PreserveDir = utils.BoolP(o) +} + +func WithPreserveDir(b ...bool) Option { + return preserveDir(utils.OptionalDefaultedBool(true, b...)) +} + +type includeFiles []string + +func (o includeFiles) ApplyTo(opts *Options) { + opts.IncludeFiles = slices.Clone(o) +} + +func WithIncludeFiles(files []string) Option { + return includeFiles(files) +} + +type excludeFiles []string + +func (o excludeFiles) ApplyTo(opts *Options) { + opts.ExcludeFiles = slices.Clone(o) +} + +func WithExcludeFiles(files []string) Option { + return excludeFiles(files) +} + +type followSymlinks bool + +func (o followSymlinks) ApplyTo(opts *Options) { + opts.FollowSymlinks = utils.BoolP(o) +} + +func WithFollowSymlinks(b ...bool) Option { + return followSymlinks(utils.OptionalDefaultedBool(true, b...)) +} diff --git a/pkg/blobaccess/doc.go b/api/utils/blobaccess/doc.go similarity index 100% rename from pkg/blobaccess/doc.go rename to api/utils/blobaccess/doc.go diff --git a/api/utils/blobaccess/dockerdaemon/access.go b/api/utils/blobaccess/dockerdaemon/access.go new file mode 100644 index 000000000..8acd4c00e --- /dev/null +++ b/api/utils/blobaccess/dockerdaemon/access.go @@ -0,0 +1,76 @@ +package dockerdaemon + +import ( + "fmt" + + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/oci/annotations" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/oci/extensions/repositories/docker" + cpi "ocm.software/ocm/api/oci/types" + "ocm.software/ocm/api/utils/blobaccess/bpi" +) + +func (o *Options) OCIContext() cpi.Context { + if o.Context == nil { + return cpi.DefaultContext() + } + return o.Context +} + +func ImageInfoFor(name string, opts ...Option) (locator string, version string, err error) { + eff := optionutils.EvalOptions(opts...) + + locator, version, err = docker.ParseGenericRef(name) + if err != nil { + return "", "", err + } + + if version == "" || version == "latest" || optionutils.AsValue(eff.OverrideVersion) { + version = eff.Version + } + if version == "" { + return "", "", fmt.Errorf("no version specified") + } + return locator, version, nil +} + +func Provider(name string, opts ...Option) bpi.BlobAccessProvider { + return bpi.BlobAccessProviderFunction(func() (bpi.BlobAccess, error) { + b, _, err := BlobAccess(name, opts...) + return b, err + }) +} + +// BlobAccess returns a BlobAccess for the image with the given name. +func BlobAccess(name string, opts ...Option) (bpi.BlobAccess, string, error) { + eff := optionutils.EvalOptions(opts...) + ctx := eff.OCIContext() + + locator, version, err := ImageInfoFor(name, eff) + if err != nil { + return nil, "", err + } + spec := docker.NewRepositorySpec() + repo, err := ctx.RepositoryForSpec(spec) + if err != nil { + return nil, "", err + } + ns, err := repo.LookupNamespace(locator) + if err != nil { + return nil, "", err + } + blob, err := artifactset.SynthesizeArtifactBlob(ns, version, + func(art cpi.ArtifactAccess) error { + if eff.Origin != nil { + art.Artifact().SetAnnotation(annotations.COMPVERS_ANNOTATION, eff.Origin.String()) + } + return nil + }, + ) + if err != nil { + return nil, "", err + } + return blob, version, nil +} diff --git a/api/utils/blobaccess/dockerdaemon/deprecated.go b/api/utils/blobaccess/dockerdaemon/deprecated.go new file mode 100644 index 000000000..83c602642 --- /dev/null +++ b/api/utils/blobaccess/dockerdaemon/deprecated.go @@ -0,0 +1,17 @@ +package dockerdaemon + +import ( + "ocm.software/ocm/api/utils/blobaccess/bpi" +) + +// BlobAccessProviderForImageFromDockerDaemon returns a BlobAccessProvider for the image with the given name. +// Deprecated: use Provider. +func BlobAccessProviderForImageFromDockerDaemon(name string, opts ...Option) bpi.BlobAccessProvider { + return Provider(name, opts...) +} + +// BlobAccessForImageFromDockerDaemon returns a BlobAccess for the image with the given name. +// Decrecated: use BlobAccess. +func BlobAccessForImageFromDockerDaemon(name string, opts ...Option) (bpi.BlobAccess, string, error) { + return BlobAccess(name, opts...) +} diff --git a/api/utils/blobaccess/dockerdaemon/options.go b/api/utils/blobaccess/dockerdaemon/options.go new file mode 100644 index 000000000..0cdaf9546 --- /dev/null +++ b/api/utils/blobaccess/dockerdaemon/options.go @@ -0,0 +1,110 @@ +package dockerdaemon + +import ( + "github.com/mandelsoft/goutils/optionutils" + + cpi "ocm.software/ocm/api/oci/types" + "ocm.software/ocm/api/utils" + common "ocm.software/ocm/api/utils/misc" +) + +type Option = optionutils.Option[*Options] + +type Options struct { + Context cpi.Context + Name string + Version string + OverrideVersion *bool + Origin *common.NameVersion +} + +func (o *Options) ApplyTo(opts *Options) { + if opts == nil { + return + } + if o.Context != nil { + opts.Context = o.Context + } + if o.Name != "" { + opts.Name = o.Name + } + if o.Version != "" { + opts.Version = o.Version + } + if o.OverrideVersion != nil { + opts.OverrideVersion = o.OverrideVersion + } + if o.Origin != nil { + opts.Origin = o.Origin + } +} + +//////////////////////////////////////////////////////////////////////////////// + +type context struct { + cpi.Context +} + +func (o context) ApplyTo(opts *Options) { + opts.Context = o +} + +func WithContext(ctx cpi.ContextProvider) Option { + return context{ctx.OCIContext()} +} + +//////////////////////////////////////////////////////////////////////////////// + +type name string + +func (o name) ApplyTo(opts *Options) { + opts.Name = string(o) +} + +func WithName(n string) Option { + return name(n) +} + +//////////////////////////////////////////////////////////////////////////////// + +type version string + +func (o version) ApplyTo(opts *Options) { + opts.Version = string(o) +} + +func WithVersion(v string) Option { + return version(v) +} + +//////////////////////////////////////////////////////////////////////////////// + +type override struct { + flag bool + version string +} + +func (o *override) ApplyTo(opts *Options) { + opts.OverrideVersion = utils.BoolP(o.flag) + opts.Version = o.version +} + +func WithVersionOverride(v string, flag ...bool) Option { + return &override{ + version: v, + flag: utils.OptionalDefaultedBool(true, flag...), + } +} + +//////////////////////////////////////////////////////////////////////////////// + +type compvers common.NameVersion + +func (o compvers) ApplyTo(opts *Options) { + n := common.NameVersion(o) + opts.Origin = &n +} + +func WithOrigin(o common.NameVersion) Option { + return compvers(o) +} diff --git a/api/utils/blobaccess/dockermulti/access.go b/api/utils/blobaccess/dockermulti/access.go new file mode 100644 index 000000000..600e24ada --- /dev/null +++ b/api/utils/blobaccess/dockermulti/access.go @@ -0,0 +1,156 @@ +package dockermulti + +import ( + "fmt" + + . "github.com/mandelsoft/goutils/finalizer" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/optionutils" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/annotations" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/cpi" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/oci/extensions/repositories/docker" + "ocm.software/ocm/api/utils/blobaccess/bpi" +) + +func (o *Options) OCIContext() oci.Context { + if o.Context == nil { + return oci.DefaultContext() + } + return o.Context +} + +func (s *Options) getVariant(ctx oci.Context, finalize *Finalizer, variant string) (oci.ArtifactAccess, error) { + locator, version, err := docker.ParseGenericRef(variant) + if err != nil { + return nil, err + } + if version == "" { + return nil, fmt.Errorf("artifact version required") + } + spec := docker.NewRepositorySpec() + repo, err := ctx.RepositoryForSpec(spec) + if err != nil { + return nil, err + } + finalize.Close(repo) + ns, err := repo.LookupNamespace(locator) + if err != nil { + return nil, err + } + finalize.Close(ns) + + art, err := ns.GetArtifact(version) + if err != nil { + return nil, artifactset.GetArtifactError{Original: err, Ref: locator + ":" + version} + } + finalize.Close(art) + return art, nil +} + +func BlobAccess(opts ...Option) (bpi.BlobAccess, error) { + eff := optionutils.EvalOptions(opts...) + ctx := eff.OCIContext() + + index := artdesc.NewIndex() + i := 0 + + version := eff.Version + if eff.Origin != nil { + if version == "" { + version = eff.Origin.GetVersion() + } + index.SetAnnotation(annotations.COMPVERS_ANNOTATION, eff.Origin.String()) + } + if version == "" { + return nil, fmt.Errorf("no versio specified") + } + + feedback := func(blob bpi.BlobAccess, art cpi.ArtifactAccess) error { + desc := artdesc.DefaultBlobDescriptor(blob) + if art.IsManifest() { + cfgBlob, err := art.ManifestAccess().GetConfigBlob() + if err != nil { + return errors.Wrapf(err, "cannot get config blob") + } + cfg, err := artdesc.ParseImageConfig(cfgBlob) + if err != nil { + return errors.Wrapf(err, "cannot parse config blob") + } + if cfg.Architecture != "" { + desc.Platform = &artdesc.Platform{ + Architecture: cfg.Architecture, + OS: cfg.OS, + Variant: cfg.Variant, + } + } + } + index.AddManifest(desc) + return nil + } + + blob, err := artifactset.SynthesizeArtifactBlobFor(version, func() (fac artifactset.ArtifactFactory, main bool, err error) { + var art cpi.ArtifactAccess + var blob bpi.BlobAccess + + switch { + case i > len(eff.Variants): + // end loop + case i == len(eff.Variants): + // provide index (main) artifact + if eff.Printer != nil { + eff.Printer.Printf("image %d: INDEX\n", i) + } + fac = func(set *artifactset.ArtifactSet) (digest.Digest, string, error) { + art, err = set.NewArtifact(index) + if err != nil { + return "", "", errors.Wrapf(err, "cannot create index artifact") + } + defer art.Close() + blob, err = set.AddArtifact(art) + if err != nil { + return "", "", errors.Wrapf(err, "cannot add index artifact") + } + defer blob.Close() + return blob.Digest(), blob.MimeType(), nil + } + main = true + default: + // provide variant + if eff.Printer != nil { + eff.Printer.Printf("image %d: %s\n", i, eff.Variants[i]) + } + var finalize Finalizer + + art, err = eff.getVariant(ctx, &finalize, eff.Variants[i]) + + if err == nil { + if eff.Origin != nil { + art.Artifact().SetAnnotation(annotations.COMPVERS_ANNOTATION, eff.Origin.String()) + } + blob, err = art.Blob() + if err == nil { + finalize.Close(art) + fac = artifactset.ArtifactTransferCreator(art, &finalize, feedback) + } + } + } + i++ + return + }) + if err != nil { + return nil, err + } + return blob, nil +} + +func Provider(opts ...Option) bpi.BlobAccessProvider { + return bpi.BlobAccessProviderFunction(func() (bpi.BlobAccess, error) { + return BlobAccess(opts...) + }) +} diff --git a/api/utils/blobaccess/dockermulti/deprecated.go b/api/utils/blobaccess/dockermulti/deprecated.go new file mode 100644 index 000000000..b565de43c --- /dev/null +++ b/api/utils/blobaccess/dockermulti/deprecated.go @@ -0,0 +1,17 @@ +package dockermulti + +import ( + "ocm.software/ocm/api/utils/blobaccess/bpi" +) + +// BlobAccessForMultiImageFromDockerDaemon returns a BlobAccess for the image with the given name. +// Deprecated: use BlobAccess. +func BlobAccessForMultiImageFromDockerDaemon(opts ...Option) (bpi.BlobAccess, error) { + return BlobAccess(opts...) +} + +// BlobAccessProviderForMultiImageFromDockerDaemon returns a BlobAccessProvider for the image with the given name. +// Deprecated: use Provider. +func BlobAccessProviderForMultiImageFromDockerDaemon(opts ...Option) bpi.BlobAccessProvider { + return Provider(opts...) +} diff --git a/api/utils/blobaccess/dockermulti/options.go b/api/utils/blobaccess/dockermulti/options.go new file mode 100644 index 000000000..2ab00fc36 --- /dev/null +++ b/api/utils/blobaccess/dockermulti/options.go @@ -0,0 +1,105 @@ +package dockermulti + +import ( + "github.com/mandelsoft/goutils/optionutils" + "golang.org/x/exp/slices" + + "ocm.software/ocm/api/oci" + common "ocm.software/ocm/api/utils/misc" +) + +type Option = optionutils.Option[*Options] + +type Options struct { + Context oci.Context + Version string + Variants []string + Origin *common.NameVersion + Printer common.Printer +} + +func (o *Options) ApplyTo(opts *Options) { + if opts == nil { + return + } + if o.Context != nil { + opts.Context = o.Context + } + if o.Version != "" { + opts.Version = o.Version + } + if o.Variants != nil { + opts.Variants = append(opts.Variants, o.Variants...) + } + if o.Origin != nil { + opts.Origin = o.Origin + } + if o.Printer != nil { + opts.Printer = o.Printer + } +} + +//////////////////////////////////////////////////////////////////////////////// + +type context struct { + oci.Context +} + +func (o context) ApplyTo(opts *Options) { + opts.Context = o +} + +func WithContext(ctx oci.ContextProvider) Option { + return context{ctx.OCIContext()} +} + +//////////////////////////////////////////////////////////////////////////////// + +type version string + +func (o version) ApplyTo(opts *Options) { + opts.Version = string(o) +} + +func WithVersion(v string) Option { + return version(v) +} + +//////////////////////////////////////////////////////////////////////////////// + +type compvers common.NameVersion + +func (o compvers) ApplyTo(opts *Options) { + n := common.NameVersion(o) + opts.Origin = &n +} + +func WithOrigin(o common.NameVersion) Option { + return compvers(o) +} + +//////////////////////////////////////////////////////////////////////////////// + +type variants []string + +func (o variants) ApplyTo(opts *Options) { + opts.Variants = append(opts.Variants, []string(o)...) +} + +func WithVariants(v ...string) Option { + return variants(slices.Clone(v)) +} + +//////////////////////////////////////////////////////////////////////////////// + +type printer struct { + common.Printer +} + +func (o printer) ApplyTo(opts *Options) { + opts.Printer = o +} + +func WithPrinter(p common.Printer) Option { + return printer{p} +} diff --git a/api/utils/blobaccess/file/access.go b/api/utils/blobaccess/file/access.go new file mode 100644 index 000000000..5be16984a --- /dev/null +++ b/api/utils/blobaccess/file/access.go @@ -0,0 +1,340 @@ +package file + +import ( + "io" + "sync" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/optionutils" + "github.com/mandelsoft/vfs/pkg/vfs" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/blobaccess/bpi" + "ocm.software/ocm/api/utils/iotools" +) + +type ( + _nopCloser = iotools.NopCloser + _blobAccess = bpi.BlobAccess +) + +//////////////////////////////////////////////////////////////////////////////// + +type fileDataAccess struct { + _nopCloser + fs vfs.FileSystem + path string +} + +var ( + _ bpi.DataSource = (*fileDataAccess)(nil) + _ bpi.Validatable = (*fileDataAccess)(nil) +) + +func DataAccess(fs vfs.FileSystem, path string) bpi.DataAccess { + return &fileDataAccess{fs: fs, path: path} +} + +func (a *fileDataAccess) Get() ([]byte, error) { + data, err := vfs.ReadFile(a.fs, a.path) + if err != nil { + return nil, errors.Wrapf(err, "file %q", a.path) + } + return data, nil +} + +func (a *fileDataAccess) Reader() (io.ReadCloser, error) { + file, err := a.fs.Open(a.path) + if err != nil { + return nil, errors.Wrapf(err, "file %q", a.path) + } + return file, nil +} + +// Validate checks if the access is valid, meaning +// it can provide data. Here, this means +// that the file exists. +func (a *fileDataAccess) Validate() error { + ok, err := vfs.Exists(a.fs, a.path) + if err != nil { + return err + } + if !ok { + return errors.ErrNotFound("file", a.path) + } + return nil +} + +func (a *fileDataAccess) Origin() string { + return a.path +} + +//////////////////////////////////////////////////////////////////////////////// + +type fileBlobAccess struct { + fileDataAccess + mimeType string +} + +var ( + _ bpi.BlobAccess = (*fileBlobAccess)(nil) + _ bpi.FileLocation = (*fileBlobAccess)(nil) +) + +func (f *fileBlobAccess) FileSystem() vfs.FileSystem { + return f.fs +} + +func (f *fileBlobAccess) Path() string { + return f.path +} + +func (f *fileBlobAccess) Dup() (bpi.BlobAccess, error) { + return f, nil +} + +func (f *fileBlobAccess) Size() int64 { + size := bpi.BLOB_UNKNOWN_SIZE + fi, err := f.fs.Stat(f.path) + if err == nil { + size = fi.Size() + } + return size +} + +func (f *fileBlobAccess) MimeType() string { + return f.mimeType +} + +func (f *fileBlobAccess) DigestKnown() bool { + return false +} + +func (f *fileBlobAccess) Digest() digest.Digest { + r, err := f.Reader() + if err != nil { + return "" + } + defer r.Close() + d, err := digest.FromReader(r) + if err != nil { + return "" + } + return d +} + +// BlobAccess wraps a file path into a BlobAccess, which does not need a close. +func BlobAccess(mime string, path string, fss ...vfs.FileSystem) bpi.BlobAccess { + return &fileBlobAccess{ + mimeType: mime, + fileDataAccess: fileDataAccess{fs: utils.FileSystem(fss...), path: path}, + } +} + +func Provider(mime string, path string, fss ...vfs.FileSystem) bpi.BlobAccessProvider { + return bpi.BlobAccessProviderFunction(func() (bpi.BlobAccess, error) { + return BlobAccess(mime, path, fss...), nil + }) +} + +type fileBlobAccessView struct { + _blobAccess + access *fileDataAccess +} + +var ( + _ bpi.BlobAccess = (*fileBlobAccessView)(nil) + _ bpi.FileLocation = (*fileBlobAccessView)(nil) +) + +func (f *fileBlobAccessView) Dup() (bpi.BlobAccess, error) { + b, err := f._blobAccess.Dup() + if err != nil { + return nil, err + } + return &fileBlobAccessView{b, f.access}, nil +} + +func (f *fileBlobAccessView) FileSystem() vfs.FileSystem { + return f.access.fs +} + +func (f *fileBlobAccessView) Path() string { + return f.access.path +} + +func BlobAccessWithCloser(closer io.Closer, mime string, path string, fss ...vfs.FileSystem) bpi.BlobAccess { + fb := &fileBlobAccess{fileDataAccess{fs: utils.FileSystem(fss...), path: path}, mime} + return &fileBlobAccessView{ + bpi.NewBlobAccessForBase(fb, closer), + &fb.fileDataAccess, + } +} + +//////////////////////////////////////////////////////////////////////////////// + +type temporaryFileBlob struct { + _blobAccess + lock sync.Mutex + path string + file vfs.File + filesystem vfs.FileSystem +} + +var ( + _ bpi.BlobAccessBase = (*temporaryFileBlob)(nil) + _ bpi.FileLocation = (*temporaryFileBlob)(nil) +) + +// Validate checks if the access is valid, meaning +// it can provide data. Here, this means +// that the file exists. +func (b *temporaryFileBlob) Validate() error { + b.lock.Lock() + defer b.lock.Unlock() + if b.path == "" { + return bpi.ErrClosed + } + ok, err := vfs.Exists(b.filesystem, b.path) + if err != nil { + return err + } + if !ok { + return errors.ErrNotFound("file", b.path) + } + return nil +} + +func (b *temporaryFileBlob) Close() error { + b.lock.Lock() + defer b.lock.Unlock() + if b.path != "" { + list := errors.ErrListf("temporary blob") + if b.file != nil { + list.Add(b.file.Close()) + } + list.Add(b.filesystem.Remove(b.path)) + b.path = "" + b.file = nil + b._blobAccess = nil + return list.Result() + } + return nil +} + +func (b *temporaryFileBlob) FileSystem() vfs.FileSystem { + return b.filesystem +} + +func (b *temporaryFileBlob) Path() string { + return b.path +} + +func BlobAccessForTemporaryFile(mime string, temp vfs.File, opts ...Option) bpi.BlobAccess { + eff := optionutils.EvalOptions(opts...) + t := &temporaryFileBlob{ + _blobAccess: BlobAccess(mime, temp.Name(), eff.FileSystem), + filesystem: utils.FileSystem(eff.FileSystem), + path: temp.Name(), + file: temp, + } + // TODO: handle FileLocation interface in combination with partially set meta data. + if eff.Digest != "" || eff.GetSize() != bpi.BLOB_UNKNOWN_SIZE { + return bpi.NewBlobAccessForBase(bpi.BaseAccessForDataAccessAndMeta(mime, t, eff.Digest, eff.GetSize())) + } + return bpi.NewBlobAccessForBase(t) +} + +func BlobAccessForTemporaryFilePath(mime string, temp string, opts ...Option) bpi.BlobAccess { + eff := optionutils.EvalOptions(opts...) + return bpi.NewBlobAccessForBase(bpi.BaseAccessForDataAccessAndMeta(mime, &temporaryFileBlob{ + _blobAccess: BlobAccess(mime, temp, eff.FileSystem), + filesystem: utils.FileSystem(eff.FileSystem), + path: temp, + }, eff.Digest, eff.GetSize())) +} + +//////////////////////////////////////////////////////////////////////////////// + +// TempFile holds a temporary file that should be kept open. +// Close should never be called directly. +// It can be passed to another responsibility realm by calling Release- +// For example to be transformed into a TemporaryBlobAccess. +// Close will close and remove an unreleased file and does +// nothing if it has been released. +// If it has been released the new realm is responsible. +// to close and remove it. +type TempFile struct { + lock sync.Mutex + temp vfs.File + filesystem vfs.FileSystem +} + +func NewTempFile(dir string, pattern string, fss ...vfs.FileSystem) (*TempFile, error) { + fs := utils.FileSystem(fss...) + temp, err := vfs.TempFile(fs, dir, pattern) + if err != nil { + return nil, err + } + return &TempFile{ + temp: temp, + filesystem: fs, + }, nil +} + +func (t *TempFile) Name() string { + t.lock.Lock() + defer t.lock.Unlock() + return t.temp.Name() +} + +func (t *TempFile) FileSystem() vfs.FileSystem { + t.lock.Lock() + defer t.lock.Unlock() + return t.filesystem +} + +// Release passes the responsibility for closing and removing +// the temporary file to another realm. After calling this method +// the TempFile object will not handle these operations anymore, if it is closed. +func (t *TempFile) Release() vfs.File { + t.lock.Lock() + defer t.lock.Unlock() + if t.temp != nil { + t.temp.Sync() + } + tmp := t.temp + t.temp = nil + return tmp +} + +func (t *TempFile) Writer() io.Writer { + t.lock.Lock() + defer t.lock.Unlock() + return t.temp +} + +func (t *TempFile) Sync() error { + t.lock.Lock() + defer t.lock.Unlock() + return t.temp.Sync() +} + +func (t *TempFile) AsBlob(mime string) bpi.BlobAccess { + return BlobAccessForTemporaryFile(mime, t.Release(), WithFileSystem(t.filesystem)) +} + +// Close closes and removes the temporary file as long it has not +// been released before by calling Release. +func (t *TempFile) Close() error { + t.lock.Lock() + defer t.lock.Unlock() + if t.temp != nil { + name := t.temp.Name() + t.temp.Close() + t.temp = nil + return t.filesystem.Remove(name) + } + return nil +} diff --git a/api/utils/blobaccess/file/options.go b/api/utils/blobaccess/file/options.go new file mode 100644 index 000000000..656b7dc02 --- /dev/null +++ b/api/utils/blobaccess/file/options.go @@ -0,0 +1,76 @@ +package file + +import ( + "github.com/mandelsoft/goutils/generics" + "github.com/mandelsoft/goutils/optionutils" + "github.com/mandelsoft/vfs/pkg/vfs" + "github.com/opencontainers/go-digest" + + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/blobaccess/bpi" +) + +type Option = optionutils.Option[*Options] + +type Options struct { + // FileSystem defines the file system that contains the specified directory. + FileSystem vfs.FileSystem + Digest digest.Digest + Size *int64 +} + +func (o *Options) GetSize() int64 { + if o.Size == nil { + return bpi.BLOB_UNKNOWN_SIZE + } + return *o.Size +} + +func (o *Options) ApplyTo(opts *Options) { + if opts == nil { + return + } + if o.FileSystem != nil { + opts.FileSystem = o.FileSystem + } + if o.Digest != "" { + opts.Digest = o.Digest + } + optionutils.ApplyOption(o.Size, &opts.Size) +} + +//////////////////////////////////////////////////////////////////////////////// + +type fileSystem struct { + fs vfs.FileSystem +} + +func (o *fileSystem) ApplyTo(opts *Options) { + opts.FileSystem = o.fs +} + +func WithFileSystem(fss ...vfs.FileSystem) Option { + return &fileSystem{fs: utils.FileSystem(fss...)} +} + +//////////////////////////////////////////////////////////////////////////////// + +type size int64 + +func (o size) ApplyTo(opts *Options) { + opts.Size = generics.Pointer(int64(o)) +} + +func WithSize(s int64) Option { + return size(s) +} + +type _digest digest.Digest + +func (o _digest) ApplyTo(opts *Options) { + opts.Digest = digest.Digest(o) +} + +func WithDigest(d digest.Digest) Option { + return _digest(d) +} diff --git a/pkg/blobaccess/file/suite_test.go b/api/utils/blobaccess/file/suite_test.go similarity index 100% rename from pkg/blobaccess/file/suite_test.go rename to api/utils/blobaccess/file/suite_test.go diff --git a/pkg/blobaccess/file/temp_test.go b/api/utils/blobaccess/file/temp_test.go similarity index 97% rename from pkg/blobaccess/file/temp_test.go rename to api/utils/blobaccess/file/temp_test.go index 2937eb06f..ad3d5d4cf 100644 --- a/pkg/blobaccess/file/temp_test.go +++ b/api/utils/blobaccess/file/temp_test.go @@ -7,7 +7,7 @@ import ( "github.com/mandelsoft/vfs/pkg/osfs" "github.com/mandelsoft/vfs/pkg/vfs" - me "github.com/open-component-model/ocm/pkg/blobaccess" + me "ocm.software/ocm/api/utils/blobaccess" ) var _ = Describe("temp file management", func() { diff --git a/pkg/blobaccess/forward.go b/api/utils/blobaccess/forward.go similarity index 92% rename from pkg/blobaccess/forward.go rename to api/utils/blobaccess/forward.go index ef6bdfb93..d2c4652ae 100644 --- a/pkg/blobaccess/forward.go +++ b/api/utils/blobaccess/forward.go @@ -6,16 +6,16 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" "github.com/opencontainers/go-digest" - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" - "github.com/open-component-model/ocm/pkg/blobaccess/dirtree" - "github.com/open-component-model/ocm/pkg/blobaccess/dockerdaemon" - "github.com/open-component-model/ocm/pkg/blobaccess/dockermulti" - "github.com/open-component-model/ocm/pkg/blobaccess/file" - "github.com/open-component-model/ocm/pkg/blobaccess/helm" - "github.com/open-component-model/ocm/pkg/blobaccess/maven" - "github.com/open-component-model/ocm/pkg/blobaccess/ociartifact" - "github.com/open-component-model/ocm/pkg/blobaccess/wget" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/bpi" + "ocm.software/ocm/api/utils/blobaccess/dirtree" + "ocm.software/ocm/api/utils/blobaccess/dockerdaemon" + "ocm.software/ocm/api/utils/blobaccess/dockermulti" + "ocm.software/ocm/api/utils/blobaccess/file" + "ocm.software/ocm/api/utils/blobaccess/helm" + "ocm.software/ocm/api/utils/blobaccess/maven" + "ocm.software/ocm/api/utils/blobaccess/ociartifact" + "ocm.software/ocm/api/utils/blobaccess/wget" ) /////////// diff --git a/api/utils/blobaccess/helm/access.go b/api/utils/blobaccess/helm/access.go new file mode 100644 index 000000000..4af936328 --- /dev/null +++ b/api/utils/blobaccess/helm/access.go @@ -0,0 +1,81 @@ +package helm + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/optionutils" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/credentials/builtin/helm/identity" + ocihelm "ocm.software/ocm/api/oci/ociutils/helm" + "ocm.software/ocm/api/tech/helm" + "ocm.software/ocm/api/tech/helm/loader" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/blobaccess/bpi" + common "ocm.software/ocm/api/utils/misc" +) + +func BlobAccess(path string, opts ...Option) (blob bpi.BlobAccess, name, version string, err error) { + eff := optionutils.EvalOptions(opts...) + ctx := eff.OCIContext() + fs := utils.FileSystem(eff.FileSystem) + printer := eff.Printer + if printer == nil { + printer = common.NewPrinter(nil) + } + + var chartLoader loader.Loader + if eff.HelmRepository == "" { + if ok, err := vfs.Exists(fs, path); !ok || err != nil { + return nil, "", "", errors.NewEf(err, "invalid file path %q", path) + } + chartLoader = loader.VFSLoader(path, fs) + } else { + cert := []byte(eff.CACert) + if eff.CACertFile != "" { + cert, err = vfs.ReadFile(fs, eff.CACertFile) + if err != nil { + return nil, "", "", errors.Wrapf(err, "cannot read root certificates from %q", eff.CACertFile) + } + } + + acc, err := helm.DownloadChart(printer, ctx, path, eff.Version, eff.HelmRepository, + helm.WithCredentials(identity.GetCredentials(ctx, eff.HelmRepository, path)), + helm.WithRootCert(cert)) + if err != nil { + return nil, "", "", errors.Wrapf(err, "cannot download chart %s:%s from %s", path, eff.Version, eff.HelmRepository) + } + chartLoader = loader.AccessLoader(acc) + } + + defer errors.PropagateError(&err, chartLoader.Close) + + chart, err := chartLoader.Chart() + if err != nil { + return nil, "", "", err + } + vers := chart.Metadata.Version + if vers == "" || optionutils.AsValue(eff.OverrideVersion) { + vers = eff.Version + } + if vers == "" { + return nil, "", "", fmt.Errorf("no version found or specified") + } + + blob, err = chartLoader.ChartArtefactSet() + if err == nil && blob == nil { + blob, err = ocihelm.SynthesizeArtifactBlob(chartLoader) + if err != nil { + return nil, "", "", errors.Wrapf(err, "cannot synthesize artifact blob") + } + } + return blob, chart.Name(), vers, err +} + +func Provider(name string, opts ...Option) bpi.BlobAccessProvider { + return bpi.BlobAccessProviderFunction(func() (bpi.BlobAccess, error) { + b, _, _, err := BlobAccess(name, opts...) + return b, err + }) +} diff --git a/api/utils/blobaccess/helm/deprecated.go b/api/utils/blobaccess/helm/deprecated.go new file mode 100644 index 000000000..3a47cde48 --- /dev/null +++ b/api/utils/blobaccess/helm/deprecated.go @@ -0,0 +1,17 @@ +package helm + +import ( + "ocm.software/ocm/api/utils/blobaccess/bpi" +) + +// BlobAccessForHelmChart returns a BlobAccess for the Helm chart with the given path. +// Deprecated: use BlobAccess. +func BlobAccessForHelmChart(path string, opts ...Option) (blob bpi.BlobAccess, name, version string, err error) { + return BlobAccess(path, opts...) +} + +// BlobAccessProviderForHelmChart returns a BlobAccessProvider for the Helm chart with the given name. +// Deprecated: use Provider. +func BlobAccessProviderForHelmChart(name string, opts ...Option) bpi.BlobAccessProvider { + return Provider(name, opts...) +} diff --git a/api/utils/blobaccess/helm/options.go b/api/utils/blobaccess/helm/options.go new file mode 100644 index 000000000..88bd1ba1d --- /dev/null +++ b/api/utils/blobaccess/helm/options.go @@ -0,0 +1,171 @@ +package helm + +import ( + "github.com/mandelsoft/goutils/optionutils" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/utils" + common "ocm.software/ocm/api/utils/misc" +) + +type Option = optionutils.Option[*Options] + +type Options struct { + Context oci.Context + FileSystem vfs.FileSystem + Version string + OverrideVersion *bool + HelmRepository string + CACert string + CACertFile string + + Printer common.Printer +} + +func (o *Options) ApplyTo(opts *Options) { + if opts == nil { + return + } + if o.Context != nil { + opts.Context = o.Context + } + if o.FileSystem != nil { + opts.FileSystem = o.FileSystem + } + if o.Version != "" { + opts.Version = o.Version + } + if o.OverrideVersion != nil { + opts.OverrideVersion = o.OverrideVersion + } + if o.HelmRepository != "" { + opts.HelmRepository = o.HelmRepository + } + if o.CACert != "" { + opts.CACert = o.CACert + } + if o.CACertFile != "" { + opts.CACertFile = o.CACertFile + } + if o.Printer != nil { + opts.Printer = o.Printer + } +} + +func (o *Options) OCIContext() oci.Context { + if o.Context == nil { + return oci.DefaultContext() + } + return o.Context +} + +//////////////////////////////////////////////////////////////////////////////// + +type context struct { + oci.Context +} + +func (o context) ApplyTo(opts *Options) { + opts.Context = o +} + +func WithContext(ctx oci.ContextProvider) Option { + return context{ctx.OCIContext()} +} + +//////////////////////////////////////////////////////////////////////////////// + +type fileSystem struct { + fs vfs.FileSystem +} + +func (o *fileSystem) ApplyTo(opts *Options) { + opts.FileSystem = o.fs +} + +func WithFileSystem(fs vfs.FileSystem) Option { + return &fileSystem{fs: fs} +} + +//////////////////////////////////////////////////////////////////////////////// + +type version string + +func (o version) ApplyTo(opts *Options) { + opts.Version = string(o) +} + +func WithVersion(v string) Option { + return version(v) +} + +//////////////////////////////////////////////////////////////////////////////// + +type override struct { + flag bool + version string +} + +func (o *override) ApplyTo(opts *Options) { + opts.OverrideVersion = utils.BoolP(o.flag) + opts.Version = o.version +} + +func WithVersionOverride(v string, flag ...bool) Option { + return &override{ + version: v, + flag: utils.OptionalDefaultedBool(true, flag...), + } +} + +//////////////////////////////////////////////////////////////////////////////// + +type helmrepo string + +func (o helmrepo) ApplyTo(opts *Options) { + opts.HelmRepository = string(o) +} + +// WithHelmRepository defines the helm repository to read from. +func WithHelmRepository(v string) Option { + return helmrepo(v) +} + +//////////////////////////////////////////////////////////////////////////////// + +type cacert string + +func (o cacert) ApplyTo(opts *Options) { + opts.CACert = string(o) +} + +func WithCACert(v string) Option { + return cacert(v) +} + +//////////////////////////////////////////////////////////////////////////////// + +type cacertfile string + +func (o cacertfile) ApplyTo(opts *Options) { + opts.CACertFile = string(o) +} + +func WithCACertFile(v string) Option { + return cacertfile(v) +} + +//////////////////////////////////////////////////////////////////////////////// + +type printer struct { + common.Printer +} + +func (o printer) ApplyTo(opts *Options) { + opts.Printer = o +} + +func WithPrinter(p common.Printer) Option { + return printer{p} +} diff --git a/api/utils/blobaccess/interface.go b/api/utils/blobaccess/interface.go new file mode 100644 index 000000000..58922ca8d --- /dev/null +++ b/api/utils/blobaccess/interface.go @@ -0,0 +1,30 @@ +package blobaccess + +import ( + "ocm.software/ocm/api/utils/blobaccess/internal" +) + +const ( + KIND_BLOB = internal.KIND_BLOB + KIND_MEDIATYPE = internal.KIND_MEDIATYPE + + BLOB_UNKNOWN_SIZE = internal.BLOB_UNKNOWN_SIZE + BLOB_UNKNOWN_DIGEST = internal.BLOB_UNKNOWN_DIGEST +) + +type ( + DataAccess = internal.DataAccess + DataReader = internal.DataReader + DataGetter = internal.DataGetter +) + +type ( + BlobAccess = internal.BlobAccess + BlobAccessProvider = internal.BlobAccessProvider + + DataSource = internal.DataSource + DigestSource = internal.DigestSource + MimeType = internal.MimeType +) + +type FileLocation = internal.FileLocation diff --git a/pkg/blobaccess/internal/interface.go b/api/utils/blobaccess/internal/interface.go similarity index 100% rename from pkg/blobaccess/internal/interface.go rename to api/utils/blobaccess/internal/interface.go diff --git a/api/utils/blobaccess/maven/access.go b/api/utils/blobaccess/maven/access.go new file mode 100644 index 000000000..c3a37acfc --- /dev/null +++ b/api/utils/blobaccess/maven/access.go @@ -0,0 +1,37 @@ +package maven + +import ( + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/tech/maven" + "ocm.software/ocm/api/utils/blobaccess/bpi" +) + +func DataAccess(repo *maven.Repository, groupId, artifactId, version string, opts ...Option) (bpi.DataAccess, error) { + return BlobAccess(repo, groupId, artifactId, version, opts...) +} + +func BlobAccess(repo *maven.Repository, groupId, artifactId, version string, opts ...Option) (bpi.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 BlobAccessForCoords(repo *maven.Repository, coords *maven.Coordinates, opts ...Option) (bpi.BlobAccess, error) { + return BlobAccess(repo, coords.GroupId, coords.ArtifactId, coords.Version, optionutils.WithDefaults(opts, WithOptionalClassifier(coords.Classifier), WithOptionalExtension(coords.Extension))...) +} + +func Provider(repo *maven.Repository, groupId, artifactId, version string, opts ...Option) bpi.BlobAccessProvider { + return bpi.BlobAccessProviderFunction(func() (bpi.BlobAccess, error) { + b, err := BlobAccess(repo, groupId, artifactId, version, opts...) + return b, err + }) +} + +func ProviderCoords(repo *maven.Repository, coords *maven.Coordinates, opts ...Option) bpi.BlobAccessProvider { + return Provider(repo, coords.GroupId, coords.ArtifactId, coords.Version, optionutils.WithDefaults(opts, WithOptionalClassifier(coords.Classifier), WithOptionalExtension(coords.Extension))...) +} diff --git a/api/utils/blobaccess/maven/access_test.go b/api/utils/blobaccess/maven/access_test.go new file mode 100644 index 000000000..565c8c48e --- /dev/null +++ b/api/utils/blobaccess/maven/access_test.go @@ -0,0 +1,128 @@ +package maven_test + +import ( + "time" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + + "ocm.software/ocm/api/tech/maven" + "ocm.software/ocm/api/tech/maven/maventest" + me "ocm.software/ocm/api/utils/blobaccess/maven" + "ocm.software/ocm/api/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.BlobAccess(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.BlobAccessForCoords(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.BlobAccessForCoords(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.BlobAccessForCoords(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.BlobAccessForCoords(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.BlobAccessForCoords(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.BlobAccessForCoords(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/api/utils/blobaccess/maven/deprecated.go b/api/utils/blobaccess/maven/deprecated.go new file mode 100644 index 000000000..26bf90d14 --- /dev/null +++ b/api/utils/blobaccess/maven/deprecated.go @@ -0,0 +1,36 @@ +package maven + +import ( + "ocm.software/ocm/api/tech/maven" + "ocm.software/ocm/api/utils/blobaccess/bpi" +) + +// DataAccessForMaven returns a DataAccess for the Maven artifact with the given coordinates. +// Deprecated: use DataAccess. +func DataAccessForMaven(repo *maven.Repository, groupId, artifactId, version string, opts ...Option) (bpi.DataAccess, error) { + return DataAccess(repo, groupId, artifactId, version, opts...) +} + +// BlobAccessForMaven returns a BlobAccess for the Maven artifact with the given coordinates. +// Deprecated: use BlobAccess. +func BlobAccessForMaven(repo *maven.Repository, groupId, artifactId, version string, opts ...Option) (bpi.BlobAccess, error) { + return BlobAccess(repo, groupId, artifactId, version, opts...) +} + +// BlobAccessForMavenCoords returns a BlobAccessProvider for the Maven artifact with the given coordinates. +// Deprecated: use BlobAccessForCoords. +func BlobAccessForMavenCoords(repo *maven.Repository, coords *maven.Coordinates, opts ...Option) (bpi.BlobAccess, error) { + return BlobAccessForCoords(repo, coords, opts...) +} + +// BlobAccessProviderForMaven returns a BlobAccessProvider for the Maven artifact with the given coordinates. +// Deprecated: use Provider. +func BlobAccessProviderForMaven(repo *maven.Repository, groupId, artifactId, version string, opts ...Option) bpi.BlobAccessProvider { + return Provider(repo, groupId, artifactId, version, opts...) +} + +// BlobAccessProviderForMavenCoords returns a BlobAccessProvider for the Maven artifact with the given coordinates. +// Deprecated: use ProviderCoords. +func BlobAccessProviderForMavenCoords(repo *maven.Repository, coords *maven.Coordinates, opts ...Option) bpi.BlobAccessProvider { + return ProviderCoords(repo, coords, opts...) +} diff --git a/pkg/blobaccess/maven/maven.go b/api/utils/blobaccess/maven/maven.go similarity index 94% rename from pkg/blobaccess/maven/maven.go rename to api/utils/blobaccess/maven/maven.go index 5d334937b..3ef96f1d6 100644 --- a/pkg/blobaccess/maven/maven.go +++ b/api/utils/blobaccess/maven/maven.go @@ -4,7 +4,7 @@ import ( "github.com/mandelsoft/goutils/optionutils" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/maven" + "ocm.software/ocm/api/tech/maven" ) type ( diff --git a/api/utils/blobaccess/maven/options.go b/api/utils/blobaccess/maven/options.go new file mode 100644 index 000000000..8b05860ea --- /dev/null +++ b/api/utils/blobaccess/maven/options.go @@ -0,0 +1,202 @@ +package maven + +import ( + "github.com/mandelsoft/goutils/optionutils" + "github.com/mandelsoft/logging" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/datacontext/attrs/tmpcache" + "ocm.software/ocm/api/tech/maven" + ocmlog "ocm.software/ocm/api/utils/logging" +) + +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 + + maven.FileCoordinates +} + +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 &optionutils.NoOption[*Options]{} +} + +//////////////////////////////////////////////////////////////////////////////// + +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 &optionutils.NoOption[*Options]{} +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/pkg/blobaccess/maven/suite_test.go b/api/utils/blobaccess/maven/suite_test.go similarity index 100% rename from pkg/blobaccess/maven/suite_test.go rename to api/utils/blobaccess/maven/suite_test.go diff --git a/api/utils/blobaccess/maven/utils.go b/api/utils/blobaccess/maven/utils.go new file mode 100644 index 000000000..9e61f0c69 --- /dev/null +++ b/api/utils/blobaccess/maven/utils.go @@ -0,0 +1,168 @@ +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" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/builtin/maven/identity" + "ocm.software/ocm/api/tech/maven" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/bpi" + "ocm.software/ocm/api/utils/blobaccess/file" + "ocm.software/ocm/api/utils/iotools" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/api/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 file.BlobAccessForTemporaryFilePath(mime.MIME_TGZ, tgz.Name(), file.WithFileSystem(fs), file.WithDigest(dw.Digest()), file.WithSize(dw.Size())), 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/api/utils/blobaccess/ociartifact/access.go b/api/utils/blobaccess/ociartifact/access.go new file mode 100644 index 000000000..ac5ed136b --- /dev/null +++ b/api/utils/blobaccess/ociartifact/access.go @@ -0,0 +1,55 @@ +package ociartifact + +import ( + "fmt" + + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/utils/blobaccess/bpi" +) + +func BlobAccess(refname string, opts ...Option) (bpi.BlobAccess, string, error) { + eff := optionutils.EvalOptions(opts...) + + eff.Printf("image %s\n", refname) + ref, err := oci.ParseRef(refname) + if err != nil { + return nil, "", err + } + + spec, err := eff.OCIContext().MapUniformRepositorySpec(&ref.UniformRepositorySpec) + if err != nil { + return nil, "", err + } + + repo, err := eff.OCIContext().RepositoryForSpec(spec) + if err != nil { + return nil, "", err + } + ns, err := repo.LookupNamespace(ref.Repository) + if err != nil { + return nil, "", err + } + + version := ref.Version() + if version == "" || version == "latest" { + version = eff.Version + } + if version == "" { + return nil, "", fmt.Errorf("no version specified") + } + blob, err := artifactset.SynthesizeArtifactBlobWithFilter(ns, version, eff.Filter) + if err != nil { + return nil, "", err + } + return blob, version, nil +} + +func Provider(name string, opts ...Option) bpi.BlobAccessProvider { + return bpi.BlobAccessProviderFunction(func() (bpi.BlobAccess, error) { + b, _, err := BlobAccess(name, opts...) + return b, err + }) +} diff --git a/api/utils/blobaccess/ociartifact/deprecated.go b/api/utils/blobaccess/ociartifact/deprecated.go new file mode 100644 index 000000000..d1a0f0a76 --- /dev/null +++ b/api/utils/blobaccess/ociartifact/deprecated.go @@ -0,0 +1,17 @@ +package ociartifact + +import ( + "ocm.software/ocm/api/utils/blobaccess/bpi" +) + +// BlobAccessForOCIArtifact returns a BlobAccess for the OCI artifact with the given refname. +// Deprecated: use BlobAccess. +func BlobAccessForOCIArtifact(refname string, opts ...Option) (bpi.BlobAccess, string, error) { + return BlobAccess(refname, opts...) +} + +// BlobAccessProviderForOCIArtifact returns a BlobAccessProvider for the OCI artifact with the given name. +// Deprecated: use Provider. +func BlobAccessProviderForOCIArtifact(name string, opts ...Option) bpi.BlobAccessProvider { + return Provider(name, opts...) +} diff --git a/api/utils/blobaccess/ociartifact/options.go b/api/utils/blobaccess/ociartifact/options.go new file mode 100644 index 000000000..1420ddfc2 --- /dev/null +++ b/api/utils/blobaccess/ociartifact/options.go @@ -0,0 +1,112 @@ +package ociartifact + +import ( + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/tools/transfer/filters" + common "ocm.software/ocm/api/utils/misc" +) + +type Option = optionutils.Option[*Options] + +type Filter = filters.Filter + +type Options struct { + Context oci.Context + Version string + Filter Filter + Printer common.Printer +} + +func (o *Options) OCIContext() oci.Context { + if o.Context == nil { + return oci.DefaultContext() + } + return o.Context +} + +func (o *Options) GetPrinter() common.Printer { + if o.Printer == nil { + return common.NewPrinter(nil) + } + return o.Printer +} + +func (o *Options) Printf(msg string, args ...interface{}) { + if o.Printer != nil { + o.Printer.Printf(msg, args...) + } +} + +func (o *Options) ApplyTo(opts *Options) { + if opts == nil { + return + } + if o.Context != nil { + opts.Context = o.Context + } + if o.Version != "" { + opts.Version = o.Version + } + if o.Printer != nil { + opts.Printer = o.Printer + } + if o.Filter != nil { + opts.Filter = o.Filter + } +} + +//////////////////////////////////////////////////////////////////////////////// + +type context struct { + oci.Context +} + +func (o context) ApplyTo(opts *Options) { + opts.Context = o +} + +func WithContext(ctx oci.ContextProvider) Option { + return context{ctx.OCIContext()} +} + +//////////////////////////////////////////////////////////////////////////////// + +type version string + +func (o version) ApplyTo(opts *Options) { + opts.Version = string(o) +} + +func WithVersion(v string) Option { + return version(v) +} + +//////////////////////////////////////////////////////////////////////////////// + +type printer struct { + common.Printer +} + +func (o printer) ApplyTo(opts *Options) { + opts.Printer = o +} + +func WithPrinter(p common.Printer) Option { + return printer{p} +} + +//////////////////////////////////////////////////////////////////////////////// + +type _filter struct { + filters.Filter +} + +func (o _filter) ApplyTo(opts *Options) { + opts.Filter = o.Filter +} + +func WithFilter(f filters.Filter) Option { + return _filter{f} +} diff --git a/api/utils/blobaccess/wget/access.go b/api/utils/blobaccess/wget/access.go new file mode 100644 index 000000000..36cb970ab --- /dev/null +++ b/api/utils/blobaccess/wget/access.go @@ -0,0 +1,181 @@ +package wget + +import ( + gocontext "context" + "crypto/tls" + "encoding/base64" + "io" + "mime" + "net/http" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/builtin/wget/identity" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/bpi" + "ocm.software/ocm/api/utils/blobaccess/file" + ocmmime "ocm.software/ocm/api/utils/mime" +) + +const ( + CACHE_CONTENT_THRESHOLD = 4096 +) + +func DataAccess(url string, opts ...Option) (bpi.DataAccess, error) { + blobAccess, err := BlobAccess(url, opts...) + if err != nil { + return nil, err + } + return blobAccess, nil +} + +func BlobAccess(url string, opts ...Option) (_ bpi.BlobAccess, rerr error) { + eff := optionutils.EvalOptions(opts...) + log := eff.Logger("URL", url) + + creds, err := eff.GetCredentials(url) + if err != nil { + return nil, err + } + if creds == nil { + log.Debug("no credentials found for url {{url}}", "url", url) + } + + // configure http client + rootCAs, err := credentials.GetRootCAs(eff.CredentialContext, creds) + if rerr != nil { + return nil, err + } + clientCerts, err := credentials.GetClientCerts(eff.CredentialContext, creds) + if err != nil { + return nil, errors.New("client certificate and private key provided in credentials could not be loaded " + + "as tls certificate") + } + + transport := &http.Transport{ + TLSClientConfig: &tls.Config{ + MinVersion: tls.VersionTLS13, + RootCAs: rootCAs, + Certificates: clientCerts, + }, + } + + var redirectFunc func(req *http.Request, via []*http.Request) error = nil + if eff.NoRedirect != nil && *eff.NoRedirect { + redirectFunc = func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + } + } + + client := &http.Client{ + CheckRedirect: redirectFunc, + Transport: transport, + } + + if eff.Verb == "" { + eff.Verb = http.MethodGet + } + + // configure http request + request, err := http.NewRequestWithContext(gocontext.Background(), eff.Verb, url, eff.Body) + if err != nil { + return nil, err + } + + if eff.Header != nil { + for key, arr := range eff.Header { + for _, el := range arr { + request.Header.Add(key, el) + } + } + } + + if creds != nil { + user := creds.GetProperty(identity.ATTR_USERNAME) + password := creds.GetProperty(identity.ATTR_PASSWORD) + token := creds.GetProperty(identity.ATTR_IDENTITY_TOKEN) + + if user != "" && password != "" { + auth := user + ":" + password + auth = base64.StdEncoding.EncodeToString([]byte(auth)) + request.Header.Add("Authorization", "Basic "+auth) + } else if token != "" { + request.Header.Add("Authorization", "Bearer "+token) + } + } + + // make http request + resp, err := client.Do(request) + if err != nil { + return nil, err + } + defer errors.PropagateError(&rerr, resp.Body.Close) + log.Debug("http status code {{code}}", "code", resp.StatusCode) + + // determine effective mime type + if eff.MimeType == "" { + log.Debug("no mime type provided as option, trying to extract mime type from content type " + + "response header") + + contentType := resp.Header.Get("Content-Type") + eff.MimeType, _, err = mime.ParseMediaType(contentType) + if err != nil { + log.Debug("failed to get mime type from content type response header with error {{err}}", + "err", err) + } + } + if eff.MimeType == "" { + log.Debug("no mime type was provided as content type header of the http response, trying to" + + "extract mime type from url") + ext, err := utils.GetFileExtensionFromUrl(url) + if err == nil && ext != "" { + eff.MimeType = mime.TypeByExtension(ext) + } else if err != nil { + log.Debug(err.Error()) + } + } + if eff.MimeType == "" { + eff.MimeType = ocmmime.MIME_OCTET + log.Debug("no mime type could be extract from the url, defaulting to {{default}}", "default", + eff.MimeType) + } + + // download content + var blob cpi.BlobAccess + if resp.ContentLength < 0 || resp.ContentLength > CACHE_CONTENT_THRESHOLD { + log.Debug("download to file because content length is unknown or greater than {{threshold}}", "threshold", CACHE_CONTENT_THRESHOLD) + f, err := file.NewTempFile("", "wget") + if err != nil { + return nil, err + } + defer errors.PropagateError(&rerr, f.Close) + + n, err := io.Copy(f.Writer(), resp.Body) + if err != nil { + return nil, err + } + log.Debug("downloaded size {{size}} to {{filepath}}", "size", n, "filepath", f.Name()) + + blob = f.AsBlob(eff.MimeType) + } else { + log.Debug("download to memory because content length is less than {{threshold}}", "threshold", CACHE_CONTENT_THRESHOLD) + buf, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + blob = blobaccess.ForData(eff.MimeType, buf) + } + + return blob, nil +} + +func Provider(url string, opts ...Option) bpi.BlobAccessProvider { + return bpi.BlobAccessProviderFunction(func() (bpi.BlobAccess, error) { + b, err := BlobAccess(url, opts...) + return b, err + }) +} diff --git a/api/utils/blobaccess/wget/deprecated.go b/api/utils/blobaccess/wget/deprecated.go new file mode 100644 index 000000000..4f1e20c9b --- /dev/null +++ b/api/utils/blobaccess/wget/deprecated.go @@ -0,0 +1,23 @@ +package wget + +import ( + "ocm.software/ocm/api/utils/blobaccess/bpi" +) + +// DataAccessForWget returns a DataAccess for the given URL. +// Deprecated: use DataAccess. +func DataAccessForWget(url string, opts ...Option) (bpi.DataAccess, error) { + return DataAccess(url, opts...) +} + +// BlobAccessForWget returns a BlobAccess for the given URL. +// Deprecated: use BlobAccess. +func BlobAccessForWget(url string, opts ...Option) (_ bpi.BlobAccess, rerr error) { + return BlobAccess(url, opts...) +} + +// BlobAccessProviderForWget returns a BlobAccessProvider for the given URL. +// Deprecated: use Provider. +func BlobAccessProviderForWget(url string, opts ...Option) bpi.BlobAccessProvider { + return Provider(url, opts...) +} diff --git a/api/utils/blobaccess/wget/logging.go b/api/utils/blobaccess/wget/logging.go new file mode 100644 index 000000000..a92b683ee --- /dev/null +++ b/api/utils/blobaccess/wget/logging.go @@ -0,0 +1,7 @@ +package wget + +import ( + ocmlog "ocm.software/ocm/api/utils/logging" +) + +var REALM = ocmlog.DefineSubRealm("blob access for wget", "blobaccess/wget") diff --git a/api/utils/blobaccess/wget/options.go b/api/utils/blobaccess/wget/options.go new file mode 100644 index 000000000..ae6561998 --- /dev/null +++ b/api/utils/blobaccess/wget/options.go @@ -0,0 +1,188 @@ +package wget + +import ( + "io" + "net/http" + + "github.com/mandelsoft/goutils/optionutils" + "github.com/mandelsoft/logging" + + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/builtin/wget/identity" + "ocm.software/ocm/api/utils" + ocmlog "ocm.software/ocm/api/utils/logging" +) + +type Option = optionutils.Option[*Options] + +type Options struct { + CredentialContext credentials.Context + LoggingContext logging.Context + // Header to be passed in the http request + Header http.Header + // Verb is the http verb to be used for the request + Verb string + // Body is the body to be included in the http request + Body io.Reader + // NoRedirect allows to disable redirects + NoRedirect *bool + // MimeType defines the media type of the downloaded content + MimeType string + // Credentials allows to pass credentials and certificates for the http communication + Credentials credentials.Credentials +} + +func (o *Options) Logger(keyValuePairs ...interface{}) logging.Logger { + return ocmlog.LogContext(o.LoggingContext, o.CredentialContext).Logger(REALM).WithValues(keyValuePairs...) +} + +func (o *Options) GetCredentials(url string) (credentials.Credentials, error) { + switch { + case o.Credentials != nil: + return o.Credentials, nil + case o.CredentialContext != nil: + creds, err := credentials.CredentialsForConsumer(o.CredentialContext, identity.GetConsumerId(url), identity.IdentityMatcher) + if err != nil { + return nil, err + } + return creds, nil + default: + return nil, nil + } +} + +func (o *Options) ApplyTo(opts *Options) { + if opts == nil { + return + } + if o.MimeType != "" { + opts.MimeType = o.MimeType + } + if o.CredentialContext != nil { + opts.CredentialContext = o.CredentialContext + } + if o.LoggingContext != nil { + opts.LoggingContext = o.LoggingContext + } + if o.Credentials != nil { + opts.Credentials = o.Credentials + } + if o.Header != nil { + opts.Header = o.Header + } + if o.Verb != "" { + opts.Verb = o.Verb + } + if o.Body != nil { + opts.Body = o.Body + } + if o.NoRedirect != nil { + opts.NoRedirect = o.NoRedirect + } +} + +//////////////////////////////////////////////////////////////////////////////// + +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 mimeType string + +func (o mimeType) ApplyTo(opts *Options) { + opts.MimeType = string(o) +} + +func WithMimeType(mime string) Option { + return mimeType(mime) +} + +//////////////////////////////////////////////////////////////////////////////// + +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 header http.Header + +func (o header) ApplyTo(opts *Options) { + opts.Header = http.Header(o) +} + +func WithHeader(h http.Header) Option { + return header(h) +} + +//////////////////////////////////////////////////////////////////////////////// + +type verb string + +func (o verb) ApplyTo(opts *Options) { + opts.Verb = string(o) +} + +func WithVerb(v string) Option { + return verb(v) +} + +//////////////////////////////////////////////////////////////////////////////// + +type body struct { + io.Reader +} + +func (o *body) ApplyTo(opts *Options) { + if o.Reader != nil { + opts.Body = io.Reader(o) + } +} + +func WithBody(v io.Reader) Option { + return &body{v} +} + +//////////////////////////////////////////////////////////////////////////////// + +type noredirect bool + +func (o noredirect) ApplyTo(opts *Options) { + opts.NoRedirect = utils.BoolP(o) +} + +func WithNoRedirect(r ...bool) Option { + return noredirect(utils.OptionalDefaultedBool(true, r...)) +} diff --git a/api/utils/clisupport/labels.go b/api/utils/clisupport/labels.go new file mode 100644 index 000000000..8fae0340d --- /dev/null +++ b/api/utils/clisupport/labels.go @@ -0,0 +1,94 @@ +package clisupport + +import ( + "encoding/json" + "strings" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/vfs/pkg/osfs" + "github.com/mandelsoft/vfs/pkg/vfs" + "sigs.k8s.io/yaml" + + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/utils" +) + +func gkind(kind ...string) string { + for _, k := range kind { + if k != "" { + return k + } + } + return "label" +} + +func ParseLabel(fs vfs.FileSystem, a string, kind ...string) (*metav1.Label, error) { + var err error + + if fs == nil { + fs = osfs.New() + } + i := strings.Index(a, "=") + if i < 0 { + return nil, errors.ErrInvalid(gkind(kind...), a) + } + label := a[:i] + + data, err := utils.ResolveData(a[i+1:], fs) + if err != nil { + return nil, err + } + + var value interface{} + err = yaml.Unmarshal(data, &value) + if err != nil { + return nil, errors.Wrapf(errors.ErrInvalid(gkind(kind...), a), "no yaml or json") + } + data, err = json.Marshal(value) + if err != nil { + return nil, errors.Wrapf(errors.ErrInvalid(gkind(kind...), a), err.Error()) + } + return &metav1.Label{ + Name: strings.TrimSpace(label), + Value: json.RawMessage(data), + }, nil +} + +func AddParsedLabel(fs vfs.FileSystem, labels metav1.Labels, a string, kind ...string) (metav1.Labels, error) { + l, err := ParseLabel(fs, a, kind...) + if err != nil { + return nil, err + } + for _, c := range labels { + if c.Name == l.Name { + return nil, errors.Newf("duplicate %s %q", gkind(kind...), l.Name) + } + } + return append(labels, *l), nil +} + +func ParseLabels(fs vfs.FileSystem, labels []string, kind ...string) (metav1.Labels, error) { + var err error + result := metav1.Labels{} + for _, l := range labels { + result, err = AddParsedLabel(fs, result, l, kind...) + if err != nil { + return nil, err + } + } + return result, err +} + +func SetParsedLabel(fs vfs.FileSystem, labels metav1.Labels, a string, kind ...string) (metav1.Labels, error) { + l, err := ParseLabel(fs, a, kind...) + if err != nil { + return nil, err + } + for i, c := range labels { + if c.Name == l.Name { + labels[i].Value = l.Value + return labels, nil + } + } + return append(labels, *l), nil +} diff --git a/pkg/cobrautils/cleanup_test.go b/api/utils/cobrautils/cleanup_test.go similarity index 93% rename from pkg/cobrautils/cleanup_test.go rename to api/utils/cobrautils/cleanup_test.go index 0afb7b42f..9455a0854 100644 --- a/pkg/cobrautils/cleanup_test.go +++ b/api/utils/cobrautils/cleanup_test.go @@ -5,7 +5,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/cobrautils" + "ocm.software/ocm/api/utils/cobrautils" ) var _ = Describe("processing buffer", func() { diff --git a/pkg/cobrautils/flag/bytes.go b/api/utils/cobrautils/flag/bytes.go similarity index 100% rename from pkg/cobrautils/flag/bytes.go rename to api/utils/cobrautils/flag/bytes.go diff --git a/pkg/cobrautils/flag/labelledstring.go b/api/utils/cobrautils/flag/labelledstring.go similarity index 100% rename from pkg/cobrautils/flag/labelledstring.go rename to api/utils/cobrautils/flag/labelledstring.go diff --git a/pkg/cobrautils/flag/labelledstring_test.go b/api/utils/cobrautils/flag/labelledstring_test.go similarity index 100% rename from pkg/cobrautils/flag/labelledstring_test.go rename to api/utils/cobrautils/flag/labelledstring_test.go diff --git a/pkg/cobrautils/flag/labelledvalue.go b/api/utils/cobrautils/flag/labelledvalue.go similarity index 100% rename from pkg/cobrautils/flag/labelledvalue.go rename to api/utils/cobrautils/flag/labelledvalue.go diff --git a/pkg/cobrautils/flag/labelledvalue_test.go b/api/utils/cobrautils/flag/labelledvalue_test.go similarity index 100% rename from pkg/cobrautils/flag/labelledvalue_test.go rename to api/utils/cobrautils/flag/labelledvalue_test.go diff --git a/pkg/cobrautils/flag/path.go b/api/utils/cobrautils/flag/path.go similarity index 100% rename from pkg/cobrautils/flag/path.go rename to api/utils/cobrautils/flag/path.go diff --git a/pkg/cobrautils/flag/path_array.go b/api/utils/cobrautils/flag/path_array.go similarity index 100% rename from pkg/cobrautils/flag/path_array.go rename to api/utils/cobrautils/flag/path_array.go diff --git a/pkg/cobrautils/flag/path_array_test.go b/api/utils/cobrautils/flag/path_array_test.go similarity index 92% rename from pkg/cobrautils/flag/path_array_test.go rename to api/utils/cobrautils/flag/path_array_test.go index 95f308961..2c21e3a2b 100644 --- a/pkg/cobrautils/flag/path_array_test.go +++ b/api/utils/cobrautils/flag/path_array_test.go @@ -5,7 +5,7 @@ package flag_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/cobrautils/flag" + . "ocm.software/ocm/api/utils/cobrautils/flag" "github.com/spf13/pflag" ) diff --git a/pkg/cobrautils/flag/path_array_win_test.go b/api/utils/cobrautils/flag/path_array_win_test.go similarity index 92% rename from pkg/cobrautils/flag/path_array_win_test.go rename to api/utils/cobrautils/flag/path_array_win_test.go index 7be5fbec6..be185f03c 100644 --- a/pkg/cobrautils/flag/path_array_win_test.go +++ b/api/utils/cobrautils/flag/path_array_win_test.go @@ -5,7 +5,7 @@ package flag_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/cobrautils/flag" + . "ocm.software/ocm/api/utils/cobrautils/flag" "github.com/spf13/pflag" ) diff --git a/pkg/cobrautils/flag/path_test.go b/api/utils/cobrautils/flag/path_test.go similarity index 92% rename from pkg/cobrautils/flag/path_test.go rename to api/utils/cobrautils/flag/path_test.go index dad81f330..859634b63 100644 --- a/pkg/cobrautils/flag/path_test.go +++ b/api/utils/cobrautils/flag/path_test.go @@ -5,7 +5,7 @@ package flag_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/cobrautils/flag" + . "ocm.software/ocm/api/utils/cobrautils/flag" "github.com/spf13/pflag" ) diff --git a/pkg/cobrautils/flag/path_win_test.go b/api/utils/cobrautils/flag/path_win_test.go similarity index 92% rename from pkg/cobrautils/flag/path_win_test.go rename to api/utils/cobrautils/flag/path_win_test.go index 7c28732aa..0b7bc1354 100644 --- a/pkg/cobrautils/flag/path_win_test.go +++ b/api/utils/cobrautils/flag/path_win_test.go @@ -5,7 +5,7 @@ package flag_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/cobrautils/flag" + . "ocm.software/ocm/api/utils/cobrautils/flag" "github.com/spf13/pflag" ) diff --git a/pkg/cobrautils/flag/replace.go b/api/utils/cobrautils/flag/replace.go similarity index 100% rename from pkg/cobrautils/flag/replace.go rename to api/utils/cobrautils/flag/replace.go diff --git a/pkg/cobrautils/flag/semver.go b/api/utils/cobrautils/flag/semver.go similarity index 100% rename from pkg/cobrautils/flag/semver.go rename to api/utils/cobrautils/flag/semver.go diff --git a/pkg/cobrautils/flag/semver_constraint.go b/api/utils/cobrautils/flag/semver_constraint.go similarity index 100% rename from pkg/cobrautils/flag/semver_constraint.go rename to api/utils/cobrautils/flag/semver_constraint.go diff --git a/pkg/cobrautils/flag/string_colon_stringslice.go b/api/utils/cobrautils/flag/string_colon_stringslice.go similarity index 100% rename from pkg/cobrautils/flag/string_colon_stringslice.go rename to api/utils/cobrautils/flag/string_colon_stringslice.go diff --git a/pkg/cobrautils/flag/string_colon_stringslice_test.go b/api/utils/cobrautils/flag/string_colon_stringslice_test.go similarity index 98% rename from pkg/cobrautils/flag/string_colon_stringslice_test.go rename to api/utils/cobrautils/flag/string_colon_stringslice_test.go index 09f0a5ef1..da62b295a 100644 --- a/pkg/cobrautils/flag/string_colon_stringslice_test.go +++ b/api/utils/cobrautils/flag/string_colon_stringslice_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/open-component-model/ocm/pkg/cobrautils/flag" + "ocm.software/ocm/api/utils/cobrautils/flag" ) func setUpSCSSFlagSet(scssp *MySSM) *FlagSet { diff --git a/pkg/cobrautils/flag/string_to_string.go b/api/utils/cobrautils/flag/string_to_string.go similarity index 100% rename from pkg/cobrautils/flag/string_to_string.go rename to api/utils/cobrautils/flag/string_to_string.go diff --git a/pkg/cobrautils/flag/string_to_string_test.go b/api/utils/cobrautils/flag/string_to_string_test.go similarity index 98% rename from pkg/cobrautils/flag/string_to_string_test.go rename to api/utils/cobrautils/flag/string_to_string_test.go index 786c4c658..3f140e384 100644 --- a/pkg/cobrautils/flag/string_to_string_test.go +++ b/api/utils/cobrautils/flag/string_to_string_test.go @@ -15,7 +15,7 @@ import ( "github.com/spf13/pflag" - "github.com/open-component-model/ocm/pkg/cobrautils/flag" + "ocm.software/ocm/api/utils/cobrautils/flag" ) type ( diff --git a/pkg/cobrautils/flag/string_to_stringslice.go b/api/utils/cobrautils/flag/string_to_stringslice.go similarity index 100% rename from pkg/cobrautils/flag/string_to_stringslice.go rename to api/utils/cobrautils/flag/string_to_stringslice.go diff --git a/pkg/cobrautils/flag/string_to_stringslice_test.go b/api/utils/cobrautils/flag/string_to_stringslice_test.go similarity index 98% rename from pkg/cobrautils/flag/string_to_stringslice_test.go rename to api/utils/cobrautils/flag/string_to_stringslice_test.go index 589abb8bf..1231232e2 100644 --- a/pkg/cobrautils/flag/string_to_stringslice_test.go +++ b/api/utils/cobrautils/flag/string_to_stringslice_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/open-component-model/ocm/pkg/cobrautils/flag" + "ocm.software/ocm/api/utils/cobrautils/flag" ) // MySSM is My String Slice Map diff --git a/pkg/cobrautils/flag/string_to_value.go b/api/utils/cobrautils/flag/string_to_value.go similarity index 100% rename from pkg/cobrautils/flag/string_to_value.go rename to api/utils/cobrautils/flag/string_to_value.go diff --git a/pkg/cobrautils/flag/string_to_value_test.go b/api/utils/cobrautils/flag/string_to_value_test.go similarity index 100% rename from pkg/cobrautils/flag/string_to_value_test.go rename to api/utils/cobrautils/flag/string_to_value_test.go diff --git a/pkg/cobrautils/flag/suite_test.go b/api/utils/cobrautils/flag/suite_test.go similarity index 100% rename from pkg/cobrautils/flag/suite_test.go rename to api/utils/cobrautils/flag/suite_test.go diff --git a/pkg/cobrautils/flag/yaml.go b/api/utils/cobrautils/flag/yaml.go similarity index 100% rename from pkg/cobrautils/flag/yaml.go rename to api/utils/cobrautils/flag/yaml.go diff --git a/pkg/cobrautils/flag/yaml_test.go b/api/utils/cobrautils/flag/yaml_test.go similarity index 100% rename from pkg/cobrautils/flag/yaml_test.go rename to api/utils/cobrautils/flag/yaml_test.go diff --git a/pkg/cobrautils/flagsets/configoptions.go b/api/utils/cobrautils/flagsets/configoptions.go similarity index 100% rename from pkg/cobrautils/flagsets/configoptions.go rename to api/utils/cobrautils/flagsets/configoptions.go diff --git a/pkg/cobrautils/flagsets/configoptionset.go b/api/utils/cobrautils/flagsets/configoptionset.go similarity index 99% rename from pkg/cobrautils/flagsets/configoptionset.go rename to api/utils/cobrautils/flagsets/configoptionset.go index fe8c07831..ae846beb2 100644 --- a/pkg/cobrautils/flagsets/configoptionset.go +++ b/api/utils/cobrautils/flagsets/configoptionset.go @@ -6,7 +6,7 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" ) type ConfigOptionType interface { diff --git a/pkg/cobrautils/flagsets/configure_test.go b/api/utils/cobrautils/flagsets/configure_test.go similarity index 96% rename from pkg/cobrautils/flagsets/configure_test.go rename to api/utils/cobrautils/flagsets/configure_test.go index acea6b9df..28dd22b3a 100644 --- a/pkg/cobrautils/flagsets/configure_test.go +++ b/api/utils/cobrautils/flagsets/configure_test.go @@ -7,7 +7,7 @@ import ( "github.com/spf13/pflag" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" + "ocm.software/ocm/api/utils/cobrautils/flagsets" ) func adder(opts flagsets.ConfigOptions, data flagsets.Config) error { diff --git a/pkg/cobrautils/flagsets/filter.go b/api/utils/cobrautils/flagsets/filter.go similarity index 100% rename from pkg/cobrautils/flagsets/filter.go rename to api/utils/cobrautils/flagsets/filter.go diff --git a/pkg/cobrautils/flagsets/flags_test.go b/api/utils/cobrautils/flagsets/flags_test.go similarity index 97% rename from pkg/cobrautils/flagsets/flags_test.go rename to api/utils/cobrautils/flagsets/flags_test.go index 110ba911a..602aa654f 100644 --- a/pkg/cobrautils/flagsets/flags_test.go +++ b/api/utils/cobrautils/flagsets/flags_test.go @@ -6,7 +6,7 @@ import ( "github.com/spf13/pflag" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" + "ocm.software/ocm/api/utils/cobrautils/flagsets" ) var _ = Describe("type set", func() { diff --git a/pkg/cobrautils/flagsets/flagsetscheme/doc.go b/api/utils/cobrautils/flagsets/flagsetscheme/doc.go similarity index 100% rename from pkg/cobrautils/flagsets/flagsetscheme/doc.go rename to api/utils/cobrautils/flagsets/flagsetscheme/doc.go diff --git a/api/utils/cobrautils/flagsets/flagsetscheme/scheme.go b/api/utils/cobrautils/flagsets/flagsetscheme/scheme.go new file mode 100644 index 000000000..f0ed2ea6c --- /dev/null +++ b/api/utils/cobrautils/flagsets/flagsetscheme/scheme.go @@ -0,0 +1,129 @@ +package flagsetscheme + +import ( + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/api/utils/logging" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/api/utils/runtime/descriptivetype" +) + +// VersionTypedObjectType is the appropriately extended type interface +// based on runtime.VersionTypedObjectType. +type VersionTypedObjectType[T runtime.VersionedTypedObject] interface { + descriptivetype.TypedObjectType[T] + + ConfigOptionTypeSetHandler() flagsets.ConfigOptionTypeSetHandler +} + +//////////////////////////////////////////////////////////////////////////////// + +// ExtendedTypeScheme is the appropriately extended scheme interface based on +// runtime.TypeScheme supporting an extended config provider interface. +type ExtendedTypeScheme[T runtime.VersionedTypedObject, R VersionTypedObjectType[T], P flagsets.ConfigTypeOptionSetConfigProvider] interface { + descriptivetype.TypeScheme[T, R] + + CreateConfigTypeSetConfigProvider() P + + Unwrap() TypeScheme[T, R] +} + +type _TypeScheme[T runtime.VersionedTypedObject, R VersionTypedObjectType[T]] interface { + TypeScheme[T, R] +} + +type typeSchemeWrapper[T runtime.VersionedTypedObject, R VersionTypedObjectType[T], P flagsets.ConfigTypeOptionSetConfigProvider] struct { + _TypeScheme[T, R] +} + +func (s *typeSchemeWrapper[T, R, P]) CreateConfigTypeSetConfigProvider() P { + return s._TypeScheme.CreateConfigTypeSetConfigProvider().(P) +} + +func (s *typeSchemeWrapper[T, R, P]) Unwrap() TypeScheme[T, R] { + return s._TypeScheme +} + +// NewTypeSchemeWrapper wraps a [TypeScheme] into a scheme returning a specialized config provider +// by casting the result. The type scheme constructor provides different implementations based on its +// arguments. This method here can be used to provide a type scheme returning the correct type. +func NewTypeSchemeWrapper[T runtime.VersionedTypedObject, R VersionTypedObjectType[T], P flagsets.ConfigTypeOptionSetConfigProvider](s TypeScheme[T, R]) ExtendedTypeScheme[T, R, P] { + return &typeSchemeWrapper[T, R, P]{s} +} + +// TypeScheme is the appropriately extended scheme interface based on +// runtime.TypeScheme. +type TypeScheme[T runtime.VersionedTypedObject, R VersionTypedObjectType[T]] interface { + ExtendedTypeScheme[T, R, flagsets.ConfigTypeOptionSetConfigProvider] +} + +type _typeScheme[T runtime.VersionedTypedObject, R VersionTypedObjectType[T]] interface { + descriptivetype.TypeScheme[T, R] +} + +type typeScheme[T runtime.VersionedTypedObject, R VersionTypedObjectType[T], S TypeScheme[T, R]] struct { + cfgname string + description string + group string + typeOption string + _typeScheme[T, R] +} + +func flagExtender[T runtime.VersionedTypedObject, R VersionTypedObjectType[T]](ty R) string { + if h := ty.ConfigOptionTypeSetHandler(); h != nil { + return utils.IndentLines(flagsets.FormatConfigOptions(h), " ") + } + return "" +} + +// NewTypeScheme provides an TypeScheme implementation based on the interfaces +// and the default runtime.TypeScheme implementation. +func NewTypeScheme[T runtime.VersionedTypedObject, R VersionTypedObjectType[T], S TypeScheme[T, R]](kindname string, cfgname, typeOption, desc, group string, unknown runtime.Unstructured, acceptUnknown bool, base ...S) TypeScheme[T, R] { + scheme := descriptivetype.NewTypeScheme[T, R](kindname, flagExtender[T, R], unknown, acceptUnknown, utils.Optional(base...)) + return &typeScheme[T, R, S]{ + cfgname: cfgname, + description: desc, + group: group, + typeOption: typeOption, + _typeScheme: scheme, + } +} + +func (s *typeScheme[T, R, S]) Unwrap() TypeScheme[T, R] { + return s +} + +func (t *typeScheme[T, R, S]) CreateConfigTypeSetConfigProvider() flagsets.ConfigTypeOptionSetConfigProvider { + var prov flagsets.ConfigTypeOptionSetConfigProvider + if t.typeOption == "" { + prov = flagsets.NewExplicitlyTypedConfigProvider(t.cfgname, t.description, true) + } else { + prov = flagsets.NewTypedConfigProvider(t.cfgname, t.description, t.typeOption, true) + } + if t.group != "" { + prov.AddGroups(t.group) + } + for _, p := range t.KnownTypes() { + err := prov.AddTypeSet(p.ConfigOptionTypeSetHandler()) + if err != nil { + logging.Logger().LogError(err, "cannot compose type CLI options", "type", t.cfgname) + } + } + if t.BaseScheme() != nil { + base := t.BaseScheme() + for _, s := range base.(S).CreateConfigTypeSetConfigProvider().OptionTypeSets() { + if prov.GetTypeSet(s.GetName()) == nil { + err := prov.AddTypeSet(s) + if err != nil { + logging.Logger().LogError(err, "cannot compose type CLI options", "type", t.cfgname) + } + } + } + } + + return prov +} + +func (t *typeScheme[T, R, S]) KnownTypes() runtime.KnownTypes[T, R] { + return t._typeScheme.KnownTypes() // Goland +} diff --git a/api/utils/cobrautils/flagsets/flagsetscheme/types.go b/api/utils/cobrautils/flagsets/flagsetscheme/types.go new file mode 100644 index 000000000..1cf41b482 --- /dev/null +++ b/api/utils/cobrautils/flagsets/flagsetscheme/types.go @@ -0,0 +1,62 @@ +package flagsetscheme + +import ( + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/api/utils/runtime/descriptivetype" +) + +type additionalTypeInfo interface { + ConfigOptionTypeSetHandler() flagsets.ConfigOptionTypeSetHandler + Description() string + Format() string +} + +type TypedObjectTypeObject[E runtime.VersionedTypedObject] struct { + *descriptivetype.TypedObjectTypeObject[E] + handler flagsets.ConfigOptionTypeSetHandler + validator func(E) error +} + +var _ additionalTypeInfo = (*TypedObjectTypeObject[runtime.VersionedTypedObject])(nil) + +func NewTypedObjectTypeObject[E runtime.VersionedTypedObject](vt runtime.VersionedTypedObjectType[E], opts ...TypeOption) *TypedObjectTypeObject[E] { + t := NewTypeObjectTarget[E](&TypedObjectTypeObject[E]{ + TypedObjectTypeObject: descriptivetype.NewTypedObjectTypeObject[E](vt), + }) + optionutils.ApplyOptions[OptionTarget](t, opts...) + return t.target +} + +func (t *TypedObjectTypeObject[E]) ConfigOptionTypeSetHandler() flagsets.ConfigOptionTypeSetHandler { + return t.handler +} + +func (t *TypedObjectTypeObject[E]) Validate(e E) error { + if t.validator == nil { + return nil + } + return t.validator(e) +} + +//////////////////////////////////////////////////////////////////////////////// + +// TypeObjectTarget is used as target for option functions, it provides +// setters for fields, which should nor be modifiable for a final type object. +type TypeObjectTarget[E runtime.VersionedTypedObject] struct { + *descriptivetype.TypeObjectTarget[E] + target *TypedObjectTypeObject[E] +} + +func NewTypeObjectTarget[E runtime.VersionedTypedObject](target *TypedObjectTypeObject[E]) *TypeObjectTarget[E] { + return &TypeObjectTarget[E]{ + target: target, + TypeObjectTarget: descriptivetype.NewTypeObjectTarget[E](target.TypedObjectTypeObject), + } +} + +func (t TypeObjectTarget[E]) SetConfigHandler(value flagsets.ConfigOptionTypeSetHandler) { + t.target.handler = value +} diff --git a/pkg/cobrautils/flagsets/flagsetscheme/types_options.go b/api/utils/cobrautils/flagsets/flagsetscheme/types_options.go similarity index 89% rename from pkg/cobrautils/flagsets/flagsetscheme/types_options.go rename to api/utils/cobrautils/flagsets/flagsetscheme/types_options.go index 1e84507b4..f6283b976 100644 --- a/pkg/cobrautils/flagsets/flagsetscheme/types_options.go +++ b/api/utils/cobrautils/flagsets/flagsetscheme/types_options.go @@ -3,8 +3,8 @@ package flagsetscheme import ( "github.com/mandelsoft/goutils/optionutils" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/runtime/descriptivetype" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/api/utils/runtime/descriptivetype" ) //////////////////////////////////////////////////////////////////////////////// diff --git a/pkg/cobrautils/flagsets/handler.go b/api/utils/cobrautils/flagsets/handler.go similarity index 100% rename from pkg/cobrautils/flagsets/handler.go rename to api/utils/cobrautils/flagsets/handler.go diff --git a/api/utils/cobrautils/flagsets/provider.go b/api/utils/cobrautils/flagsets/provider.go new file mode 100644 index 000000000..2a9a7b677 --- /dev/null +++ b/api/utils/cobrautils/flagsets/provider.go @@ -0,0 +1,252 @@ +package flagsets + +import ( + "fmt" + "strings" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" +) + +type ConfigProvider interface { + CreateOptions() ConfigOptions + GetConfigFor(opts ConfigOptions) (Config, error) +} + +type ConfigTypeOptionSetConfigProvider interface { + ConfigProvider + ConfigOptionTypeSet + + GetPlainOptionType() ConfigOptionType + GetTypeOptionType() ConfigOptionType + + IsExplicitlySelected(opts ConfigOptions) bool +} + +type _ConfigTypeOptionSetConfigProvider = ConfigTypeOptionSetConfigProvider + +//////////////////////////////////////////////////////////////////////////////// + +type plainConfigProvider struct { + ConfigOptionTypeSetHandler +} + +var _ ConfigTypeOptionSetConfigProvider = (*plainConfigProvider)(nil) + +func NewPlainConfigProvider(name string, adder ConfigAdder, types ...ConfigOptionType) ConfigTypeOptionSetConfigProvider { + h := NewConfigOptionTypeSetHandler(name, adder, types...) + return &plainConfigProvider{ + ConfigOptionTypeSetHandler: h, + } +} + +func (p *plainConfigProvider) GetConfigOptionTypeSet() ConfigOptionTypeSet { + return p +} + +func (p *plainConfigProvider) GetPlainOptionType() ConfigOptionType { + return nil +} + +func (p *plainConfigProvider) GetTypeOptionType() ConfigOptionType { + return nil +} + +func (p *plainConfigProvider) IsExplicitlySelected(opts ConfigOptions) bool { + return opts.FilterBy(p.HasOptionType).Changed() +} + +func (p *plainConfigProvider) GetConfigFor(opts ConfigOptions) (Config, error) { + if !p.IsExplicitlySelected(opts) { + return nil, nil + } + config := Config{} + err := p.ApplyConfig(opts, config) + return config, err +} + +//////////////////////////////////////////////////////////////////////////////// + +type typedConfigProvider struct { + _ConfigTypeOptionSetConfigProvider + typeOptionType ConfigOptionType +} + +var _ ConfigTypeOptionSetConfigProvider = (*typedConfigProvider)(nil) + +func NewTypedConfigProvider(name string, desc, typeOption string, acceptUnknown ...bool) ConfigTypeOptionSetConfigProvider { + typeOpt := NewStringOptionType(name+"Type", "type of "+desc) + return &typedConfigProvider{NewTypedConfigProviderBase(name, desc, TypeNameProviderFromOptions(typeOption), utils.Optional(acceptUnknown...), typeOpt), typeOpt} +} + +func (p *typedConfigProvider) GetTypeOptionType() ConfigOptionType { + return p.typeOptionType +} + +func (p *typedConfigProvider) IsExplicitlySelected(opts ConfigOptions) bool { + return opts.Changed(p.typeOptionType.GetName(), p.GetName()) +} + +func TypeNameProviderFromOptions(name string) TypeNameProvider { + return func(opts ConfigOptions) (string, error) { + typv, _ := opts.GetValue(name) + typ, ok := typv.(string) + if !ok { + return "", fmt.Errorf("failed to assert type %T as string", typv) + } + return typ, nil + } +} + +/////////////////////////////////////////////////////////////////////////////// + +type ExplicitlyTypedConfigTypeOptionSetConfigProvider interface { + ConfigTypeOptionSetConfigProvider + SetTypeName(n string) +} + +type explicitlyTypedConfigProvider struct { + _ConfigTypeOptionSetConfigProvider + typeName string +} + +var _ ConfigTypeOptionSetConfigProvider = (*typedConfigProvider)(nil) + +func NewExplicitlyTypedConfigProvider(name string, desc string, acceptUnknown ...bool) ExplicitlyTypedConfigTypeOptionSetConfigProvider { + p := &explicitlyTypedConfigProvider{} + p._ConfigTypeOptionSetConfigProvider = NewTypedConfigProviderBase(name, desc, p.getTypeName, utils.Optional(acceptUnknown...)) + return p +} + +func (p *explicitlyTypedConfigProvider) SetTypeName(n string) { + p.typeName = n +} + +func (p *explicitlyTypedConfigProvider) getTypeName(opts ConfigOptions) (string, error) { + return p.typeName, nil +} + +//////////////////////////////////////////////////////////////////////////////// + +type TypeNameProvider func(opts ConfigOptions) (string, error) + +type typedConfigProviderBase struct { + ConfigOptionTypeSet + typeProvider TypeNameProvider + meta ConfigOptionTypeSet + acceptUnknown bool + plainOptionType ConfigOptionType +} + +var _ ConfigTypeOptionSetConfigProvider = (*typedConfigProviderBase)(nil) + +func NewTypedConfigProviderBase(name string, desc string, prov TypeNameProvider, acceptUnknown bool, types ...ConfigOptionType) ConfigTypeOptionSetConfigProvider { + plainType := NewValueMapYAMLOptionType(name, desc+" (YAML)") + set := NewConfigOptionTypeSet(name, append(types, plainType)...) + return &typedConfigProviderBase{ + ConfigOptionTypeSet: set, + typeProvider: prov, + meta: NewConfigOptionTypeSet(name, append(types, NewValueMapYAMLOptionType(name, desc+" (YAML)"))...), + acceptUnknown: acceptUnknown, + plainOptionType: plainType, + } +} + +func (p *typedConfigProviderBase) GetConfigOptionTypeSet() ConfigOptionTypeSet { + return p +} + +func (p *typedConfigProviderBase) GetPlainOptionType() ConfigOptionType { + return p.plainOptionType +} + +func (p *typedConfigProviderBase) GetTypeOptionType() ConfigOptionType { + return nil +} + +func (p *typedConfigProviderBase) IsExplicitlySelected(opts ConfigOptions) bool { + t, err := p.typeProvider(opts) + return err == nil && t != "" +} + +func (p *typedConfigProviderBase) GetConfigFor(opts ConfigOptions) (Config, error) { + typ, err := p.typeProvider(opts) + if err != nil { + return nil, err + } + cfgv, _ := opts.GetValue(p.GetName()) + + var data Config + if cfgv != nil { + var ok bool + data, ok = cfgv.(Config) + if !ok { + return nil, fmt.Errorf("failed to assert type %T as Config", cfgv) + } + } + + opts = opts.FilterBy(p.HasOptionType) + if typ == "" && data != nil && data["type"] != nil { + t := data["type"] + if t != nil { + if s, ok := t.(string); ok { + typ = s + } else { + return nil, fmt.Errorf("type field must be a string") + } + } + } + + if opts.Changed() || typ != "" { + if typ == "" { + return nil, fmt.Errorf("type required for explicitly configured options") + } + if data == nil { + data = Config{} + } + data["type"] = typ + if err := p.applyConfigForType(typ, opts, data); err != nil { + if !p.acceptUnknown || !errors.Is(err, errors.ErrUnknown(typ)) { + return nil, err + } + unexpected := opts.FilterBy(And(Changed(opts), Not(p.meta.HasOptionType))).Names() + if len(unexpected) > 0 { + return nil, fmt.Errorf("unexpected options %s", strings.Join(unexpected, ", ")) + } + } + } + return data, nil +} + +func (p *typedConfigProviderBase) GetTypeSetForType(name string) ConfigOptionTypeSet { + set := p.GetTypeSet(name) + if set == nil { + k, v := runtime.KindVersion(name) + if v == "" { + set = p.GetTypeSet(runtime.TypeName(name, "v1")) + } else if v == "v1" { + set = p.GetTypeSet(k) + } + } + return set +} + +func (p *typedConfigProviderBase) applyConfigForType(name string, opts ConfigOptions, config Config) error { + set := p.GetTypeSetForType(name) + if set == nil { + return errors.ErrUnknown(name) + } + + opts = opts.FilterBy(Not(p.meta.HasOptionType)) + err := opts.Check(set, p.GetName()+" type "+name) + if err != nil { + return err + } + handler, ok := set.(ConfigHandler) + if !ok { + return fmt.Errorf("no config handler defined for %s type %s", p.GetName(), name) + } + return handler.ApplyConfig(opts, config) +} diff --git a/pkg/cobrautils/flagsets/suite_test.go b/api/utils/cobrautils/flagsets/suite_test.go similarity index 100% rename from pkg/cobrautils/flagsets/suite_test.go rename to api/utils/cobrautils/flagsets/suite_test.go diff --git a/api/utils/cobrautils/flagsets/types.go b/api/utils/cobrautils/flagsets/types.go new file mode 100644 index 000000000..5863a8e37 --- /dev/null +++ b/api/utils/cobrautils/flagsets/types.go @@ -0,0 +1,553 @@ +package flagsets + +import ( + "reflect" + + "github.com/spf13/pflag" + + "ocm.software/ocm/api/utils/cobrautils/flag" + "ocm.software/ocm/api/utils/cobrautils/groups" +) + +type TypeOptionBase struct { + name string + description string +} + +func (b *TypeOptionBase) GetName() string { + return b.name +} + +func (b *TypeOptionBase) GetDescription() string { + return b.description +} + +//////////////////////////////////////////////////////////////////////////////// + +type OptionBase struct { + otyp ConfigOptionType + flag *pflag.Flag + groups []string +} + +func NewOptionBase(otyp ConfigOptionType) OptionBase { + return OptionBase{otyp: otyp} +} + +func (b *OptionBase) Type() ConfigOptionType { + return b.otyp +} + +func (b *OptionBase) GetName() string { + return b.otyp.GetName() +} + +func (b *OptionBase) Description() string { + return b.otyp.GetDescription() +} + +func (b *OptionBase) Changed() bool { + return b.flag.Changed +} + +func (b *OptionBase) AddGroups(groups ...string) { + b.groups = AddGroups(b.groups, groups...) + b.addGroups() +} + +func (b *OptionBase) addGroups() { + if len(b.groups) == 0 || b.flag == nil { + return + } + if b.flag.Annotations == nil { + b.flag.Annotations = map[string][]string{} + } + list := b.flag.Annotations[groups.FlagGroupAnnotation] + b.flag.Annotations[groups.FlagGroupAnnotation] = AddGroups(list, b.groups...) +} + +func (b *OptionBase) TweakFlag(f *pflag.Flag) { + b.flag = f + b.addGroups() +} + +//////////////////////////////////////////////////////////////////////////////// + +type StringOptionType struct { + TypeOptionBase +} + +func NewStringOptionType(name string, description string) ConfigOptionType { + return &StringOptionType{ + TypeOptionBase: TypeOptionBase{name, description}, + } +} + +func (s *StringOptionType) Equal(optionType ConfigOptionType) bool { + return reflect.DeepEqual(s, optionType) +} + +func (s *StringOptionType) Create() Option { + return &StringOption{ + OptionBase: NewOptionBase(s), + } +} + +type StringOption struct { + OptionBase + value string +} + +var _ Option = (*StringOption)(nil) + +func (o *StringOption) AddFlags(fs *pflag.FlagSet) { + o.TweakFlag(flag.StringVarPF(fs, &o.value, o.otyp.GetName(), "", "", o.otyp.GetDescription())) +} + +func (o *StringOption) Value() interface{} { + return o.value +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +type StringArrayOptionType struct { + TypeOptionBase +} + +func NewStringArrayOptionType(name string, description string) ConfigOptionType { + return &StringArrayOptionType{ + TypeOptionBase: TypeOptionBase{name, description}, + } +} + +func (s *StringArrayOptionType) Equal(optionType ConfigOptionType) bool { + return reflect.DeepEqual(s, optionType) +} + +func (s *StringArrayOptionType) Create() Option { + return &StringArrayOption{ + OptionBase: NewOptionBase(s), + } +} + +type StringArrayOption struct { + OptionBase + value []string +} + +var _ Option = (*StringArrayOption)(nil) + +func (o *StringArrayOption) AddFlags(fs *pflag.FlagSet) { + o.TweakFlag(flag.StringArrayVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription())) +} + +func (o *StringArrayOption) Value() interface{} { + return o.value +} + +// PathOptionType ////////////////////////////////////////////////////////////////////////////// + +type PathOptionType struct { + TypeOptionBase +} + +func NewPathOptionType(name string, description string) ConfigOptionType { + return &PathOptionType{ + TypeOptionBase: TypeOptionBase{name, description}, + } +} + +func (s *PathOptionType) Equal(optionType ConfigOptionType) bool { + return reflect.DeepEqual(s, optionType) +} + +func (s *PathOptionType) Create() Option { + return &PathOption{ + OptionBase: NewOptionBase(s), + } +} + +type PathOption struct { + OptionBase + value string +} + +var _ Option = (*PathOption)(nil) + +func (o *PathOption) AddFlags(fs *pflag.FlagSet) { + o.TweakFlag(flag.PathVarPF(fs, &o.value, o.otyp.GetName(), "", "", o.otyp.GetDescription())) +} + +func (o *PathOption) Value() interface{} { + return o.value +} + +// PathArrayOptionType ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +type PathArrayOptionType struct { + TypeOptionBase +} + +func NewPathArrayOptionType(name string, description string) ConfigOptionType { + return &PathArrayOptionType{ + TypeOptionBase: TypeOptionBase{name, description}, + } +} + +func (s *PathArrayOptionType) Equal(optionType ConfigOptionType) bool { + return reflect.DeepEqual(s, optionType) +} + +func (s *PathArrayOptionType) Create() Option { + return &PathArrayOption{ + OptionBase: NewOptionBase(s), + } +} + +type PathArrayOption struct { + OptionBase + value []string +} + +var _ Option = (*PathArrayOption)(nil) + +func (o *PathArrayOption) AddFlags(fs *pflag.FlagSet) { + o.TweakFlag(flag.PathArrayVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription())) +} + +func (o *PathArrayOption) Value() interface{} { + return o.value +} + +//////////////////////////////////////////////////////////////////////////////// + +type BoolOptionType struct { + TypeOptionBase +} + +func NewBoolOptionType(name string, description string) ConfigOptionType { + return &BoolOptionType{ + TypeOptionBase: TypeOptionBase{name, description}, + } +} + +func (s *BoolOptionType) Equal(optionType ConfigOptionType) bool { + return reflect.DeepEqual(s, optionType) +} + +func (s *BoolOptionType) Create() Option { + return &BoolOption{ + OptionBase: NewOptionBase(s), + } +} + +type BoolOption struct { + OptionBase + value bool +} + +var _ Option = (*BoolOption)(nil) + +func (o *BoolOption) AddFlags(fs *pflag.FlagSet) { + o.TweakFlag(flag.BoolVarPF(fs, &o.value, o.otyp.GetName(), "", false, o.otyp.GetDescription())) +} + +func (o *BoolOption) Value() interface{} { + return o.value +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +type IntOptionType struct { + TypeOptionBase +} + +func NewIntOptionType(name string, description string) ConfigOptionType { + return &IntOptionType{ + TypeOptionBase: TypeOptionBase{name, description}, + } +} + +func (s *IntOptionType) Equal(optionType ConfigOptionType) bool { + return reflect.DeepEqual(s, optionType) +} + +func (s *IntOptionType) Create() Option { + return &IntOption{ + OptionBase: NewOptionBase(s), + } +} + +type IntOption struct { + OptionBase + value int +} + +var _ Option = (*IntOption)(nil) + +func (o *IntOption) AddFlags(fs *pflag.FlagSet) { + o.TweakFlag(flag.IntVarPF(fs, &o.value, o.otyp.GetName(), "", 0, o.otyp.GetDescription())) +} + +func (o *IntOption) Value() interface{} { + return o.value +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +type YAMLOptionType struct { + TypeOptionBase +} + +func NewYAMLOptionType(name string, description string) ConfigOptionType { + return &YAMLOptionType{ + TypeOptionBase: TypeOptionBase{name, description}, + } +} + +func (s *YAMLOptionType) Equal(optionType ConfigOptionType) bool { + return reflect.DeepEqual(s, optionType) +} + +func (s *YAMLOptionType) Create() Option { + return &YAMLOption{ + OptionBase: NewOptionBase(s), + } +} + +type YAMLOption struct { + OptionBase + value interface{} +} + +var _ Option = (*YAMLOption)(nil) + +func (o *YAMLOption) AddFlags(fs *pflag.FlagSet) { + o.TweakFlag(flag.YAMLVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription())) +} + +func (o *YAMLOption) Value() interface{} { + return o.value +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +type ValueMapYAMLOptionType struct { + TypeOptionBase +} + +func NewValueMapYAMLOptionType(name string, description string) ConfigOptionType { + return &ValueMapYAMLOptionType{ + TypeOptionBase: TypeOptionBase{name, description}, + } +} + +func (s *ValueMapYAMLOptionType) Equal(optionType ConfigOptionType) bool { + return reflect.DeepEqual(s, optionType) +} + +func (s *ValueMapYAMLOptionType) Create() Option { + return &ValueMapYAMLOption{ + OptionBase: NewOptionBase(s), + } +} + +type ValueMapYAMLOption struct { + OptionBase + value map[string]interface{} +} + +var _ Option = (*ValueMapYAMLOption)(nil) + +func (o *ValueMapYAMLOption) AddFlags(fs *pflag.FlagSet) { + o.TweakFlag(flag.YAMLVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription())) +} + +func (o *ValueMapYAMLOption) Value() interface{} { + return o.value +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +type ValueMapOptionType struct { + TypeOptionBase +} + +func NewValueMapOptionType(name string, description string) ConfigOptionType { + return &ValueMapOptionType{ + TypeOptionBase: TypeOptionBase{name, description}, + } +} + +func (s *ValueMapOptionType) Equal(optionType ConfigOptionType) bool { + return reflect.DeepEqual(s, optionType) +} + +func (s *ValueMapOptionType) Create() Option { + return &ValueMapOption{ + OptionBase: NewOptionBase(s), + } +} + +type ValueMapOption struct { + OptionBase + value map[string]interface{} +} + +var _ Option = (*ValueMapOption)(nil) + +func (o *ValueMapOption) AddFlags(fs *pflag.FlagSet) { + o.TweakFlag(flag.StringToValueVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription())) +} + +func (o *ValueMapOption) Value() interface{} { + return o.value +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +type StringMapOptionType struct { + TypeOptionBase +} + +func NewStringMapOptionType(name string, description string) ConfigOptionType { + return &StringMapOptionType{ + TypeOptionBase: TypeOptionBase{name, description}, + } +} + +func (s *StringMapOptionType) Equal(optionType ConfigOptionType) bool { + return reflect.DeepEqual(s, optionType) +} + +func (s *StringMapOptionType) Create() Option { + return &StringMapOption{ + OptionBase: NewOptionBase(s), + } +} + +type StringMapOption struct { + OptionBase + value map[string]string +} + +var _ Option = (*StringMapOption)(nil) + +func (o *StringMapOption) AddFlags(fs *pflag.FlagSet) { + o.TweakFlag(flag.StringToStringVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription())) +} + +func (o *StringMapOption) Value() interface{} { + return o.value +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +type BytesOptionType struct { + TypeOptionBase +} + +func NewBytesOptionType(name string, description string) ConfigOptionType { + return &BytesOptionType{ + TypeOptionBase: TypeOptionBase{name, description}, + } +} + +func (s *BytesOptionType) Equal(optionType ConfigOptionType) bool { + return reflect.DeepEqual(s, optionType) +} + +func (s *BytesOptionType) Create() Option { + return &BytesOption{ + OptionBase: NewOptionBase(s), + } +} + +type BytesOption struct { + OptionBase + value []byte +} + +var _ Option = (*BytesOption)(nil) + +func (o *BytesOption) AddFlags(fs *pflag.FlagSet) { + o.TweakFlag(flag.BytesBase64VarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription())) +} + +func (o *BytesOption) Value() interface{} { + return o.value +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +type StringSliceMapOptionType struct { + TypeOptionBase +} + +func NewStringSliceMapOptionType(name string, description string) ConfigOptionType { + return &StringSliceMapOptionType{ + TypeOptionBase: TypeOptionBase{name, description}, + } +} + +func (s *StringSliceMapOptionType) Equal(optionType ConfigOptionType) bool { + return reflect.DeepEqual(s, optionType) +} + +func (s *StringSliceMapOptionType) Create() Option { + return &StringSliceMapOption{ + OptionBase: NewOptionBase(s), + } +} + +type StringSliceMapOption struct { + OptionBase + value map[string][]string +} + +var _ Option = (*StringSliceMapOption)(nil) + +func (o *StringSliceMapOption) AddFlags(fs *pflag.FlagSet) { + o.TweakFlag(flag.StringToStringSliceVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription())) +} + +func (o *StringSliceMapOption) Value() interface{} { + return o.value +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +type StringSliceMapColonOptionType struct { + TypeOptionBase +} + +func NewStringSliceMapColonOptionType(name string, description string) ConfigOptionType { + return &StringSliceMapColonOptionType{ + TypeOptionBase: TypeOptionBase{name, description}, + } +} + +func (s *StringSliceMapColonOptionType) Equal(optionType ConfigOptionType) bool { + return reflect.DeepEqual(s, optionType) +} + +func (s *StringSliceMapColonOptionType) Create() Option { + return &StringSliceMapColonOption{ + OptionBase: NewOptionBase(s), + } +} + +type StringSliceMapColonOption struct { + OptionBase + value map[string][]string +} + +var _ Option = (*StringSliceMapColonOption)(nil) + +func (o *StringSliceMapColonOption) AddFlags(fs *pflag.FlagSet) { + o.TweakFlag(flag.StringColonStringSliceVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription())) +} + +func (o *StringSliceMapColonOption) Value() interface{} { + return o.value +} diff --git a/pkg/cobrautils/flagsets/typeset_test.go b/api/utils/cobrautils/flagsets/typeset_test.go similarity index 96% rename from pkg/cobrautils/flagsets/typeset_test.go rename to api/utils/cobrautils/flagsets/typeset_test.go index 8bbfa48bf..77fb84b7d 100644 --- a/pkg/cobrautils/flagsets/typeset_test.go +++ b/api/utils/cobrautils/flagsets/typeset_test.go @@ -4,7 +4,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" + "ocm.software/ocm/api/utils/cobrautils/flagsets" ) var _ = Describe("type set", func() { diff --git a/pkg/cobrautils/flagsets/utils.go b/api/utils/cobrautils/flagsets/utils.go similarity index 100% rename from pkg/cobrautils/flagsets/utils.go rename to api/utils/cobrautils/flagsets/utils.go diff --git a/api/utils/cobrautils/flagsets/utils_test.go b/api/utils/cobrautils/flagsets/utils_test.go new file mode 100644 index 000000000..34a3a4a38 --- /dev/null +++ b/api/utils/cobrautils/flagsets/utils_test.go @@ -0,0 +1,112 @@ +package flagsets_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/utils/cobrautils/flagsets" +) + +type Config = flagsets.Config + +var ( + GetField = flagsets.GetField + SetField = flagsets.SetField +) + +var _ = Describe("config", func() { + Context("get", func() { + It("gets a field from empty map", func() { + m := flagsets.Config{} + Expect(GetField(m, "a")).To(BeNil()) + }) + + It("gets a non existing field", func() { + m := Config{} + m["b"] = "vb" + Expect(GetField(m, "a")).To(BeNil()) + }) + + It("gets a flat field", func() { + m := Config{} + m["a"] = "va" + Expect(GetField(m, "a")).To(Equal("va")) + }) + + It("gets a deep field", func() { + m := Config{} + a := Config{} + m["a"] = a + a["b"] = "vb" + Expect(GetField(m, "a", "b")).To(Equal("vb")) + }) + + It("fails for non map", func() { + m := Config{} + m["a"] = "va" + _, err := GetField(m, "a", "b") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("a is no map")) + }) + + It("fails for deep non map", func() { + m := Config{} + a := Config{} + m["a"] = a + a["b"] = "va" + _, err := GetField(m, "a", "b", "c") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("a.b is no map")) + }) + }) + + Context("set", func() { + It("fails for empty map", func() { + err := SetField(nil, "x", "a") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("no map given")) + }) + + It("sets flat field", func() { + m := Config{} + err := SetField(m, "x", "a") + Expect(err).To(Succeed()) + Expect(m).To(Equal(Config{"a": "x"})) + }) + + It("adds flat field", func() { + m := Config{"b": "y"} + err := SetField(m, "x", "a") + Expect(err).To(Succeed()) + Expect(m).To(Equal(Config{"a": "x", "b": "y"})) + }) + + It("sets deep field", func() { + m := Config{"a": Config{}} + err := SetField(m, "x", "a", "b") + Expect(err).To(Succeed()) + Expect(m).To(Equal(Config{"a": Config{"b": "x"}})) + }) + + It("inserts intermediate maps", func() { + m := Config{"a": Config{}} + err := SetField(m, "x", "a", "b", "c", "d") + Expect(err).To(Succeed()) + Expect(m).To(Equal(Config{"a": Config{"b": Config{"c": Config{"d": "x"}}}})) + }) + + It("fails for non map", func() { + m := Config{"a": "va"} + err := SetField(m, "x", "a", "b") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("a is no map")) + }) + + It("fails for non map intermediate", func() { + m := Config{"a": "va"} + err := SetField(m, "x", "a", "b", "c") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("a is no map")) + }) + }) +}) diff --git a/pkg/cobrautils/funcs.go b/api/utils/cobrautils/funcs.go similarity index 98% rename from pkg/cobrautils/funcs.go rename to api/utils/cobrautils/funcs.go index 965398a91..8abb9e9b0 100644 --- a/pkg/cobrautils/funcs.go +++ b/api/utils/cobrautils/funcs.go @@ -12,7 +12,7 @@ import ( "golang.org/x/text/cases" "golang.org/x/text/language" - "github.com/open-component-model/ocm/pkg/cobrautils/groups" + "ocm.software/ocm/api/utils/cobrautils/groups" ) var templatefuncs = map[string]interface{}{ diff --git a/pkg/cobrautils/groups/flagusages.go b/api/utils/cobrautils/groups/flagusages.go similarity index 97% rename from pkg/cobrautils/groups/flagusages.go rename to api/utils/cobrautils/groups/flagusages.go index b6fe2c12a..1b3150e24 100644 --- a/pkg/cobrautils/groups/flagusages.go +++ b/api/utils/cobrautils/groups/flagusages.go @@ -7,7 +7,7 @@ import ( "github.com/spf13/pflag" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" ) const FlagGroupAnnotation = "flag-group-annotation" diff --git a/pkg/cobrautils/links.go b/api/utils/cobrautils/links.go similarity index 100% rename from pkg/cobrautils/links.go rename to api/utils/cobrautils/links.go diff --git a/api/utils/cobrautils/logopts/close_test.go b/api/utils/cobrautils/logopts/close_test.go new file mode 100644 index 000000000..fdc7efd9b --- /dev/null +++ b/api/utils/cobrautils/logopts/close_test.go @@ -0,0 +1,55 @@ +package logopts + +import ( + "runtime" + "time" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/mandelsoft/logging" + "github.com/mandelsoft/vfs/pkg/osfs" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/ocm" + logging2 "ocm.software/ocm/api/utils/cobrautils/logopts/logging" +) + +var _ = Describe("log file", func() { + var fs vfs.FileSystem + + BeforeEach(func() { + fs = Must(osfs.NewTempFileSystem()) + }) + + AfterEach(func() { + vfs.Cleanup(fs) + }) + + It("closes log file", func() { + ctx := ocm.New(datacontext.MODE_INITIAL) + lctx := logging.NewDefault() + + vfsattr.Set(ctx, fs) + + opts := &Options{ + ConfigFragment: ConfigFragment{ + LogLevel: "debug", + LogFileName: "debug.log", + }, + } + + MustBeSuccessful(opts.Configure(ctx, lctx)) + + Expect(logging2.GetLogFileFor(opts.LogFileName, fs)).NotTo(BeNil()) + lctx = nil + for i := 1; i < 100; i++ { + time.Sleep(1 * time.Millisecond) + runtime.GC() + } + Expect(logging2.GetLogFileFor(opts.LogFileName, fs)).To(BeNil()) + }) +}) diff --git a/api/utils/cobrautils/logopts/config.go b/api/utils/cobrautils/logopts/config.go new file mode 100644 index 000000000..149ad3d0d --- /dev/null +++ b/api/utils/cobrautils/logopts/config.go @@ -0,0 +1,146 @@ +package logopts + +import ( + "strings" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/logging" + "github.com/mandelsoft/logging/config" + "github.com/mandelsoft/logging/logrusl/adapter" + "github.com/mandelsoft/logging/logrusr" + "github.com/mandelsoft/vfs/pkg/vfs" + "github.com/sirupsen/logrus" + "github.com/spf13/pflag" + + "ocm.software/ocm/api/datacontext/attrs/logforward" + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/utils" + logdata "ocm.software/ocm/api/utils/cobrautils/logopts/logging" +) + +// ConfigFragment is a serializable log config used +// for CLI commands. +type ConfigFragment struct { + LogLevel string `json:"logLevel,omitempty"` + LogConfig string `json:"logConfig,omitempty"` + LogKeys []string `json:"logKeys,omitempty"` + Json bool `json:"json,omitempty"` + + // LogFileName is a CLI option, only. Do not serialize and forward + LogFileName string `json:"-"` +} + +func (c *ConfigFragment) AddFlags(fs *pflag.FlagSet) { + fs.BoolVarP(&c.Json, "logJson", "", false, "log as json instead of human readable logs") + fs.StringVarP(&c.LogLevel, "loglevel", "l", "", "set log level") + fs.StringVarP(&c.LogFileName, "logfile", "L", "", "set log file") + fs.StringVarP(&c.LogConfig, "logconfig", "", "", "log config") + fs.StringArrayVarP(&c.LogKeys, "logkeys", "", nil, "log tags/realms(with leading /) to be enabled ([/[+]]name{,[/[+]]name}[=level])") +} + +func (c *ConfigFragment) GetLogConfig(fss ...vfs.FileSystem) (*config.Config, error) { + var ( + err error + cfg *config.Config + ) + + if c.LogConfig != "" { + var data []byte + if strings.HasPrefix(c.LogConfig, "@") { + data, err = utils.ReadFile(c.LogConfig[1:], utils.FileSystem(fss...)) + if err != nil { + return nil, errors.Wrapf(err, "cannot read logging config file %q", c.LogConfig[1:]) + } + } else { + data = []byte(c.LogConfig) + } + if cfg, err = config.EvaluateFromData(data); err != nil { + return nil, errors.Wrapf(err, "invalid logging config: %q", c.LogConfig) + } + } else { + cfg = &config.Config{DefaultLevel: "Warn"} + } + + for _, t := range c.LogKeys { + level := logging.InfoLevel + i := strings.Index(t, "=") + if i >= 0 { + level, err = logging.ParseLevel(t[i+1:]) + if err != nil { + return nil, errors.Wrapf(err, "invalid log tag setting") + } + t = t[:i] + } + var cfgcond []config.Condition + + for _, tag := range strings.Split(t, ",") { + tag = strings.TrimSpace(tag) + if strings.HasPrefix(tag, "/") { + realm := tag[1:] + if strings.HasPrefix(realm, "+") { + cfgcond = append(cfgcond, config.RealmPrefix(realm[1:])) + } else { + cfgcond = append(cfgcond, config.Realm(realm)) + } + } else { + cfgcond = append(cfgcond, config.Tag(tag)) + } + } + cfg.Rules = append(cfg.Rules, config.ConditionalRule(logging.LevelName(level), cfgcond...)) + } + + if c.LogLevel != "" { + _, err := logging.ParseLevel(c.LogLevel) + if err != nil { + return nil, errors.Wrapf(err, "invalid log level %q", c.LogLevel) + } + cfg.DefaultLevel = c.LogLevel + } + + return cfg, nil +} + +func (c *ConfigFragment) Evaluate(ctx ocm.Context, logctx logging.Context, main bool) (*EvaluatedOptions, error) { + var err error + var opts EvaluatedOptions + + for logctx.Tree().GetBaseContext() != nil { + logctx = logctx.Tree().GetBaseContext() + } + + fs := vfsattr.Get(ctx) + if main && c.LogFileName != "" && logdata.GlobalLogFileOverride == "" { + if opts.LogFile == nil { + opts.LogFile, err = logdata.LogFileFor(c.LogFileName, fs) + if err != nil { + return nil, errors.Wrapf(err, "cannot open log file %q", opts.LogFile) + } + } + logdata.ConfigureLogrusFor(logctx, !c.Json, opts.LogFile) + if logctx == logging.DefaultContext() { + logdata.GlobalLogFile = opts.LogFile + } + } else { + // overwrite current log formatter in case of a logrus logger is + // used as logging backend. + var f logrus.Formatter = adapter.NewJSONFormatter() + if !c.Json { + f = adapter.NewTextFmtFormatter() + } + logrusr.SetFormatter(logging.UnwrapLogSink(logctx.GetSink()), f) + } + + cfg, err := c.GetLogConfig(fs) + if err != nil { + return &opts, err + } + err = config.Configure(logctx, cfg) + if err != nil { + return &opts, err + } + opts.LogForward = cfg + logforward.Set(ctx.AttributesContext(), opts.LogForward) + + return &opts, nil +} diff --git a/pkg/cobrautils/logopts/doc.go b/api/utils/cobrautils/logopts/doc.go similarity index 100% rename from pkg/cobrautils/logopts/doc.go rename to api/utils/cobrautils/logopts/doc.go diff --git a/pkg/cobrautils/logopts/logging/config.go b/api/utils/cobrautils/logopts/logging/config.go similarity index 100% rename from pkg/cobrautils/logopts/logging/config.go rename to api/utils/cobrautils/logopts/logging/config.go diff --git a/pkg/cobrautils/logopts/logging/global.go b/api/utils/cobrautils/logopts/logging/global.go similarity index 100% rename from pkg/cobrautils/logopts/logging/global.go rename to api/utils/cobrautils/logopts/logging/global.go diff --git a/pkg/cobrautils/logopts/logging/logfiles.go b/api/utils/cobrautils/logopts/logging/logfiles.go similarity index 100% rename from pkg/cobrautils/logopts/logging/logfiles.go rename to api/utils/cobrautils/logopts/logging/logfiles.go diff --git a/api/utils/cobrautils/logopts/options.go b/api/utils/cobrautils/logopts/options.go new file mode 100644 index 000000000..5f96ba9a6 --- /dev/null +++ b/api/utils/cobrautils/logopts/options.go @@ -0,0 +1,71 @@ +package logopts + +import ( + "github.com/mandelsoft/logging" + "github.com/mandelsoft/logging/config" + "github.com/spf13/pflag" + + "ocm.software/ocm/api/ocm" + logging2 "ocm.software/ocm/api/utils/cobrautils/logopts/logging" +) + +var Description = ` +The --log* options can be used to configure the logging behaviour. +For details see ocm logging. + +There is a quick config option --logkeys to configure simple +tag/realm based condition rules. The comma-separated names build an AND rule. +Hereby, names starting with a slash (/) denote a realm (without +the leading slash). A realm is a slash separated sequence of identifiers. If +the realm name starts with a plus (+) character the generated rule +will match the realm and all its sub-realms, otherwise, only the dedicated +realm is affected. For example /+ocm=trace will enable all log output of the +OCM library. + +A tag directly matches the logging tags. Used tags and realms can be found under +topic ocm logging. The ocm coding basically uses the realm ocm. +The default level to enable is info. Separated by an equal sign (=) +optionally a dedicated level can be specified. Log levels can be (error, +warn, info, debug and trace. +The default level is warn. +The --logconfig* options can be used to configure a complete +logging configuration (yaml/json) via command line. If the argument starts with +an @, the logging configuration is taken from a file. +` + +//////////////////////////////////////////////////////////////////////////////// + +type EvaluatedOptions struct { + LogForward *config.Config + LogFile *logging2.LogFile +} + +func (o *EvaluatedOptions) Close() error { + if o.LogFile == nil { + return nil + } + return o.LogFile.Close() +} + +type Options struct { + ConfigFragment + *EvaluatedOptions +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { + o.ConfigFragment.AddFlags(fs) +} + +func (o *Options) Close() error { + if o.EvaluatedOptions == nil { + return nil + } + return o.EvaluatedOptions.Close() +} + +func (o *Options) Configure(ctx ocm.Context, logctx logging.Context) error { + var err error + + o.EvaluatedOptions, err = o.ConfigFragment.Evaluate(ctx, logctx, true) + return err +} diff --git a/api/utils/cobrautils/logopts/options_test.go b/api/utils/cobrautils/logopts/options_test.go new file mode 100644 index 000000000..3e6765ae7 --- /dev/null +++ b/api/utils/cobrautils/logopts/options_test.go @@ -0,0 +1,59 @@ +package logopts + +import ( + "encoding/json" + "fmt" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/mandelsoft/logging" + "github.com/mandelsoft/logging/config" + "sigs.k8s.io/yaml" + + clictx "ocm.software/ocm/api/cli" +) + +var _ = Describe("log configuration", func() { + It("provides forward config", func() { + ctx := clictx.New() + + opts := Options{ + ConfigFragment: ConfigFragment{ + LogLevel: "debug", + LogKeys: []string{ + "tag=trace", + "/realm=info", + "/+all=info", + }, + }, + } + + MustBeSuccessful(opts.Configure(ctx.OCMContext(), ctx.LoggingContext())) + Expect(opts.LogForward).NotTo(BeNil()) + + data := Must(yaml.Marshal(opts.LogForward)) + fmt.Printf("%s\n", string(data)) + logctx := logging.NewWithBase(nil) + MustBeSuccessful(config.Configure(logctx, opts.LogForward)) + + Expect(logctx.GetDefaultLevel()).To(Equal(logging.DebugLevel)) + Expect(logctx.Logger(logging.NewTag("tag")).Enabled(logging.TraceLevel)).To(BeTrue()) + Expect(logctx.Logger(logging.NewRealm("all")).Enabled(logging.DebugLevel)).To(BeFalse()) + Expect(logctx.Logger(logging.NewRealm("all/test")).Enabled(logging.DebugLevel)).To(BeFalse()) + Expect(logctx.Logger(logging.NewRealm("realm")).Enabled(logging.InfoLevel)).To(BeTrue()) + Expect(logctx.Logger(logging.NewRealm("realm")).Enabled(logging.DebugLevel)).To(BeFalse()) + Expect(logctx.Logger(logging.NewRealm("realm/test")).Enabled(logging.InfoLevel)).To(BeTrue()) + Expect(logctx.Logger(logging.NewRealm("realm/test")).Enabled(logging.DebugLevel)).To(BeTrue()) + }) + + Context("serialize", func() { + It("does not serialize log file name", func() { + var c ConfigFragment + c.LogFileName = "test" + data := Must(json.Marshal(&c)) + Expect(string(data)).To(Equal("{}")) + }) + }) +}) diff --git a/pkg/cobrautils/logopts/suite_test.go b/api/utils/cobrautils/logopts/suite_test.go similarity index 100% rename from pkg/cobrautils/logopts/suite_test.go rename to api/utils/cobrautils/logopts/suite_test.go diff --git a/pkg/cobrautils/suite_test.go b/api/utils/cobrautils/suite_test.go similarity index 100% rename from pkg/cobrautils/suite_test.go rename to api/utils/cobrautils/suite_test.go diff --git a/pkg/cobrautils/template.go b/api/utils/cobrautils/template.go similarity index 100% rename from pkg/cobrautils/template.go rename to api/utils/cobrautils/template.go diff --git a/pkg/cobrautils/tweak.go b/api/utils/cobrautils/tweak.go similarity index 97% rename from pkg/cobrautils/tweak.go rename to api/utils/cobrautils/tweak.go index 5501fcfba..c1faf0ed3 100644 --- a/pkg/cobrautils/tweak.go +++ b/api/utils/cobrautils/tweak.go @@ -5,11 +5,11 @@ import ( "regexp" "strings" - _ "github.com/open-component-model/ocm/pkg/contexts/clictx/config" + _ "ocm.software/ocm/api/cli/config" "github.com/spf13/cobra" - "github.com/open-component-model/ocm/pkg/out" + "ocm.software/ocm/api/utils/out" ) func TweakCommand(cmd *cobra.Command, ctx out.Context) *cobra.Command { diff --git a/pkg/cobrautils/utils.go b/api/utils/cobrautils/utils.go similarity index 100% rename from pkg/cobrautils/utils.go rename to api/utils/cobrautils/utils.go diff --git a/pkg/common/compression/LICENSE b/api/utils/compression/LICENSE similarity index 100% rename from pkg/common/compression/LICENSE rename to api/utils/compression/LICENSE diff --git a/pkg/common/compression/README.md b/api/utils/compression/README.md similarity index 100% rename from pkg/common/compression/README.md rename to api/utils/compression/README.md diff --git a/pkg/common/compression/c_bzip2.go b/api/utils/compression/c_bzip2.go similarity index 100% rename from pkg/common/compression/c_bzip2.go rename to api/utils/compression/c_bzip2.go diff --git a/pkg/common/compression/c_gzip.go b/api/utils/compression/c_gzip.go similarity index 100% rename from pkg/common/compression/c_gzip.go rename to api/utils/compression/c_gzip.go diff --git a/pkg/common/compression/c_xz.go b/api/utils/compression/c_xz.go similarity index 100% rename from pkg/common/compression/c_xz.go rename to api/utils/compression/c_xz.go diff --git a/pkg/common/compression/c_zstd.go b/api/utils/compression/c_zstd.go similarity index 100% rename from pkg/common/compression/c_zstd.go rename to api/utils/compression/c_zstd.go diff --git a/pkg/common/compression/compression.go b/api/utils/compression/compression.go similarity index 100% rename from pkg/common/compression/compression.go rename to api/utils/compression/compression.go diff --git a/pkg/common/compression/compression_test.go b/api/utils/compression/compression_test.go similarity index 100% rename from pkg/common/compression/compression_test.go rename to api/utils/compression/compression_test.go diff --git a/pkg/common/compression/default.go b/api/utils/compression/default.go similarity index 100% rename from pkg/common/compression/default.go rename to api/utils/compression/default.go diff --git a/pkg/common/compression/fixtures/Hello.bz2 b/api/utils/compression/fixtures/Hello.bz2 similarity index 100% rename from pkg/common/compression/fixtures/Hello.bz2 rename to api/utils/compression/fixtures/Hello.bz2 diff --git a/pkg/common/compression/fixtures/Hello.gz b/api/utils/compression/fixtures/Hello.gz similarity index 100% rename from pkg/common/compression/fixtures/Hello.gz rename to api/utils/compression/fixtures/Hello.gz diff --git a/pkg/common/compression/fixtures/Hello.uncompressed b/api/utils/compression/fixtures/Hello.uncompressed similarity index 100% rename from pkg/common/compression/fixtures/Hello.uncompressed rename to api/utils/compression/fixtures/Hello.uncompressed diff --git a/pkg/common/compression/fixtures/Hello.xz b/api/utils/compression/fixtures/Hello.xz similarity index 100% rename from pkg/common/compression/fixtures/Hello.xz rename to api/utils/compression/fixtures/Hello.xz diff --git a/pkg/common/compression/fixtures/Hello.zst b/api/utils/compression/fixtures/Hello.zst similarity index 100% rename from pkg/common/compression/fixtures/Hello.zst rename to api/utils/compression/fixtures/Hello.zst diff --git a/pkg/common/compression/matchreader.go b/api/utils/compression/matchreader.go similarity index 100% rename from pkg/common/compression/matchreader.go rename to api/utils/compression/matchreader.go diff --git a/pkg/common/compression/types.go b/api/utils/compression/types.go similarity index 100% rename from pkg/common/compression/types.go rename to api/utils/compression/types.go diff --git a/pkg/common/compression/utils.go b/api/utils/compression/utils.go similarity index 100% rename from pkg/common/compression/utils.go rename to api/utils/compression/utils.go diff --git a/pkg/utils/ctf.go b/api/utils/ctf.go similarity index 100% rename from pkg/utils/ctf.go rename to api/utils/ctf.go diff --git a/api/utils/dirtree/context.go b/api/utils/dirtree/context.go new file mode 100644 index 000000000..204335c0e --- /dev/null +++ b/api/utils/dirtree/context.go @@ -0,0 +1,53 @@ +package dirtree + +import ( + "crypto/sha1" //nolint: gosec // required + "fmt" + "hash" + "io" + + "github.com/mandelsoft/logging" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/utils" +) + +type Context interface { + logging.Context + Hasher() hash.Hash + FileMode(vfs.FileMode) Mode + DirMode(vfs.FileMode) Mode + LinkMode(vfs.FileMode) Mode + WriteHeader(w io.Writer, typ string, size int64) error +} + +// DefaultContext provides a default directory tree hashing context. +// It is based on the Git tree hash mechanism. +func DefaultContext(ctx ...logging.Context) Context { + return &defaultContext{utils.OptionalDefaulted(LogContext, ctx...)} +} + +type defaultContext struct { + logging.Context +} + +func (d defaultContext) Hasher() hash.Hash { + return sha1.New() //nolint: gosec // required +} + +func (d defaultContext) FileMode(mode vfs.FileMode) Mode { + return FileMode(mode) | ModeBlob +} + +func (d defaultContext) DirMode(mode vfs.FileMode) Mode { + return ModeDir +} + +func (d defaultContext) LinkMode(mode vfs.FileMode) Mode { + return ModeSym +} + +func (d defaultContext) WriteHeader(w io.Writer, typ string, size int64) error { + _, err := w.Write([]byte(fmt.Sprintf("%s %d\000", typ, size))) + return err +} diff --git a/pkg/dirtree/dirtree.go b/api/utils/dirtree/dirtree.go similarity index 98% rename from pkg/dirtree/dirtree.go rename to api/utils/dirtree/dirtree.go index ff78c0fef..800546418 100644 --- a/pkg/dirtree/dirtree.go +++ b/api/utils/dirtree/dirtree.go @@ -12,7 +12,7 @@ import ( "github.com/mandelsoft/logging" "github.com/mandelsoft/vfs/pkg/vfs" - ocmlog "github.com/open-component-model/ocm/pkg/logging" + ocmlog "ocm.software/ocm/api/utils/logging" ) const ( diff --git a/pkg/dirtree/suite_test.go b/api/utils/dirtree/suite_test.go similarity index 100% rename from pkg/dirtree/suite_test.go rename to api/utils/dirtree/suite_test.go diff --git a/pkg/dirtree/tar.go b/api/utils/dirtree/tar.go similarity index 100% rename from pkg/dirtree/tar.go rename to api/utils/dirtree/tar.go diff --git a/pkg/dirtree/testdata/fs.tar b/api/utils/dirtree/testdata/fs.tar similarity index 100% rename from pkg/dirtree/testdata/fs.tar rename to api/utils/dirtree/testdata/fs.tar diff --git a/pkg/dirtree/testdata/fs/data b/api/utils/dirtree/testdata/fs/data similarity index 100% rename from pkg/dirtree/testdata/fs/data rename to api/utils/dirtree/testdata/fs/data diff --git a/pkg/dirtree/testdata/fs/dir/file b/api/utils/dirtree/testdata/fs/dir/file similarity index 100% rename from pkg/dirtree/testdata/fs/dir/file rename to api/utils/dirtree/testdata/fs/dir/file diff --git a/pkg/dirtree/testdata/fs/link b/api/utils/dirtree/testdata/fs/link similarity index 100% rename from pkg/dirtree/testdata/fs/link rename to api/utils/dirtree/testdata/fs/link diff --git a/pkg/dirtree/utils.go b/api/utils/dirtree/utils.go similarity index 100% rename from pkg/dirtree/utils.go rename to api/utils/dirtree/utils.go diff --git a/pkg/dirtree/vfs.go b/api/utils/dirtree/vfs.go similarity index 96% rename from pkg/dirtree/vfs.go rename to api/utils/dirtree/vfs.go index bfe0bb453..44a074c97 100644 --- a/pkg/dirtree/vfs.go +++ b/api/utils/dirtree/vfs.go @@ -8,7 +8,7 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" ) func NewVFSDirNode(ctx Context, p string, fss ...vfs.FileSystem) (*DirNode, error) { diff --git a/pkg/dirtree/vfs_test.go b/api/utils/dirtree/vfs_test.go similarity index 91% rename from pkg/dirtree/vfs_test.go rename to api/utils/dirtree/vfs_test.go index 8195f6729..3d202be5a 100644 --- a/pkg/dirtree/vfs_test.go +++ b/api/utils/dirtree/vfs_test.go @@ -10,8 +10,8 @@ import ( "github.com/mandelsoft/logging" - "github.com/open-component-model/ocm/pkg/dirtree" - "github.com/open-component-model/ocm/pkg/env" + "ocm.software/ocm/api/helper/env" + "ocm.software/ocm/api/utils/dirtree" ) var _ = Describe("file system", func() { diff --git a/api/utils/encrypt/encrypt.go b/api/utils/encrypt/encrypt.go new file mode 100644 index 000000000..5a4b7442e --- /dev/null +++ b/api/utils/encrypt/encrypt.go @@ -0,0 +1,189 @@ +package encrypt + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/pem" + "io" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/utils" +) + +const ( + PEM_ENCRYPTION_KEY = "ENCRYPTION KEY" + PEM_ENCRYPTED_DATA = "ENCRYPTED DATA" +) + +type algorithm byte + +const ( + AES_128 = algorithm(16) + AES_192 = algorithm(24) + AES_256 = algorithm(32) +) + +const ALGO = "algorithm" + +const ( + ALGO_AES_128 = "AES-128" + ALGO_AES_192 = "AES-192" + ALGO_AES_256 = "AES-256" +) + +var algos = map[algorithm]string{ + AES_128: ALGO_AES_128, + AES_192: ALGO_AES_192, + AES_256: ALGO_AES_256, +} + +var name2algo = map[string]algorithm{ + ALGO_AES_128: AES_128, + ALGO_AES_192: AES_192, + ALGO_AES_256: AES_256, +} + +func (a algorithm) String() string { + return algos[a] +} + +func (a algorithm) KeyLength() int { + return int(a) +} + +func (a algorithm) CheckKey(key []byte) error { + if a.KeyLength() != len(key) { + return aes.KeySizeError(len(key)) + } + return nil +} + +func AlgoForKey(key []byte) (algorithm, error) { + for a := range algos { + if len(key) == a.KeyLength() { + return a, nil + } + } + return 0, aes.KeySizeError(len(key)) +} + +func NewKey(t algorithm) ([]byte, error) { + key := make([]byte, t) + if _, err := io.ReadFull(rand.Reader, key); err != nil { + return nil, err + } + return key, nil +} + +func Encrypt(key []byte, data []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + nonce := make([]byte, gcm.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return nil, err + } + + return gcm.Seal(nonce, nonce, data, nil), nil +} + +func Decrypt(key []byte, cipherText []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + nonce := cipherText[:gcm.NonceSize()] + cipherText = cipherText[gcm.NonceSize():] + return gcm.Open(nil, nonce, cipherText, nil) +} + +func KeyFromPem(data []byte) ([]byte, error) { + for block, rest := pem.Decode(data); block != nil; block, rest = pem.Decode(rest) { + if block.Type == PEM_ENCRYPTION_KEY { + return block.Bytes, nil + } + } + return nil, errors.ErrNotFound("pem block", PEM_ENCRYPTION_KEY) +} + +func KeyToPem(data []byte) []byte { + return pem.EncodeToMemory(&pem.Block{ + Type: PEM_ENCRYPTION_KEY, + Headers: nil, + Bytes: data, + }) +} + +func KeyFromAny(k interface{}) ([]byte, error) { + if data, ok := k.([]byte); ok { + for block, rest := pem.Decode(data); block != nil; block, rest = pem.Decode(rest) { + if block.Type == PEM_ENCRYPTION_KEY { + return block.Bytes, nil + } + } + return data, nil + } + return nil, errors.ErrUnknown(PEM_ENCRYPTION_KEY) +} + +func GetEncyptedData(data []byte) ([]byte, algorithm) { + for block, rest := pem.Decode(data); block != nil; block, rest = pem.Decode(rest) { + if block.Type == PEM_ENCRYPTED_DATA { + algo := AES_256 + if block.Headers != nil { + if name := block.Headers[ALGO]; name != "" { + algo = name2algo[name] + } + } + return block.Bytes, algo + } + } + return nil, 0 +} + +func EncryptedToPem(algo algorithm, data []byte) []byte { + return pem.EncodeToMemory(&pem.Block{ + Type: PEM_ENCRYPTED_DATA, + Headers: map[string]string{ALGO: algo.String()}, + Bytes: data, + }) +} + +func OptionalDecrypt(key []byte, data []byte) ([]byte, error) { + cipherText, algo := GetEncyptedData(data) + if cipherText != nil { + if len(key) != algo.KeyLength() { + return nil, aes.KeySizeError(len(key)) + } + return Decrypt(key, cipherText) + } + return data, nil +} + +func WriteKey(key []byte, path string, fss ...vfs.FileSystem) error { + data := KeyToPem(key) + return vfs.WriteFile(utils.FileSystem(fss...), path, data, 0o100) +} + +func ReadKey(path string, fss ...vfs.FileSystem) ([]byte, error) { + data, err := vfs.ReadFile(utils.FileSystem(fss...), path) + if err != nil { + return nil, err + } + return KeyFromPem(data) +} diff --git a/pkg/errkind/kinds.go b/api/utils/errkind/kinds.go similarity index 100% rename from pkg/errkind/kinds.go rename to api/utils/errkind/kinds.go diff --git a/pkg/errkind/utils.go b/api/utils/errkind/utils.go similarity index 100% rename from pkg/errkind/utils.go rename to api/utils/errkind/utils.go diff --git a/pkg/filelock/lock.go b/api/utils/filelock/lock.go similarity index 100% rename from pkg/filelock/lock.go rename to api/utils/filelock/lock.go diff --git a/pkg/filelock/lock_test.go b/api/utils/filelock/lock_test.go similarity index 93% rename from pkg/filelock/lock_test.go rename to api/utils/filelock/lock_test.go index 5d9bdd5e1..903d0d110 100644 --- a/pkg/filelock/lock_test.go +++ b/api/utils/filelock/lock_test.go @@ -9,7 +9,7 @@ import ( "github.com/mandelsoft/filepath/pkg/filepath" - "github.com/open-component-model/ocm/pkg/filelock" + "ocm.software/ocm/api/utils/filelock" ) var _ = Describe("lock identity", func() { diff --git a/pkg/filelock/suite_test.go b/api/utils/filelock/suite_test.go similarity index 100% rename from pkg/filelock/suite_test.go rename to api/utils/filelock/suite_test.go diff --git a/pkg/filelock/testdata/lock b/api/utils/filelock/testdata/lock similarity index 100% rename from pkg/filelock/testdata/lock rename to api/utils/filelock/testdata/lock diff --git a/pkg/iotools/countingreader.go b/api/utils/iotools/countingreader.go similarity index 100% rename from pkg/iotools/countingreader.go rename to api/utils/iotools/countingreader.go diff --git a/pkg/iotools/deprecated.go b/api/utils/iotools/deprecated.go similarity index 100% rename from pkg/iotools/deprecated.go rename to api/utils/iotools/deprecated.go diff --git a/pkg/iotools/digestreader.go b/api/utils/iotools/digestreader.go similarity index 100% rename from pkg/iotools/digestreader.go rename to api/utils/iotools/digestreader.go diff --git a/pkg/iotools/digests.go b/api/utils/iotools/digests.go similarity index 100% rename from pkg/iotools/digests.go rename to api/utils/iotools/digests.go diff --git a/pkg/iotools/digests_test.go b/api/utils/iotools/digests_test.go similarity index 97% rename from pkg/iotools/digests_test.go rename to api/utils/iotools/digests_test.go index 980472223..ee9ede51d 100644 --- a/pkg/iotools/digests_test.go +++ b/api/utils/iotools/digests_test.go @@ -8,7 +8,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/iotools" + "ocm.software/ocm/api/utils/iotools" ) var _ = Describe("digests.go tests", func() { diff --git a/pkg/iotools/digestwriter.go b/api/utils/iotools/digestwriter.go similarity index 100% rename from pkg/iotools/digestwriter.go rename to api/utils/iotools/digestwriter.go diff --git a/pkg/iotools/files.go b/api/utils/iotools/files.go similarity index 87% rename from pkg/iotools/files.go rename to api/utils/iotools/files.go index 546fae5fd..4e7b05fda 100644 --- a/pkg/iotools/files.go +++ b/api/utils/iotools/files.go @@ -3,7 +3,7 @@ package iotools import ( "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" ) func ListFiles(path string, fss ...vfs.FileSystem) ([]string, error) { diff --git a/pkg/iotools/hashReaderWriter.go b/api/utils/iotools/hashReaderWriter.go similarity index 100% rename from pkg/iotools/hashReaderWriter.go rename to api/utils/iotools/hashReaderWriter.go diff --git a/pkg/iotools/hashReaderWriter_test.go b/api/utils/iotools/hashReaderWriter_test.go similarity index 98% rename from pkg/iotools/hashReaderWriter_test.go rename to api/utils/iotools/hashReaderWriter_test.go index 457fa1534..deaf59f31 100644 --- a/pkg/iotools/hashReaderWriter_test.go +++ b/api/utils/iotools/hashReaderWriter_test.go @@ -9,7 +9,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/iotools" + "ocm.software/ocm/api/utils/iotools" ) var _ = Describe("Hash Reader Writer tests", func() { diff --git a/pkg/iotools/readerwriter.go b/api/utils/iotools/readerwriter.go similarity index 100% rename from pkg/iotools/readerwriter.go rename to api/utils/iotools/readerwriter.go diff --git a/pkg/iotools/suite_test.go b/api/utils/iotools/suite_test.go similarity index 100% rename from pkg/iotools/suite_test.go rename to api/utils/iotools/suite_test.go diff --git a/pkg/iotools/utils.go b/api/utils/iotools/utils.go similarity index 100% rename from pkg/iotools/utils.go rename to api/utils/iotools/utils.go diff --git a/pkg/utils/key.go b/api/utils/key.go similarity index 100% rename from pkg/utils/key.go rename to api/utils/key.go diff --git a/pkg/utils/keyinfo.go b/api/utils/keyinfo.go similarity index 100% rename from pkg/utils/keyinfo.go rename to api/utils/keyinfo.go diff --git a/pkg/listformat/listhelp.go b/api/utils/listformat/listhelp.go similarity index 98% rename from pkg/listformat/listhelp.go rename to api/utils/listformat/listhelp.go index 99ce2d3ad..5a76e86a2 100644 --- a/pkg/listformat/listhelp.go +++ b/api/utils/listformat/listhelp.go @@ -6,7 +6,7 @@ import ( "sort" "strings" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" ) type StringElementDescriptionList []string diff --git a/pkg/utils/locator.go b/api/utils/locator.go similarity index 100% rename from pkg/utils/locator.go rename to api/utils/locator.go diff --git a/pkg/utils/log.go b/api/utils/log.go similarity index 100% rename from pkg/utils/log.go rename to api/utils/log.go diff --git a/api/utils/logging/config_test.go b/api/utils/logging/config_test.go new file mode 100644 index 000000000..df9c1097e --- /dev/null +++ b/api/utils/logging/config_test.go @@ -0,0 +1,55 @@ +package logging_test + +import ( + "bytes" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/utils/logging/testhelper" + + "github.com/mandelsoft/logging" + logcfg "github.com/mandelsoft/logging/config" + "github.com/tonglil/buflogr" + + local "ocm.software/ocm/api/utils/logging" +) + +//////////////////////////////////////////////////////////////////////////////// + +var _ = Describe("logging configuration", func() { + var buf bytes.Buffer + var ctx logging.Context + + BeforeEach(func() { + local.SetContext(logging.NewDefault()) + buf.Reset() + def := buflogr.NewWithBuffer(&buf) + ctx = local.Context() + ctx.SetBaseLogger(def) + }) + + It("just logs with defaults", func() { + LogTest(ctx) + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +V[3] info realm ocm +V[2] warn realm ocm +ERROR error realm ocm +`)) + }) + It("just logs with config", func() { + r := logcfg.ConditionalRule("debug") + cfg := &logcfg.Config{ + Rules: []logcfg.Rule{r}, + } + + Expect(local.Configure(cfg)).To(Succeed()) + LogTest(ctx) + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +V[4] debug realm ocm +V[3] info realm ocm +V[2] warn realm ocm +ERROR error realm ocm +`)) + }) +}) diff --git a/pkg/logging/interface.go b/api/utils/logging/interface.go similarity index 100% rename from pkg/logging/interface.go rename to api/utils/logging/interface.go diff --git a/pkg/logging/logging.go b/api/utils/logging/logging.go similarity index 100% rename from pkg/logging/logging.go rename to api/utils/logging/logging.go diff --git a/pkg/logging/stdkeys.go b/api/utils/logging/stdkeys.go similarity index 100% rename from pkg/logging/stdkeys.go rename to api/utils/logging/stdkeys.go diff --git a/pkg/logging/suite_test.go b/api/utils/logging/suite_test.go similarity index 100% rename from pkg/logging/suite_test.go rename to api/utils/logging/suite_test.go diff --git a/pkg/logging/testhelper/testhelper.go b/api/utils/logging/testhelper/testhelper.go similarity index 100% rename from pkg/logging/testhelper/testhelper.go rename to api/utils/logging/testhelper/testhelper.go diff --git a/pkg/logging/utils.go b/api/utils/logging/utils.go similarity index 100% rename from pkg/logging/utils.go rename to api/utils/logging/utils.go diff --git a/api/utils/mime/types.go b/api/utils/mime/types.go new file mode 100644 index 000000000..9c4b6f2d1 --- /dev/null +++ b/api/utils/mime/types.go @@ -0,0 +1,50 @@ +package mime + +import ( + "mime" + + "ocm.software/ocm/api/utils/logging" +) + +const ( + MIME_TEXT = "text/plain" + MIME_OCTET = "application/octet-stream" + + MIME_JSON = "application/x-json" + MIME_JSON_ALT = "text/json" // no utf8 + MIME_JSON_OFFICIAL = "application/json" + MIME_XML = "application/xml" + MIME_YAML = "application/x-yaml" + MIME_YAML_ALT = "text/yaml" // no utf8 + MIME_YAML_OFFICIAL = "application/yaml" + + MIME_GZIP = "application/gzip" + MIME_TAR = "application/x-tar" + MIME_TGZ = "application/x-tgz" + MIME_TGZ_ALT = MIME_TAR + "+gzip" + + MIME_JAR = "application/x-jar" +) + +func init() { + ocmTypes := map[string]string{ + // added entries + ".txt": MIME_TEXT, + ".yaml": MIME_YAML_OFFICIAL, + ".gzip": MIME_GZIP, + ".tar": MIME_TAR, + ".tgz": MIME_TGZ, + ".tar.gz": MIME_TGZ, + ".pom": MIME_XML, + ".zip": MIME_GZIP, + ".jar": MIME_JAR, + ".module": MIME_JSON, // gradle module metadata + } + + for k, v := range ocmTypes { + err := mime.AddExtensionType(k, v) + if err != nil { + logging.DynamicLogger(logging.DefineSubRealm("mimeutils")).Error("failed to add extension type", "extension", k, "type", v, "error", err) + } + } +} diff --git a/pkg/mime/util.go b/api/utils/mime/util.go similarity index 100% rename from pkg/mime/util.go rename to api/utils/mime/util.go diff --git a/pkg/common/const.go b/api/utils/misc/const.go similarity index 100% rename from pkg/common/const.go rename to api/utils/misc/const.go diff --git a/pkg/common/history.go b/api/utils/misc/history.go similarity index 100% rename from pkg/common/history.go rename to api/utils/misc/history.go diff --git a/pkg/common/history_test.go b/api/utils/misc/history_test.go similarity index 98% rename from pkg/common/history_test.go rename to api/utils/misc/history_test.go index 03852ff16..ad0011f19 100644 --- a/pkg/common/history_test.go +++ b/api/utils/misc/history_test.go @@ -4,7 +4,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/common" + common "ocm.software/ocm/api/utils/misc" ) type Elem struct { diff --git a/pkg/common/printer.go b/api/utils/misc/printer.go similarity index 98% rename from pkg/common/printer.go rename to api/utils/misc/printer.go index 946ce2d8b..20526740b 100644 --- a/pkg/common/printer.go +++ b/api/utils/misc/printer.go @@ -9,7 +9,7 @@ import ( "github.com/mandelsoft/logging" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" ) type Flusher interface { diff --git a/pkg/common/printer_test.go b/api/utils/misc/printer_test.go similarity index 97% rename from pkg/common/printer_test.go rename to api/utils/misc/printer_test.go index 6b0aa46fc..d78427377 100644 --- a/pkg/common/printer_test.go +++ b/api/utils/misc/printer_test.go @@ -9,7 +9,7 @@ import ( "github.com/mandelsoft/goutils/testutils" "github.com/mandelsoft/logging" - ocmlog "github.com/open-component-model/ocm/pkg/logging" + ocmlog "ocm.software/ocm/api/utils/logging" ) var _ = Describe("Printer", func() { diff --git a/pkg/common/properties.go b/api/utils/misc/properties.go similarity index 100% rename from pkg/common/properties.go rename to api/utils/misc/properties.go diff --git a/pkg/common/suite_test.go b/api/utils/misc/suite_test.go similarity index 100% rename from pkg/common/suite_test.go rename to api/utils/misc/suite_test.go diff --git a/api/utils/misc/types.go b/api/utils/misc/types.go new file mode 100644 index 000000000..e8a8a3fe6 --- /dev/null +++ b/api/utils/misc/types.go @@ -0,0 +1,86 @@ +package common + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/utils/semverutils" +) + +// VersionedElement describes an element that has a name and a version. +type VersionedElement interface { + // GetName gets the name of the element + GetName() string + // GetVersion gets the version of the element + GetVersion() string +} + +type NameVersion struct { + name string + version string +} + +var ( + _ json.Marshaler = (*NameVersion)(nil) + _ VersionedElement = (*NameVersion)(nil) +) + +func NewNameVersion(name, version string) NameVersion { + return NameVersion{name, version} +} + +func VersionedElementKey(v VersionedElement) NameVersion { + if k, ok := v.(NameVersion); ok { + return k + } + return NameVersion{v.GetName(), v.GetVersion()} +} + +func (n NameVersion) GetName() string { + return n.name +} + +func (n NameVersion) GetVersion() string { + return n.version +} + +func (n NameVersion) MarshalJSON() ([]byte, error) { + return json.Marshal(fmt.Sprintf("%s:%s", n.GetName(), n.GetVersion())) +} + +func (n NameVersion) Compare(o NameVersion) int { + c := strings.Compare(n.name, o.name) + if c == 0 { + return semverutils.Compare(n.version, o.version) + } + return c +} + +func (n NameVersion) String() string { + if n.version == "" { + return n.name + } + if n.name == "" { + return n.version + } + return n.name + ":" + n.version +} + +func ParseNameVersion(s string) (NameVersion, error) { + a := strings.Split(s, ":") + if len(a) != 2 { + return NameVersion{}, errors.ErrInvalid("name:version", s) + } + return NewNameVersion(strings.TrimSpace(a[0]), strings.TrimSpace(a[1])), nil +} + +func CompareNameVersion(a, b NameVersion) int { + d := strings.Compare(a.name, b.name) + if d == 0 { + d = strings.Compare(a.version, b.version) + } + return d +} diff --git a/pkg/common/utils.go b/api/utils/misc/utils.go similarity index 100% rename from pkg/common/utils.go rename to api/utils/misc/utils.go diff --git a/api/utils/misc/walk.go b/api/utils/misc/walk.go new file mode 100644 index 000000000..6eeba3209 --- /dev/null +++ b/api/utils/misc/walk.go @@ -0,0 +1,52 @@ +package common + +import ( + "github.com/mandelsoft/logging" + + "ocm.software/ocm/api/utils" + ocmlog "ocm.software/ocm/api/utils/logging" +) + +type NameVersionInfo[T any] map[NameVersion]T + +func (s NameVersionInfo[T]) Add(nv NameVersion, data ...T) bool { + if _, ok := s[nv]; !ok { + s[nv] = utils.Optional(data...) + return true + } + return false +} + +func (s NameVersionInfo[T]) Contains(nv NameVersion) bool { + _, ok := s[nv] + return ok +} + +type WalkingState[T any, C any] struct { + LogCtx logging.Context + Logger logging.Logger + Context C + Closure NameVersionInfo[T] + History History +} + +func NewWalkingState[T any, C any](ctx C, lctx ...logging.Context) WalkingState[T, C] { + logctx := utils.OptionalDefaulted[logging.Context](ocmlog.Context(), lctx...) + return WalkingState[T, C]{Context: ctx, Closure: NameVersionInfo[T]{}, LogCtx: logctx, Logger: logctx.Logger()} +} + +func (s *WalkingState[T, C]) Add(kind string, nv NameVersion) (bool, error) { + if err := s.History.Add(kind, nv); err != nil { + return false, err + } + return s.Closure.Add(nv), nil +} + +func (s *WalkingState[T, C]) Contains(nv NameVersion) bool { + _, ok := s.Closure[nv] + return ok +} + +func (s *WalkingState[T, C]) Get(nv NameVersion) T { + return s.Closure[nv] +} diff --git a/pkg/out/context.go b/api/utils/out/context.go similarity index 100% rename from pkg/out/context.go rename to api/utils/out/context.go diff --git a/pkg/utils/panics/panics.go b/api/utils/panics/panics.go similarity index 97% rename from pkg/utils/panics/panics.go rename to api/utils/panics/panics.go index 4555136ee..f193c82f5 100644 --- a/pkg/utils/panics/panics.go +++ b/api/utils/panics/panics.go @@ -7,7 +7,7 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/logging" + "ocm.software/ocm/api/utils/logging" ) var ReallyCrash = true diff --git a/pkg/utils/panics/panics_test.go b/api/utils/panics/panics_test.go similarity index 95% rename from pkg/utils/panics/panics_test.go rename to api/utils/panics/panics_test.go index d68dbb549..7844a39b1 100644 --- a/pkg/utils/panics/panics_test.go +++ b/api/utils/panics/panics_test.go @@ -6,7 +6,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/utils/panics" + "ocm.software/ocm/api/utils/panics" ) func caller(topanic interface{}, outerr error, handlers ...panics.PanicHandler) (err error) { diff --git a/pkg/utils/panics/suite_test.go b/api/utils/panics/suite_test.go similarity index 100% rename from pkg/utils/panics/suite_test.go rename to api/utils/panics/suite_test.go diff --git a/pkg/utils/path.go b/api/utils/path.go similarity index 100% rename from pkg/utils/path.go rename to api/utils/path.go diff --git a/pkg/refmgmt/doc.go b/api/utils/refmgmt/doc.go similarity index 100% rename from pkg/refmgmt/doc.go rename to api/utils/refmgmt/doc.go diff --git a/pkg/refmgmt/finalized/doc.go b/api/utils/refmgmt/finalized/doc.go similarity index 100% rename from pkg/refmgmt/finalized/doc.go rename to api/utils/refmgmt/finalized/doc.go diff --git a/pkg/refmgmt/finalized/finalized_test.go b/api/utils/refmgmt/finalized/finalized_test.go similarity index 93% rename from pkg/refmgmt/finalized/finalized_test.go rename to api/utils/refmgmt/finalized/finalized_test.go index bd8e3c418..5c3db515f 100644 --- a/pkg/refmgmt/finalized/finalized_test.go +++ b/api/utils/refmgmt/finalized/finalized_test.go @@ -7,9 +7,9 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/refmgmt" - "github.com/open-component-model/ocm/pkg/refmgmt/finalized" - "github.com/open-component-model/ocm/pkg/runtimefinalizer" + "ocm.software/ocm/api/utils/refmgmt" + "ocm.software/ocm/api/utils/refmgmt/finalized" + "ocm.software/ocm/api/utils/runtimefinalizer" ) type Interface interface { diff --git a/pkg/refmgmt/finalized/finalizedref.go b/api/utils/refmgmt/finalized/finalizedref.go similarity index 89% rename from pkg/refmgmt/finalized/finalizedref.go rename to api/utils/refmgmt/finalized/finalizedref.go index c9d32b8a4..dd4f85325 100644 --- a/pkg/refmgmt/finalized/finalizedref.go +++ b/api/utils/refmgmt/finalized/finalizedref.go @@ -3,8 +3,8 @@ package finalized import ( "runtime" - "github.com/open-component-model/ocm/pkg/refmgmt" - "github.com/open-component-model/ocm/pkg/runtimefinalizer" + "ocm.software/ocm/api/utils/refmgmt" + "ocm.software/ocm/api/utils/runtimefinalizer" ) type FinalizedRef struct { diff --git a/pkg/refmgmt/finalized/suite_test.go b/api/utils/refmgmt/finalized/suite_test.go similarity index 100% rename from pkg/refmgmt/finalized/suite_test.go rename to api/utils/refmgmt/finalized/suite_test.go diff --git a/pkg/refmgmt/refcloser.go b/api/utils/refmgmt/refcloser.go similarity index 100% rename from pkg/refmgmt/refcloser.go rename to api/utils/refmgmt/refcloser.go diff --git a/api/utils/refmgmt/refmgmt.go b/api/utils/refmgmt/refmgmt.go new file mode 100644 index 000000000..2fa99f505 --- /dev/null +++ b/api/utils/refmgmt/refmgmt.go @@ -0,0 +1,160 @@ +package refmgmt + +import ( + "fmt" + "sync" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/utils/logging" +) + +var ALLOC_REALM = logging.DefineSubRealm("reference counting", "refcnt") + +var AllocLog = logging.DynamicLogger(ALLOC_REALM) + +type Allocatable interface { + Ref() error + Unref() error +} + +type CleanupHandler interface { + Cleanup() +} + +type CleanupHandlerFunc func() + +func (f CleanupHandlerFunc) Cleanup() { + f() +} + +type ExtendedAllocatable interface { + BeforeCleanup(f CleanupHandler) + Ref() error + Unref() error +} + +type RefMgmt interface { + UnrefLast() error + ExtendedAllocatable + IsClosed() bool + RefCount() int + + WithName(name string) RefMgmt +} + +type refMgmt struct { + lock sync.Mutex + refcount int + closed bool + before []CleanupHandler + cleanup func() error + name string +} + +func NewAllocatable(cleanup func() error, unused ...bool) RefMgmt { + n := 1 + for _, b := range unused { + if b { + n = 0 + } + } + return &refMgmt{refcount: n, cleanup: cleanup, name: "object"} +} + +func (c *refMgmt) WithName(name string) RefMgmt { + c.name = name + return c +} + +func (c *refMgmt) IsClosed() bool { + c.lock.Lock() + defer c.lock.Unlock() + return c.closed +} + +func (c *refMgmt) Ref() error { + c.lock.Lock() + defer c.lock.Unlock() + if c.closed { + return ErrClosed + } + c.refcount++ + AllocLog.Trace("ref", "name", c.name, "refcnt", c.refcount) + return nil +} + +func (c *refMgmt) Unref() error { + c.lock.Lock() + defer c.lock.Unlock() + if c.closed { + return ErrClosed + } + + var err error + + c.refcount-- + AllocLog.Trace("unref", "name", c.name, "refcnt", c.refcount) + if c.refcount <= 0 { + for _, f := range c.before { + f.Cleanup() + } + if c.cleanup != nil { + err = c.cleanup() + } + c.closed = true + } + + if err != nil { + return fmt.Errorf("unable to unref %s: %w", c.name, err) + } + + return nil +} + +func (c *refMgmt) RefCount() int { + c.lock.Lock() + defer c.lock.Unlock() + return c.refcount +} + +func (c *refMgmt) BeforeCleanup(f CleanupHandler) { + c.lock.Lock() + defer c.lock.Unlock() + c.before = append(c.before, f) +} + +func (c *refMgmt) UnrefLast() error { + c.lock.Lock() + defer c.lock.Unlock() + if c.closed { + return ErrClosed + } + + if c.refcount > 1 { + AllocLog.Trace("unref last failed", "name", c.name, "pending", c.refcount) + return errors.ErrStillInUseWrap(errors.Newf("%d reference(s) pending", c.refcount), c.name) + } + + var err error + + c.refcount-- + AllocLog.Trace("unref last", "name", c.name, "refcnt", c.refcount) + if c.refcount <= 0 { + for _, f := range c.before { + f.Cleanup() + } + if c.cleanup != nil { + err = c.cleanup() + } + + c.closed = true + } + + if err != nil { + AllocLog.Trace("cleanup last failed", "name", c.name, "error", err.Error()) + return errors.Wrapf(err, "unable to cleanup %s while unref last", c.name) + } + + return nil +} diff --git a/pkg/refmgmt/resource/doc.go b/api/utils/refmgmt/resource/doc.go similarity index 100% rename from pkg/refmgmt/resource/doc.go rename to api/utils/refmgmt/resource/doc.go diff --git a/api/utils/refmgmt/resource/resource.go b/api/utils/refmgmt/resource/resource.go new file mode 100644 index 000000000..069353f87 --- /dev/null +++ b/api/utils/refmgmt/resource/resource.go @@ -0,0 +1,275 @@ +package resource + +import ( + "io" + + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/refmgmt" +) + +type CloserView interface { + Close() error + IsClosed() bool + Execute(func() error) error + Allocatable() refmgmt.ExtendedAllocatable + refmgmt.LazyMode + refmgmt.RefCountProvider +} + +var _ CloserView = refmgmt.CloserView(nil) + +var ErrClosed = accessio.ErrClosed + +// resourceViewInterface is a helper type used to implement parameter type +// recursion for ResourceView[T ResourceView[T]], which is not allowed in Go. +type resourceViewInterface[T any] interface { + io.Closer + IsClosed() bool + Dup[T] +} + +// ResourceView is the view related part of a resource interface T. +// T must incorporate ResourceView[T], which cannot directly be expressed +// in go, but with the helper interface defining the API. +type ResourceView[T resourceViewInterface[T]] interface { + resourceViewInterface[T] +} + +// ResourceViewInt can be used to execute an operation on a non-closed +// view. +// Execute call a synchronized function on a non-closed view. +type ResourceViewInt[T resourceViewInterface[T]] interface { + refmgmt.LazyMode + refmgmt.RefCountProvider + resourceViewInterface[T] + + Execute(func() error) error + Allocatable() refmgmt.ExtendedAllocatable +} + +type Dup[T any] interface { + Dup() (T, error) +} + +//////////////////////////////////////////////////////////////////////////////// + +// ViewManager is the interface of the reference manager, which +// can be used to gain new views to a managed resource. +type ViewManager[T any] interface { + RefCount() int + Allocatable() refmgmt.ExtendedAllocatable + View(main ...bool) (T, error) + IsClosed() bool +} + +// ResourceViewCreator is a function which must be provided by the resource provider +// to map an implementation to the resource interface T. +// It must use NewView to create the view related part of a resource. +type ResourceViewCreator[T any, I io.Closer] func(I, CloserView, ViewManager[T]) T + +type viewManager[T any, I ResourceImplementation[T]] struct { + refs refmgmt.ReferencableCloser + creator ResourceViewCreator[T, I] + impl I +} + +// ResourceImplementation is the minimal interface for an implementation +// a resource with managed views. +type ResourceImplementation[T any] interface { + io.Closer + SetViewManager(m ViewManager[T]) + ViewManager[T] +} + +// NewResource creates a resource based on an implementation and a ResourceViewCreator. +// function. +func NewResource[T any, I ResourceImplementation[T]](impl I, c ResourceViewCreator[T, I], name string, main ...bool) T { + i := &viewManager[T, I]{ + refs: refmgmt.NewRefCloser(impl, true).WithName(name), + creator: c, + impl: impl, + } + impl.SetViewManager(i) + t, _ := i.View(main...) + return t +} + +func (i *viewManager[T, I]) RefCount() int { + return i.refs.RefCount() +} + +func (i *viewManager[T, I]) Allocatable() refmgmt.ExtendedAllocatable { + return i.refs +} + +func (i *viewManager[T, I]) View(main ...bool) (T, error) { + var _nil T + + v, err := i.refs.View(main...) + if err != nil { + return _nil, err + } + return i.creator(i.impl, v, i), nil +} + +func (i *viewManager[T, I]) IsClosed() bool { + return i.refs.IsClosed() +} + +//////////////////////////////////////////////////////////////////////////////// + +// noneRefCloser is used to compose a non-referencing +// view, which does not forward the close operation +// to the view manager. Its state directly reflects +// the state of the view manager. +type noneRefCloser[T io.Closer] struct { + mgr ViewManager[T] +} + +var _ CloserView = (*noneRefCloser[io.Closer])(nil) + +func (n *noneRefCloser[T]) Close() error { + if n.mgr.IsClosed() { + return ErrClosed + } + return nil +} + +func (n *noneRefCloser[T]) IsClosed() bool { + return n.mgr.IsClosed() +} + +func (n *noneRefCloser[T]) Execute(f func() error) error { + v, err := n.mgr.View() + if err != nil { + return err + } + defer v.Close() + return f() +} + +func (n *noneRefCloser[T]) Lazy() { +} + +func (n *noneRefCloser[T]) RefCount() int { + return n.mgr.RefCount() +} + +func (n *noneRefCloser[T]) Allocatable() refmgmt.ExtendedAllocatable { + return n.mgr.Allocatable() +} + +//////////////////////////////////////////////////////////////////////////////// + +type resourceView[T any] struct { + view CloserView + mgr ViewManager[T] +} + +// NewView is to be called by a resource view creator to map +// the given resource implementation to complete resource interface. +// It should create an object with two local embedded fields: +// - the returned ResourceView and the +// - given resource implementation. +func NewView[T resourceViewInterface[T]](v CloserView, d ViewManager[T]) ResourceViewInt[T] { + return &resourceView[T]{v, d} +} + +// NewNonRefView provides a reference-less view directly for the reference manager. +// It is valid as long as the reference manager is not closed with the last regular +// reference. +func NewNonRefView[T resourceViewInterface[T]](d ViewManager[T]) ResourceViewInt[T] { + return &resourceView[T]{&noneRefCloser[T]{d}, d} +} + +func NoneRefCloserView[T io.Closer](d ViewManager[T]) CloserView { + return &noneRefCloser[T]{d} +} + +func (v *resourceView[T]) IsClosed() bool { + return v.view.IsClosed() +} + +func (v *resourceView[T]) Close() error { + return v.view.Close() +} + +func (v *resourceView[T]) Execute(f func() error) error { + return v.view.Execute(f) +} + +func (v *resourceView[T]) Allocatable() refmgmt.ExtendedAllocatable { + return v.view.Allocatable() +} + +func (v *resourceView[T]) Dup() (t T, err error) { + err = v.Execute(func() error { + t, err = v.mgr.View() + return err + }) + return t, err +} + +func (v *resourceView[T]) Lazy() { + v.view.Lazy() +} + +func (v *resourceView[T]) RefCount() int { + return v.view.RefCount() +} + +//////////////////////////////////////////////////////////////////////////////// + +type ResourceImplBase[T any] struct { + refs ViewManager[T] + closer []io.Closer +} + +// NewResourceImplBase creates an implementation base for a resource T +// referencing another resource M. +func NewResourceImplBase[T any, M io.Closer](m ViewManager[M], closer ...io.Closer) (*ResourceImplBase[T], error) { + if m != nil { + ref, err := m.View() + if err != nil { + return nil, err + } + closer = append(closer, ref) + } + return &ResourceImplBase[T]{ + closer: closer, + }, nil +} + +func NewSimpleResourceImplBase[T any](closer ...io.Closer) *ResourceImplBase[T] { + return &ResourceImplBase[T]{ + closer: closer, + } +} + +func (b *ResourceImplBase[T]) SetViewManager(m ViewManager[T]) { + b.refs = m +} + +func (b *ResourceImplBase[T]) RefCount() int { + return b.refs.RefCount() +} + +func (b *ResourceImplBase[T]) Allocatable() refmgmt.ExtendedAllocatable { + return b.refs.Allocatable() +} + +func (b *ResourceImplBase[T]) ViewManager() ViewManager[T] { + return b.refs +} + +func (b *ResourceImplBase[T]) View(main ...bool) (T, error) { + return b.refs.View(main...) +} + +func (b *ResourceImplBase[T]) IsClosed() bool { + return b.refs.IsClosed() +} + +func (b *ResourceImplBase[T]) Close() error { + return accessio.Close(b.closer...) +} diff --git a/api/utils/refmgmt/resource/resource_test.go b/api/utils/refmgmt/resource/resource_test.go new file mode 100644 index 000000000..be305bd21 --- /dev/null +++ b/api/utils/refmgmt/resource/resource_test.go @@ -0,0 +1,131 @@ +package resource_test + +import ( + "fmt" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/utils/refmgmt/resource" +) + +// Resource is the intended resource interface. +// It must incorporate the resource.ResourceView interface +// providing the view related part of the interface. +type Resource interface { + resource.ResourceView[Resource] + + Name() string + Operation(err error) error +} + +type ( + // ResourceImpl implements io.Closer to finally release allocated resources + // and the additional non-view-related part of the resource interface. + ResourceImpl struct { + resource.ResourceImplBase[Resource] + name string + } + _resourceImpl = *ResourceImpl +) + +var _ resource.ResourceImplementation[Resource] = (*ResourceImpl)(nil) + +func (r *ResourceImpl) Name() string { + return r.name +} + +func (r *ResourceImpl) Operation(err error) error { + return err +} + +// Close is called for the last closed view and +// may handle the release of allocated sub resources. +func (r *ResourceImpl) Close() error { + if r.name == "" { + return fmt.Errorf("oops") + } + r.name = "" + return nil +} + +type _resourceView = resource.ResourceViewInt[Resource] + +// resourceView implementation the mapping of a ResourceImpl +// to a fully-fledged Resource implementation including +// the view-related part. +type resourceView struct { + _resourceView + _resourceImpl +} + +var _ Resource = (*resourceView)(nil) + +// Close must be implemented to resolve the two provided Close +// methods to the one of the view-related part. +func (r *resourceView) Close() error { + return r._resourceView.Close() +} + +func (r *resourceView) Operation(err error) error { + return r.Execute(func() error { return r._resourceImpl.Operation(err) }) +} + +func resourceViewCreator(impl *ResourceImpl, v resource.CloserView, d resource.ViewManager[Resource]) Resource { + return &resourceView{ + _resourceView: resource.NewView(v, d), + _resourceImpl: impl, + } +} + +// New create a new Resource by creating a ResourceImpl +// and adding the reference management by calling resource.NewResource, +// which internally will call the resourceViewCreator function to create +// the first view. +func New(name string) Resource { + i := &ResourceImpl{name: name} + return resource.NewResource(i, resourceViewCreator, name) +} + +var _ = Describe("ref test", func() { + It("handles main ref", func() { + r := New("alice") + + Expect(r.IsClosed()).To(BeFalse()) + + MustBeSuccessful(r.Close()) + Expect(r.IsClosed()).To(BeTrue()) + Expect(r.Close()).To(Equal(resource.ErrClosed)) + Expect(r.Name()).To(Equal("")) + }) + + It("handle last closed view", func() { + r := New("alice") + Expect(r.IsClosed()).To(BeFalse()) + v := Must(r.Dup()) + Expect(v.IsClosed()).To(BeFalse()) + + MustBeSuccessful(r.Close()) + Expect(r.IsClosed()).To(BeTrue()) + Expect(v.IsClosed()).To(BeFalse()) + + Expect(r.Close()).To(Equal(resource.ErrClosed)) + Expect(r.Name()).To(Equal("alice")) + + MustBeSuccessful(v.Close()) + Expect(v.IsClosed()).To(BeTrue()) + Expect(v.Close()).To(Equal(resource.ErrClosed)) + Expect(v.Name()).To(Equal("")) + }) + + It("executes operation", func() { + r := New("alice") + Expect(r.IsClosed()).To(BeFalse()) + + Expect(r.Operation(nil)).To(Succeed()) + Expect(r.Operation(fmt.Errorf("fail"))).To(MatchError("fail")) + MustBeSuccessful(r.Close()) + Expect(r.Operation(nil)).To(Equal(resource.ErrClosed)) + }) +}) diff --git a/pkg/refmgmt/resource/suite_test.go b/api/utils/refmgmt/resource/suite_test.go similarity index 100% rename from pkg/refmgmt/resource/suite_test.go rename to api/utils/refmgmt/resource/suite_test.go diff --git a/pkg/refmgmt/suite_test.go b/api/utils/refmgmt/suite_test.go similarity index 100% rename from pkg/refmgmt/suite_test.go rename to api/utils/refmgmt/suite_test.go diff --git a/pkg/refmgmt/view.go b/api/utils/refmgmt/view.go similarity index 100% rename from pkg/refmgmt/view.go rename to api/utils/refmgmt/view.go diff --git a/pkg/refmgmt/view_test.go b/api/utils/refmgmt/view_test.go similarity index 97% rename from pkg/refmgmt/view_test.go rename to api/utils/refmgmt/view_test.go index 15ff47e3a..2f7e0f1de 100644 --- a/pkg/refmgmt/view_test.go +++ b/api/utils/refmgmt/view_test.go @@ -9,7 +9,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - refmgmt2 "github.com/open-component-model/ocm/pkg/refmgmt" + refmgmt2 "ocm.software/ocm/api/utils/refmgmt" ) // Objectbase is the base interface for the diff --git a/api/utils/registrations/info.go b/api/utils/registrations/info.go new file mode 100644 index 000000000..617b9542b --- /dev/null +++ b/api/utils/registrations/info.go @@ -0,0 +1,58 @@ +package registrations + +import ( + "strings" + + "github.com/mandelsoft/goutils/general" + + "ocm.software/ocm/api/utils/listformat" +) + +type HandlerInfos []HandlerInfo + +var _ listformat.ListElements = HandlerInfos(nil) + +func (h HandlerInfos) Size() int { + return len(h) +} + +func (h HandlerInfos) Key(i int) string { + return h[i].Name +} + +func (h HandlerInfos) Description(i int) string { + var desc string + + if h[i].Node { + desc = "[" + general.Conditional(h[i].ShortDesc == "", "intermediate", strings.Trim(h[i].ShortDesc, "\n")) + "]" + } else { + desc = h[i].ShortDesc + } + return desc + general.Conditional(h[i].Description == "", "", "\n\n"+strings.Trim(h[i].Description, "\n")) +} + +type HandlerInfo struct { + Name string + ShortDesc string + Description string + Node bool +} + +func NewLeafHandlerInfo(short, desc string) HandlerInfos { + return HandlerInfos{ + { + ShortDesc: short, + Description: desc, + }, + } +} + +func NewNodeHandlerInfo(short, desc string) HandlerInfos { + return HandlerInfos{ + { + ShortDesc: short, + Description: desc, + Node: true, + }, + } +} diff --git a/pkg/registrations/registrations.go b/api/utils/registrations/registrations.go similarity index 99% rename from pkg/registrations/registrations.go rename to api/utils/registrations/registrations.go index e920f6c07..7eecebe79 100644 --- a/pkg/registrations/registrations.go +++ b/api/utils/registrations/registrations.go @@ -15,7 +15,7 @@ import ( "github.com/mandelsoft/goutils/set" "golang.org/x/exp/slices" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" ) type HandlerConfig interface{} diff --git a/pkg/registrations/registrations_test.go b/api/utils/registrations/registrations_test.go similarity index 98% rename from pkg/registrations/registrations_test.go rename to api/utils/registrations/registrations_test.go index 983aff661..31aff80cf 100644 --- a/pkg/registrations/registrations_test.go +++ b/api/utils/registrations/registrations_test.go @@ -5,8 +5,8 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/registrations" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/registrations" ) type Target interface{} diff --git a/pkg/registrations/suite_test.go b/api/utils/registrations/suite_test.go similarity index 100% rename from pkg/registrations/suite_test.go rename to api/utils/registrations/suite_test.go diff --git a/api/utils/registrations/utils.go b/api/utils/registrations/utils.go new file mode 100644 index 000000000..a1e3ddd7c --- /dev/null +++ b/api/utils/registrations/utils.go @@ -0,0 +1,94 @@ +package registrations + +import ( + "encoding/json" + + "github.com/mandelsoft/goutils/errors" + + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" +) + +type Decoder func(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) + +func DecodeDefaultedConfig[T any](config interface{}, d ...Decoder) (*T, error) { + if config == nil { + var cfg T + return &cfg, nil + } + return DecodeConfig[T](config, d...) +} + +func DecodeConfig[T any](config interface{}, d ...Decoder) (*T, error) { + var err error + + if config == nil { + return nil, nil + } + + var cfg *T + switch a := config.(type) { + case string: + cfg, err = decodeConfig[T]([]byte(a), d...) + case json.RawMessage: + cfg, err = decodeConfig[T](a, d...) + case []byte: + cfg, err = decodeConfig[T](a, d...) + case *T: + cfg = a + case T: + cfg = &a + default: + var data []byte + data, err = json.Marshal(a) + if err != nil { + return nil, err + } + cfg, err = decodeConfig[T](data, d...) + } + if err != nil { + return nil, errors.Wrapf(err, "cannot unmarshal config") + } + return cfg, nil +} + +func decodeConfig[T any](data []byte, dec ...Decoder) (*T, error) { + if d := utils.Optional(dec...); d != nil { + r, err := d(data, runtime.DefaultYAMLEncoding) + if err != nil { + return nil, err + } + if eff, ok := r.(*T); ok { + return eff, nil + } + return nil, errors.Newf("invalid decoded type %T ", r) + } + + var c T + err := runtime.DefaultYAMLEncoding.Unmarshal(data, &c) + if err != nil { + return nil, err + } + return &c, nil +} + +func DecodeAnyConfig(config interface{}) (json.RawMessage, error) { + var attr json.RawMessage + switch a := config.(type) { + case json.RawMessage: + attr = a + case []byte: + err := runtime.DefaultYAMLEncoding.Unmarshal(a, &attr) + if err != nil { + return nil, errors.Wrapf(err, "invalid target specification") + } + attr = a + default: + data, err := json.Marshal(config) + if err != nil { + return nil, errors.Wrapf(err, "invalid target specification") + } + attr = data + } + return attr, nil +} diff --git a/pkg/runtime/README.md b/api/utils/runtime/README.md similarity index 100% rename from pkg/runtime/README.md rename to api/utils/runtime/README.md diff --git a/pkg/runtime/binary.go b/api/utils/runtime/binary.go similarity index 100% rename from pkg/runtime/binary.go rename to api/utils/runtime/binary.go diff --git a/pkg/runtime/convert.go b/api/utils/runtime/convert.go similarity index 100% rename from pkg/runtime/convert.go rename to api/utils/runtime/convert.go diff --git a/pkg/runtime/datatypes.go b/api/utils/runtime/datatypes.go similarity index 100% rename from pkg/runtime/datatypes.go rename to api/utils/runtime/datatypes.go diff --git a/api/utils/runtime/descriptivetype/options.go b/api/utils/runtime/descriptivetype/options.go new file mode 100644 index 000000000..730b05a4b --- /dev/null +++ b/api/utils/runtime/descriptivetype/options.go @@ -0,0 +1,65 @@ +package descriptivetype + +import ( + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/utils/runtime" +) + +//////////////////////////////////////////////////////////////////////////////// + +// TypeObjectTarget is used as target for option functions, it provides +// setters for fields, which should not be modifiable for a final type object. +type TypeObjectTarget[E runtime.VersionedTypedObject] struct { + target *TypedObjectTypeObject[E] +} + +func NewTypeObjectTarget[E runtime.VersionedTypedObject](target *TypedObjectTypeObject[E]) *TypeObjectTarget[E] { + return &TypeObjectTarget[E]{target} +} + +func (t *TypeObjectTarget[E]) SetDescription(value string) { + t.target.description = value +} + +func (t *TypeObjectTarget[E]) SetFormat(value string) { + t.target.format = value +} + +//////////////////////////////////////////////////////////////////////////////// +// Descriptive Type Options + +type OptionTarget interface { + SetFormat(string) + SetDescription(string) +} + +type Option = optionutils.Option[OptionTarget] + +//////////////////////////////////////////////////////////////////////////////// + +type formatOption struct { + value string +} + +func WithFormatSpec(value string) Option { + return formatOption{value} +} + +func (o formatOption) ApplyTo(t OptionTarget) { + t.SetFormat(o.value) +} + +//////////////////////////////////////////////////////////////////////////////// + +type descriptionOption struct { + value string +} + +func WithDescription(value string) Option { + return descriptionOption{value} +} + +func (o descriptionOption) ApplyTo(t OptionTarget) { + t.SetDescription(o.value) +} diff --git a/api/utils/runtime/descriptivetype/type.go b/api/utils/runtime/descriptivetype/type.go new file mode 100644 index 000000000..a5dada08a --- /dev/null +++ b/api/utils/runtime/descriptivetype/type.go @@ -0,0 +1,180 @@ +package descriptivetype + +import ( + "fmt" + "strings" + + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" +) + +// DescriptionExtender provides an additional descrition for a type object +// which is appended to the format description in the schmeme descrition +// for the type in question. +type DescriptionExtender[T any] func(t T) string + +// TypedObjectType is the appropriately extended type interface +// based on runtime.VersionTypedObjectType providing support for a functional and +// format description. +type TypedObjectType[T runtime.VersionedTypedObject] interface { + runtime.VersionedTypedObjectType[T] + + Description() string + Format() string +} + +//////////////////////////////////////////////////////////////////////////////// + +// TypeScheme is the appropriately extended scheme interface based on +// runtime.TypeScheme. Based on the additional type info a complete +// scheme description can be created calling the Describe method. +type TypeScheme[T runtime.VersionedTypedObject, R TypedObjectType[T]] interface { + runtime.TypeScheme[T, R] + + Describe() string +} + +type _typeScheme[T runtime.VersionedTypedObject, R TypedObjectType[T]] interface { + runtime.TypeScheme[T, R] // for goland to be able to accept extender argument type +} + +type typeScheme[T runtime.VersionedTypedObject, R TypedObjectType[T], S TypeScheme[T, R]] struct { + name string + extender DescriptionExtender[R] + _typeScheme[T, R] +} + +func MustNewDefaultTypeScheme[T runtime.VersionedTypedObject, R TypedObjectType[T], S TypeScheme[T, R]](name string, extender DescriptionExtender[R], unknown runtime.Unstructured, acceptUnknown bool, defaultdecoder runtime.TypedObjectDecoder[T], base ...TypeScheme[T, R]) TypeScheme[T, R] { + scheme := runtime.MustNewDefaultTypeScheme[T, R](unknown, acceptUnknown, defaultdecoder, utils.Optional(base...)) + return &typeScheme[T, R, S]{ + name: name, + extender: extender, + _typeScheme: scheme, + } +} + +// NewTypeScheme provides an TypeScheme implementation based on the interfaces +// and the default runtime.TypeScheme implementation. +func NewTypeScheme[T runtime.VersionedTypedObject, R TypedObjectType[T], S TypeScheme[T, R]](name string, extender DescriptionExtender[R], unknown runtime.Unstructured, acceptUnknown bool, base ...S) TypeScheme[T, R] { + scheme := runtime.MustNewDefaultTypeScheme[T, R](unknown, acceptUnknown, nil, utils.Optional(base...)) + return &typeScheme[T, R, S]{ + name: name, + extender: extender, + _typeScheme: scheme, + } +} + +func (t *typeScheme[T, R, S]) KnownTypes() runtime.KnownTypes[T, R] { + return t._typeScheme.KnownTypes() // Goland +} + +//////////////////////////////////////////////////////////////////////////////// + +func (t *typeScheme[T, R, S]) Describe() string { + s := "" + type method struct { + desc string + versions map[string]string + more string + } + + descs := map[string]*method{} + + // gather info for kinds and versions + for _, n := range t.KnownTypeNames() { + kind, vers := runtime.KindVersion(n) + + info := descs[kind] + if info == nil { + info = &method{versions: map[string]string{}} + descs[kind] = info + } + + if vers == "" { + vers = "v1" + } + if _, ok := info.versions[vers]; !ok { + info.versions[vers] = "" + } + + ty := t.GetType(n) + + if t.extender != nil { + more := t.extender(ty) + if more != "" { + info.more = more + } + } + desc := ty.Description() + if desc != "" { + info.desc = desc + } + + desc = ty.Format() + if desc != "" { + info.versions[vers] = desc + } + } + + for _, tn := range utils.StringMapKeys(descs) { + info := descs[tn] + desc := strings.Trim(info.desc, "\n") + if desc != "" { + s = fmt.Sprintf("%s\n- %s %s\n\n%s\n\n", s, t.name, tn, utils.IndentLines(desc, " ")) + + format := "" + for _, f := range utils.StringMapKeys(info.versions) { + desc = strings.Trim(info.versions[f], "\n") + if desc != "" { + format = fmt.Sprintf("%s\n- Version %s\n\n%s\n", format, f, utils.IndentLines(desc, " ")) + } + } + if format != "" { + s += fmt.Sprintf(" The following versions are supported:\n%s\n", strings.Trim(utils.IndentLines(format, " "), "\n")) + } + } + s += info.more + } + return s +} + +//////////////////////////////////////////////////////////////////////////////// + +type descriptiveTypeInfo interface { + Description() string + Format() string +} + +type TypedObjectTypeObject[T runtime.VersionedTypedObject] struct { + runtime.VersionedTypedObjectType[T] + description string + format string + validator func(T) error +} + +var _ descriptiveTypeInfo = (*TypedObjectTypeObject[runtime.VersionedTypedObject])(nil) + +func NewTypedObjectTypeObject[E runtime.VersionedTypedObject](vt runtime.VersionedTypedObjectType[E], opts ...Option) *TypedObjectTypeObject[E] { + t := NewTypeObjectTarget[E](&TypedObjectTypeObject[E]{ + VersionedTypedObjectType: vt, + }) + for _, o := range opts { + o.ApplyTo(t) + } + return t.target +} + +func (t *TypedObjectTypeObject[T]) Description() string { + return t.description +} + +func (t *TypedObjectTypeObject[T]) Format() string { + return t.format +} + +func (t *TypedObjectTypeObject[T]) Validate(e T) error { + if t.validator == nil { + return nil + } + return t.validator(e) +} diff --git a/pkg/runtime/encoding.go b/api/utils/runtime/encoding.go similarity index 100% rename from pkg/runtime/encoding.go rename to api/utils/runtime/encoding.go diff --git a/pkg/runtime/multi.go b/api/utils/runtime/multi.go similarity index 98% rename from pkg/runtime/multi.go rename to api/utils/runtime/multi.go index b3ca29a7a..2caed54bd 100644 --- a/pkg/runtime/multi.go +++ b/api/utils/runtime/multi.go @@ -8,8 +8,8 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/goutils/set" - "github.com/open-component-model/ocm/pkg/errkind" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/errkind" ) type multiFormatVersion[T VersionedTypedObject] struct { diff --git a/pkg/runtime/multi_test.go b/api/utils/runtime/multi_test.go similarity index 96% rename from pkg/runtime/multi_test.go rename to api/utils/runtime/multi_test.go index f47c969fd..46f4a435b 100644 --- a/pkg/runtime/multi_test.go +++ b/api/utils/runtime/multi_test.go @@ -5,7 +5,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/runtime" + "ocm.software/ocm/api/utils/runtime" ) type TestSpecType = runtime.VersionedTypedObjectType[TestSpecRealm] diff --git a/api/utils/runtime/object_test.go b/api/utils/runtime/object_test.go new file mode 100644 index 000000000..a2fc4b226 --- /dev/null +++ b/api/utils/runtime/object_test.go @@ -0,0 +1,97 @@ +package runtime_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/utils/runtime" +) + +var _ = Describe("*** basic types", func() { + Context("type name", func() { + It("one arg", func() { + t := runtime.TypeName("test") + Expect(t).To(Equal("test")) + }) + It("two arg", func() { + t := runtime.TypeName("test", "v1") + Expect(t).To(Equal("test" + runtime.VersionSeparator + "v1")) + }) + It("two arg empty", func() { + t := runtime.TypeName("test", "") + Expect(t).To(Equal("test")) + }) + It("two arg", func() { + defer func() { + e := recover() + Expect(e).NotTo(BeNil()) + }() + runtime.TypeName("test", "v1", "v3") + Fail("no panic") + }) + }) + Context("object type", func() { + It("gets the type", func() { + t := runtime.NewObjectType("test") + Expect(t.GetType()).To(Equal("test")) + }) + It("sets the type", func() { + t := runtime.NewObjectType("test") + t.SetType("other") + Expect(t.GetType()).To(Equal("other")) + }) + }) + + Context("versioned object type", func() { + It("get type and version of unversioned type", func() { + t := runtime.NewVersionedTypedObject("test", "") + Expect(t.GetType()).To(Equal("test")) + Expect(t.GetKind()).To(Equal("test")) + Expect(t.GetVersion()).To(Equal("v1")) + }) + It("get type and version of versioned type", func() { + t := runtime.NewVersionedTypedObject("test", "v2") + Expect(t.GetType()).To(Equal(runtime.TypeName("test", "v2"))) + Expect(t.GetKind()).To(Equal("test")) + Expect(t.GetVersion()).To(Equal("v2")) + }) + + It("set type", func() { + t := runtime.NewVersionedTypedObject("test", "v2") + t.SetType(runtime.TypeName("other", "v3")) + Expect(t.GetType()).To(Equal(runtime.TypeName("other", "v3"))) + Expect(t.GetKind()).To(Equal("other")) + Expect(t.GetVersion()).To(Equal("v3")) + }) + + It("set kind on unversioned", func() { + t := runtime.NewVersionedTypedObject("test") + t.SetKind(runtime.TypeName("other")) + Expect(t.GetType()).To(Equal(runtime.TypeName("other"))) + Expect(t.GetKind()).To(Equal("other")) + Expect(t.GetVersion()).To(Equal("v1")) + }) + It("set version on unversioned", func() { + t := runtime.NewVersionedTypedObject("test") + t.SetVersion(runtime.TypeName("v3")) + Expect(t.GetType()).To(Equal(runtime.TypeName("test", "v3"))) + Expect(t.GetKind()).To(Equal("test")) + Expect(t.GetVersion()).To(Equal("v3")) + }) + + It("set kind on versioned", func() { + t := runtime.NewVersionedTypedObject("test", "v2") + t.SetKind(runtime.TypeName("other")) + Expect(t.GetType()).To(Equal(runtime.TypeName("other", "v2"))) + Expect(t.GetKind()).To(Equal("other")) + Expect(t.GetVersion()).To(Equal("v2")) + }) + It("set version on unversioned", func() { + t := runtime.NewVersionedTypedObject("test", "v2") + t.SetVersion(runtime.TypeName("v3")) + Expect(t.GetType()).To(Equal(runtime.TypeName("test", "v3"))) + Expect(t.GetKind()).To(Equal("test")) + Expect(t.GetVersion()).To(Equal("v3")) + }) + }) +}) diff --git a/api/utils/runtime/scheme.go b/api/utils/runtime/scheme.go new file mode 100644 index 000000000..095dcc7f9 --- /dev/null +++ b/api/utils/runtime/scheme.go @@ -0,0 +1,469 @@ +package runtime + +import ( + "encoding/json" + "fmt" + "reflect" + "sort" + "sync" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/general" + "github.com/mandelsoft/goutils/generics" + "github.com/modern-go/reflect2" + + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/errkind" +) + +var ( + typeTypedObject = reflect.TypeOf((*TypedObject)(nil)).Elem() + typeUnknown = reflect.TypeOf((*Unknown)(nil)).Elem() +) + +type ( + // TypedObjectDecoder is able to provide an effective typed object for some + // serilaized form. The technical deserialization is done by an Unmarshaler. + TypedObjectDecoder[T TypedObject] interface { + Decode(data []byte, unmarshaler Unmarshaler) (T, error) + } + _TypedObjectDecoder[T TypedObject] interface { + TypedObjectDecoder[T] + } +) + +// TypedObjectEncoder is able to provide a versioned representation of +// an effective TypedObject. +type TypedObjectEncoder[T TypedObject] interface { + Encode(T, Marshaler) ([]byte, error) +} + +type DirectDecoder[T TypedObject] struct { + proto reflect.Type +} + +var _ TypedObjectDecoder[TypedObject] = &DirectDecoder[TypedObject]{} + +func MustNewDirectDecoder[T TypedObject](proto T) *DirectDecoder[T] { + d, err := NewDirectDecoder[T](proto) + if err != nil { + panic(err) + } + return d +} + +func NewDirectDecoder[T TypedObject](proto T) (*DirectDecoder[T], error) { + t := MustProtoType(proto) + if !reflect.PtrTo(t).Implements(typeTypedObject) { + return nil, errors.Newf("object interface %T: must implement TypedObject", proto) + } + if t.Kind() != reflect.Struct { + return nil, errors.Newf("prototype %q must be a struct", t) + } + return &DirectDecoder[T]{ + proto: t, + }, nil +} + +func (d *DirectDecoder[T]) CreateInstance() T { + return reflect.New(d.proto).Interface().(T) +} + +func (d *DirectDecoder[T]) Decode(data []byte, unmarshaler Unmarshaler) (T, error) { + var zero T + inst := d.CreateInstance() + err := unmarshaler.Unmarshal(data, inst) + if err != nil { + return zero, err + } + + return inst, nil +} + +func (d *DirectDecoder[T]) Encode(obj T, marshaler Marshaler) ([]byte, error) { + return marshaler.Marshal(obj) +} + +// KnownTypes is a set of known type names mapped to appropriate object decoders. +type KnownTypes[T TypedObject, R TypedObjectDecoder[T]] map[string]R + +// Copy provides a copy of the actually known types. +func (t KnownTypes[T, R]) Copy() KnownTypes[T, R] { + n := KnownTypes[T, R]{} + for k, v := range t { + n[k] = v + } + return n +} + +// TypeNames return a sorted list of known type names. +func (t KnownTypes[T, R]) TypeNames() []string { + types := make([]string, 0, len(t)) + for t := range t { + types = append(types, t) + } + sort.Strings(types) + return types +} + +// Unknown is the interface to be implemented by +// representations on an unknown, but nevertheless decoded specification +// of a typed object. +type Unknown interface { + IsUnknown() bool +} + +func IsUnknown(o TypedObject) bool { + if reflect2.IsNil(o) { + return true + } + if u, ok := o.(Unknown); ok { + return u.IsUnknown() + } + return false +} + +type ( + // Scheme is the interface to describe a set of object types + // that implement a dedicated interface. + // As such it knows about the desired interface of the instances + // and can validate it. Additionally, it provides an implementation + // for generic unstructured objects that can be used to decode + // any serialized from of object candidates and provide the + // effective type. + Scheme[T TypedObject, R TypedObjectDecoder[T]] interface { + SchemeCommon + KnownTypesProvider[T, R] + TypedObjectEncoder[T] + TypedObjectDecoder[T] + + BaseScheme() Scheme[T, R] // Go does not support an additional type parameter S Scheme[T,S] to return the correct type here + + AddKnownTypes(scheme KnownTypesProvider[T, R]) + RegisterByDecoder(typ string, decoder R) error + + ValidateInterface(object T) error + CreateUnstructured() T + Convert(object TypedObject) (T, error) + GetDecoder(otype string) R + EnforceDecode(data []byte, unmarshaler Unmarshaler) (T, error) + } + _Scheme[T TypedObject, R TypedObjectDecoder[T]] interface { // cannot omit nesting, because Goland does not accept it + Scheme[T, R] + } +) + +type KnownTypesProvider[T TypedObject, R TypedObjectDecoder[T]] interface { + KnownTypes() KnownTypes[T, R] +} + +type SchemeCommon interface { + KnownTypeNames() []string +} + +type defaultScheme[T TypedObject, R TypedObjectDecoder[T]] struct { + lock sync.RWMutex + base Scheme[T, R] + instance reflect.Type + unstructured reflect.Type + defaultdecoder TypedObjectDecoder[T] + acceptUnknown bool + types KnownTypes[T, R] +} + +var _ Scheme[VersionedTypedObject, TypedObjectDecoder[VersionedTypedObject]] = (*defaultScheme[VersionedTypedObject, TypedObjectDecoder[VersionedTypedObject]])(nil) + +func MustNewDefaultScheme[T TypedObject, R TypedObjectDecoder[T]](protoUnstr Unstructured, acceptUnknown bool, defaultdecoder TypedObjectDecoder[T], base ...Scheme[T, R]) Scheme[T, R] { + return utils.Must(NewDefaultScheme[T](protoUnstr, acceptUnknown, defaultdecoder, base...)) +} + +func NewScheme[T TypedObject, R TypedObjectDecoder[T]](base ...Scheme[T, R]) Scheme[T, R] { + s, _ := NewDefaultScheme[T](nil, false, nil, base...) + return s +} + +func NewDefaultScheme[T TypedObject, R TypedObjectDecoder[T]](protoUnstr Unstructured, acceptUnknown bool, defaultdecoder TypedObjectDecoder[T], base ...Scheme[T, R]) (Scheme[T, R], error) { + var err error + + var protoIfce T + it := reflect.TypeOf(&protoIfce) + for it.Kind() == reflect.Ptr { + it = it.Elem() + } + + var ut reflect.Type + if acceptUnknown { + ut, err = ProtoType(protoUnstr) + if err != nil { + return nil, errors.Wrapf(err, "unstructured prototype %T", protoUnstr) + } + if !reflect.PtrTo(ut).Implements(it) { + return nil, fmt.Errorf("unstructured type %T must implement %T to be acceptale as unknown result", protoUnstr, &protoIfce) + } + if !reflect.PtrTo(ut).Implements(typeUnknown) { + return nil, fmt.Errorf("unstructured type %T must implement Unknown to be acceptable as unknown result", protoUnstr) + } + } + + return &defaultScheme[T, R]{ + base: utils.Optional(base...), + instance: it, + unstructured: ut, + defaultdecoder: defaultdecoder, + types: KnownTypes[T, R]{}, + acceptUnknown: acceptUnknown, + }, nil +} + +func (d *defaultScheme[T, R]) BaseScheme() Scheme[T, R] { + return d.base +} + +func (d *defaultScheme[T, R]) AddKnownTypes(s KnownTypesProvider[T, R]) { + d.lock.Lock() + defer d.lock.Unlock() + for k, v := range s.KnownTypes() { + d.types[k] = v + } +} + +func (d *defaultScheme[T, R]) KnownTypes() KnownTypes[T, R] { + d.lock.RLock() + defer d.lock.RUnlock() + if d.base == nil { + return d.types.Copy() + } + kt := d.base.KnownTypes() + for n, t := range d.types { + kt[n] = t + } + return kt +} + +// KnownTypeNames return a sorted list of known type names. +func (d *defaultScheme[T, R]) KnownTypeNames() []string { + d.lock.RLock() + defer d.lock.RUnlock() + + types := make([]string, 0, len(d.types)) + for t := range d.types { + types = append(types, t) + } + if d.base != nil { + types = append(types, d.base.KnownTypeNames()...) + } + sort.Strings(types) + return types +} + +func (d *defaultScheme[T, R]) RegisterByDecoder(typ string, decoder R) error { + if reflect2.IsNil(decoder) { + return errors.Newf("decoder must be given") + } + d.lock.Lock() + defer d.lock.Unlock() + d.types[typ] = decoder + return nil +} + +func (d *defaultScheme[T, R]) ValidateInterface(object T) error { + t := reflect.TypeOf(object) + if !t.Implements(d.instance) { + return errors.Newf("object type %q does not implement required instance interface %q", t, d.instance) + } + return nil +} + +func (d *defaultScheme[T, R]) GetDecoder(typ string) R { + d.lock.RLock() + defer d.lock.RUnlock() + decoder := d.types[typ] + if reflect2.IsNil(decoder) && d.base != nil { + decoder = d.base.GetDecoder(typ) + } + return decoder +} + +func (d *defaultScheme[T, R]) CreateUnstructured() T { + var _nil T + if d.unstructured == nil { + return _nil + } + return reflect.New(d.unstructured).Interface().(T) +} + +func (d *defaultScheme[T, R]) Encode(obj T, marshaler Marshaler) ([]byte, error) { + if marshaler == nil { + marshaler = DefaultYAMLEncoding + } + decoder := d.GetDecoder(obj.GetType()) + if encoder, ok := generics.TryCast[TypedObjectEncoder[T]](decoder); ok { + return encoder.Encode(obj, marshaler) + } + return marshaler.Marshal(obj) +} + +func (d *defaultScheme[T, R]) Decode(data []byte, unmarshal Unmarshaler) (T, error) { + var _nil T + + var to TypedObject + un := d.CreateUnstructured() + if reflect2.IsNil(un) { + to = &UnstructuredTypedObject{} + } else { + to = un + } + if unmarshal == nil { + unmarshal = DefaultYAMLEncoding + } + err := unmarshal.Unmarshal(data, to) + if err != nil { + return _nil, errors.Wrapf(err, "cannot unmarshal unstructured") + } + if to.GetType() == "" { + return _nil, errors.Newf("no type found") + } + decoder := d.GetDecoder(to.GetType()) + if reflect2.IsNil(decoder) { + if d.defaultdecoder != nil { + o, err := d.defaultdecoder.Decode(data, unmarshal) + if err == nil { + if !reflect2.IsNil(o) { + return o, nil + } + } else if !errors.IsErrUnknownKind(err, errkind.KIND_OBJECTTYPE) { + return _nil, err + } + } + if d.acceptUnknown { + return un, nil + } + return _nil, errors.ErrUnknown(errkind.KIND_OBJECTTYPE, to.GetType()) + } + return decoder.Decode(data, unmarshal) +} + +func (d *defaultScheme[T, R]) EnforceDecode(data []byte, unmarshal Unmarshaler) (T, error) { + var _nil T + + un := d.CreateUnstructured() + if unmarshal == nil { + unmarshal = DefaultYAMLEncoding.Unmarshaler + } + err := unmarshal.Unmarshal(data, un) + if err != nil { + return _nil, errors.Wrapf(err, "cannot unmarshal unstructured") + } + if un.GetType() == "" { + if d.acceptUnknown { + return un, nil + } + return un, errors.Newf("no type found") + } + decoder := d.GetDecoder(un.GetType()) + if reflect2.IsNil(decoder) { + if d.defaultdecoder != nil { + o, err := d.defaultdecoder.Decode(data, unmarshal) + if err == nil { + return o, nil + } + if !errors.IsErrUnknownKind(err, errkind.KIND_OBJECTTYPE) { + return un, err + } + } + if d.acceptUnknown { + return un, nil + } + return un, errors.ErrUnknown(errkind.KIND_OBJECTTYPE, un.GetType()) + } + o, err := decoder.Decode(data, unmarshal) + if err != nil { + return un, err + } + return o, err +} + +func (d *defaultScheme[T, R]) Convert(o TypedObject) (T, error) { + var _nil T + + if o.GetType() == "" { + return _nil, errors.Newf("no type found") + } + + if u, ok := o.(T); ok { + return u, nil + } + + if u, ok := o.(Unstructured); ok { + raw, err := u.GetRaw() + if err != nil { + return _nil, err + } + return d.Decode(raw, DefaultJSONEncoding) + } + + data, err := json.Marshal(o) + if err != nil { + return _nil, err + } + decoder := d.GetDecoder(o.GetType()) + if reflect2.IsNil(decoder) { + if d.defaultdecoder != nil { + object, err := d.defaultdecoder.Decode(data, DefaultJSONEncoding) + if err == nil { + return object, nil + } + if !errors.IsErrUnknownKind(err, errkind.KIND_OBJECTTYPE) { + return _nil, err + } + } + return _nil, errors.ErrUnknown(errkind.KIND_OBJECTTYPE, o.GetType()) + } + r, err := decoder.Decode(data, DefaultJSONEncoding) + if err != nil { + return _nil, err + } + if reflect.TypeOf(r) == reflect.TypeOf(o) { + return o.(T), nil + } + return r, nil +} + +//////////////////////////////////////////////////////////////////////////////// + +// TypeScheme is a scheme based on Types instead of decoders. +type TypeScheme[T TypedObject, R TypedObjectType[T]] interface { + Scheme[T, R] + + Register(typ R) + GetType(name string) R +} + +type defaultTypeScheme[T TypedObject, R TypedObjectType[T]] struct { + _Scheme[T, R] +} + +func MustNewDefaultTypeScheme[T TypedObject, R TypedObjectType[T]](protoUnstr Unstructured, acceptUnknown bool, defaultdecoder TypedObjectDecoder[T], base ...TypeScheme[T, R]) TypeScheme[T, R] { + return utils.Must(NewDefaultTypeScheme[T, R](protoUnstr, acceptUnknown, defaultdecoder, base...)) +} + +func NewTypeScheme[T TypedObject, R TypedObjectType[T]](base ...TypeScheme[T, R]) TypeScheme[T, R] { + s, _ := NewDefaultTypeScheme[T](nil, false, nil, base...) + return s +} + +func NewDefaultTypeScheme[T TypedObject, R TypedObjectType[T]](protoUnstr Unstructured, acceptUnknown bool, defaultdecoder TypedObjectDecoder[T], base ...TypeScheme[T, R]) (TypeScheme[T, R], error) { + s, err := NewDefaultScheme[T](protoUnstr, acceptUnknown, defaultdecoder, generics.Cast[Scheme[T, R]](general.Optional(base...))) + if err != nil { + return nil, err + } + return &defaultTypeScheme[T, R]{s}, nil +} + +func (s *defaultTypeScheme[T, R]) Register(t R) { + s.RegisterByDecoder(t.GetType(), t) +} + +func (s *defaultTypeScheme[T, R]) GetType(name string) R { + return generics.Cast[R](s.GetDecoder(name)) +} diff --git a/pkg/runtime/scheme_test.go b/api/utils/runtime/scheme_test.go similarity index 95% rename from pkg/runtime/scheme_test.go rename to api/utils/runtime/scheme_test.go index e275a4440..2f55fb955 100644 --- a/pkg/runtime/scheme_test.go +++ b/api/utils/runtime/scheme_test.go @@ -5,8 +5,8 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" ) type TType runtime.TypedObjectDecoder[T] diff --git a/pkg/runtime/suite_test.go b/api/utils/runtime/suite_test.go similarity index 100% rename from pkg/runtime/suite_test.go rename to api/utils/runtime/suite_test.go diff --git a/pkg/runtime/typedobject.go b/api/utils/runtime/typedobject.go similarity index 100% rename from pkg/runtime/typedobject.go rename to api/utils/runtime/typedobject.go diff --git a/pkg/runtime/unstructured.go b/api/utils/runtime/unstructured.go similarity index 99% rename from pkg/runtime/unstructured.go rename to api/utils/runtime/unstructured.go index 2e5020a8e..5587d98af 100644 --- a/pkg/runtime/unstructured.go +++ b/api/utils/runtime/unstructured.go @@ -10,7 +10,7 @@ import ( "github.com/modern-go/reflect2" "github.com/sirupsen/logrus" - "github.com/open-component-model/ocm/pkg/errkind" + "ocm.software/ocm/api/utils/errkind" ) const ATTR_TYPE = "type" diff --git a/pkg/runtime/unstructured_test.go b/api/utils/runtime/unstructured_test.go similarity index 98% rename from pkg/runtime/unstructured_test.go rename to api/utils/runtime/unstructured_test.go index d466a5418..ed85c6973 100644 --- a/pkg/runtime/unstructured_test.go +++ b/api/utils/runtime/unstructured_test.go @@ -9,8 +9,8 @@ import ( "github.com/mandelsoft/logging" - ocmlog "github.com/open-component-model/ocm/pkg/logging" - "github.com/open-component-model/ocm/pkg/runtime" + ocmlog "ocm.software/ocm/api/utils/logging" + "ocm.software/ocm/api/utils/runtime" ) func InOut(log logging.Logger, in runtime.TypedObject, encoding runtime.Encoding) (runtime.TypedObject, string, error) { diff --git a/pkg/runtime/unstructuredversioned.go b/api/utils/runtime/unstructuredversioned.go similarity index 100% rename from pkg/runtime/unstructuredversioned.go rename to api/utils/runtime/unstructuredversioned.go diff --git a/pkg/runtime/utils.go b/api/utils/runtime/utils.go similarity index 100% rename from pkg/runtime/utils.go rename to api/utils/runtime/utils.go diff --git a/pkg/runtime/validate.go b/api/utils/runtime/validate.go similarity index 100% rename from pkg/runtime/validate.go rename to api/utils/runtime/validate.go diff --git a/pkg/runtime/value.go b/api/utils/runtime/value.go similarity index 100% rename from pkg/runtime/value.go rename to api/utils/runtime/value.go diff --git a/api/utils/runtime/version_test.go b/api/utils/runtime/version_test.go new file mode 100644 index 000000000..82d195c6c --- /dev/null +++ b/api/utils/runtime/version_test.go @@ -0,0 +1,170 @@ +package runtime_test + +import ( + "encoding/json" + + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/utils/runtime" +) + +const ( + Type1 = "testType1" + Type1V1 = Type1 + "/v1" + Type1V2 = Type1 + "/v2" + + Type2 = "testType2" + Type2V1 = Type2 + "/v1" +) + +var versions runtime.Scheme[TestSpecRealm, TestType] + +func init() { + versions = runtime.MustNewDefaultScheme[TestSpecRealm, TestType](nil, false, nil) + + versions.RegisterByDecoder(Type1, runtime.NewVersionedTypedObjectTypeByConverter[TestSpecRealm, *TestSpec1, *Spec1V1](Type1, &converterSpec1V1{})) + versions.RegisterByDecoder(Type1V1, runtime.NewVersionedTypedObjectTypeByConverter[TestSpecRealm, *TestSpec1, *Spec1V1](Type1V1, &converterSpec1V1{})) + + versions.RegisterByDecoder(Type2, runtime.NewVersionedTypedObjectType[TestSpecRealm, *TestSpec2](Type2)) + versions.RegisterByDecoder(Type2V1, runtime.NewVersionedTypedObjectType[TestSpecRealm, *TestSpec2](Type2V1)) +} + +type TestType runtime.TypedObjectDecoder[TestSpecRealm] + +// TestSpec is the realm. +type TestSpecRealm interface { + runtime.VersionedTypedObject + TestFunction() +} + +// TestSpec1 is a first implementation of the realm TestSpec. +// It is used as internal version. +type TestSpec1 struct { + runtime.InternalVersionedTypedObject[TestSpecRealm] + Field string `json:"field"` +} + +func (a TestSpec1) MarshalJSON() ([]byte, error) { + return runtime.MarshalVersionedTypedObject(&a) +} + +func (a *TestSpec2) TestFunction() {} + +func NewTestSpec1(field string) *TestSpec1 { + return &TestSpec1{ + InternalVersionedTypedObject: runtime.NewInternalVersionedTypedObject[TestSpecRealm](versions, Type1), + Field: field, + } +} + +// Spec1V1 is an old v1 version of a TestSpec1. +type Spec1V1 struct { + runtime.ObjectVersionedType + OldField string `json:"oldField"` +} + +type converterSpec1V1 struct{} + +var _ runtime.Converter[*TestSpec1, *Spec1V1] = (*converterSpec1V1)(nil) + +func (_ converterSpec1V1) ConvertFrom(in *TestSpec1) (*Spec1V1, error) { + return &Spec1V1{ + ObjectVersionedType: runtime.NewVersionedObjectType(in.Type), + OldField: in.Field, + }, nil +} + +func (_ converterSpec1V1) ConvertTo(in *Spec1V1) (*TestSpec1, error) { + return &TestSpec1{ + InternalVersionedTypedObject: runtime.NewInternalVersionedTypedObject[TestSpecRealm](versions, in.Type), + Field: in.OldField, + }, nil +} + +// Spec1V2 is an old v1 version of a TestSpec1. +type Spec1V2 struct { + runtime.ObjectVersionedType + Field string `json:"field"` +} + +type converterSpec1V2 struct{} + +var _ runtime.Converter[*TestSpec1, *Spec1V2] = (*converterSpec1V2)(nil) + +func (_ converterSpec1V2) ConvertFrom(in *TestSpec1) (*Spec1V2, error) { + return &Spec1V2{ + ObjectVersionedType: runtime.NewVersionedObjectType(in.Type), + Field: in.Field, + }, nil +} + +func (_ converterSpec1V2) ConvertTo(in *Spec1V2) (*TestSpec1, error) { + return &TestSpec1{ + InternalVersionedTypedObject: runtime.NewInternalVersionedTypedObject[TestSpecRealm](versions, in.Type), + Field: in.Field, + }, nil +} + +//////////////////////////////////////////////////////////////////////////////// + +// TestSpec2 is a second implementation of the realm TestSpec. +// It is used a internal version and v2. +type TestSpec2 struct { + runtime.VersionedObjectType + Field string `json:"field"` +} + +func (a *TestSpec1) TestFunction() {} + +func NewTestSpec2(field string) *TestSpec2 { + return &TestSpec2{ + VersionedObjectType: runtime.VersionedObjectType{Type2}, + Field: field, + } +} + +//////////////////////////////////////////////////////////////////////////////// + +type encoder interface { + getEncoder() int +} + +type object struct{} + +func (_ *object) getEncoder() int { + return 1 +} + +var _ = Describe("versioned types", func() { + var versions runtime.Scheme[TestSpecRealm, TestType] + + versions = runtime.MustNewDefaultScheme[TestSpecRealm, TestType](nil, false, nil) + + versions.RegisterByDecoder(Type1, runtime.NewVersionedTypedObjectTypeByConverter[TestSpecRealm, *TestSpec1, *Spec1V1](Type1, &converterSpec1V1{})) + versions.RegisterByDecoder(Type1V1, runtime.NewVersionedTypedObjectTypeByConverter[TestSpecRealm, *TestSpec1, *Spec1V1](Type1V1, &converterSpec1V1{})) + + versions.RegisterByDecoder(Type2, runtime.NewVersionedTypedObjectType[TestSpecRealm, *TestSpec2](Type2)) + versions.RegisterByDecoder(Type2V1, runtime.NewVersionedTypedObjectType[TestSpecRealm, *TestSpec2](Type2V1)) + + It("marshals version for TestSpec1", func() { + s1 := NewTestSpec1("value") + + data := Must(json.Marshal(s1)) + Expect(string(data)).To(StringEqualWithContext(`{"type":"testType1","oldField":"value"}`)) + + spec := Must(versions.Decode(data, runtime.DefaultJSONEncoding)) + Expect(spec).To(Equal(s1)) + }) + + It("unmarshal version for TestSpec2", func() { + s1 := NewTestSpec2("value") + + data := Must(json.Marshal(s1)) + Expect(string(data)).To(StringEqualWithContext(`{"type":"testType2","field":"value"}`)) + + spec := Must(versions.Decode(data, runtime.DefaultJSONEncoding)) + Expect(spec).To(Equal(s1)) + }) +}) diff --git a/pkg/runtime/versionedtype.go b/api/utils/runtime/versionedtype.go similarity index 99% rename from pkg/runtime/versionedtype.go rename to api/utils/runtime/versionedtype.go index e04dd0b01..dce59ad28 100644 --- a/pkg/runtime/versionedtype.go +++ b/api/utils/runtime/versionedtype.go @@ -6,7 +6,7 @@ import ( "github.com/mandelsoft/goutils/errors" "golang.org/x/exp/slices" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" ) const VersionSeparator = "/" diff --git a/pkg/runtimefinalizer/object.go b/api/utils/runtimefinalizer/object.go similarity index 100% rename from pkg/runtimefinalizer/object.go rename to api/utils/runtimefinalizer/object.go diff --git a/api/utils/runtimefinalizer/object_test.go b/api/utils/runtimefinalizer/object_test.go new file mode 100644 index 000000000..d1a2a6939 --- /dev/null +++ b/api/utils/runtimefinalizer/object_test.go @@ -0,0 +1,61 @@ +package runtimefinalizer_test + +import ( + "fmt" + "runtime" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/utils/runtimefinalizer" +) + +type ObjectType struct { + kind string + id runtimefinalizer.ObjectIdentity + fi *runtimefinalizer.RuntimeFinalizer +} + +func NewOType(kind string, r *runtimefinalizer.RuntimeFinalizationRecoder) *ObjectType { + id := runtimefinalizer.NewObjectIdentity(kind) + o := &ObjectType{ + kind: kind, + id: id, + fi: runtimefinalizer.NewRuntimeFinalizer(id, r), + } + return o +} + +func (o *ObjectType) Id() runtimefinalizer.ObjectIdentity { + return o.id +} + +var _ = Describe("runtime finalizer", func() { + It("finalize with arbitrary method", func() { + r := &runtimefinalizer.RuntimeFinalizationRecoder{} + + o1 := NewOType("test1", r) + o2 := NewOType("test1", r) + + id1 := o1.Id() + id2 := o2.Id() + + runtime.GC() + time.Sleep(time.Second) + + fmt.Printf("still used (%s,%s)\n", o1.Id(), o2.Id()) + Expect(len(r.Get())).To(Equal(0)) + + o1 = nil + runtime.GC() + time.Sleep(time.Second) + fmt.Printf("still used (%s)\n", o2.Id()) + Expect(r.Get()).To(Equal([]runtimefinalizer.ObjectIdentity{id1})) + + o2 = nil + runtime.GC() + time.Sleep(time.Second) + Expect(r.Get()).To(Equal([]runtimefinalizer.ObjectIdentity{id1, id2})) + }) +}) diff --git a/pkg/runtimefinalizer/suite_test.go b/api/utils/runtimefinalizer/suite_test.go similarity index 100% rename from pkg/runtimefinalizer/suite_test.go rename to api/utils/runtimefinalizer/suite_test.go diff --git a/pkg/utils/selector/selector.go b/api/utils/selector/selector.go similarity index 100% rename from pkg/utils/selector/selector.go rename to api/utils/selector/selector.go diff --git a/pkg/semverutils/sort.go b/api/utils/semverutils/sort.go similarity index 100% rename from pkg/semverutils/sort.go rename to api/utils/semverutils/sort.go diff --git a/pkg/semverutils/suite_test.go b/api/utils/semverutils/suite_test.go similarity index 100% rename from pkg/semverutils/suite_test.go rename to api/utils/semverutils/suite_test.go diff --git a/pkg/semverutils/utils.go b/api/utils/semverutils/utils.go similarity index 100% rename from pkg/semverutils/utils.go rename to api/utils/semverutils/utils.go diff --git a/pkg/semverutils/utils_test.go b/api/utils/semverutils/utils_test.go similarity index 100% rename from pkg/semverutils/utils_test.go rename to api/utils/semverutils/utils_test.go diff --git a/api/utils/spiff/options.go b/api/utils/spiff/options.go new file mode 100644 index 000000000..fb39abafa --- /dev/null +++ b/api/utils/spiff/options.go @@ -0,0 +1,166 @@ +package spiff + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/sliceutils" + "github.com/mandelsoft/spiff/spiffing" + "github.com/mandelsoft/vfs/pkg/cwdfs" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/utils" +) + +type Option interface { + ApplyToRequest(r *Request) error +} + +type Options []Option + +func (o *Options) Add(opt Option) *Options { + if opt != nil { + *o = append(*o, opt) + } + return o +} + +func (o Options) ApplyToRequest(r *Request) error { + for _, o := range o { + if o != nil { + err := o.ApplyToRequest(r) + if err != nil { + return err + } + } + } + return nil +} + +func GetRequest(opts ...Option) (*Request, error) { + req := &Request{Mode: spiffing.MODE_DEFAULT} + err := Options(opts).ApplyToRequest(req) + if err != nil { + return nil, err + } + return req, nil +} + +type OptionFunction func(r *Request) error + +func (f OptionFunction) ApplyToRequest(r *Request) error { + return f(r) +} + +func FileSystem(fs vfs.FileSystem) OptionFunction { + return func(r *Request) error { + r.FileSystem = utils.FileSystem(fs) + return nil + } +} + +func Context(ctx datacontext.Context) OptionFunction { + return FileSystem(vfsattr.Get(ctx)) +} + +func Values(values interface{}) OptionFunction { + return func(r *Request) error { + r.Values = values + return nil + } +} + +func Functions(functions spiffing.Functions) OptionFunction { + return func(r *Request) error { + r.Functions = functions + return nil + } +} + +func ValuesNode(values string) OptionFunction { + return func(r *Request) error { + r.ValuesNode = values + return nil + } +} + +func StubData(name string, data []byte) OptionFunction { + return func(r *Request) error { + if len(data) > 0 { + r.Stubs = append(r.Stubs, spiffing.NewSourceData(name, data)) + } + return nil + } +} + +func TemplateData(name string, data []byte) OptionFunction { + return func(r *Request) error { + if len(data) == 0 { + return fmt.Errorf("no template data for " + name) + } + r.Template = spiffing.NewSourceData(name, data) + return nil + } +} + +func StubFile(path string, fss ...vfs.FileSystem) OptionFunction { + return func(r *Request) error { + r.Stubs = append(r.Stubs, spiffing.NewSourceFile(path, utils.FileSystem(sliceutils.CopyAppend(fss, r.FileSystem)...))) + return nil + } +} + +func TemplateFile(path string, fss ...vfs.FileSystem) OptionFunction { + return func(r *Request) error { + r.Template = spiffing.NewSourceFile(path, utils.FileSystem(sliceutils.CopyAppend(fss, r.FileSystem)...)) + return nil + } +} + +func WorkDir(path string) OptionFunction { + return func(r *Request) error { + fs, err := cwdfs.New(r.FileSystem, path) + if err != nil { + return errors.Wrapf(err, "cannot set working directory %s", path) + } + r.FileSystem = fs + return nil + } +} + +func Mode(m int) OptionFunction { + return func(r *Request) error { + r.Mode = m + return nil + } +} + +func Validated(schemedata []byte, opts ...Option) Option { + if schemedata == nil { + return Options(opts) + } + return OptionFunction(func(r *Request) error { + tmp := *r + tmp.Template = nil + tmp.Stubs = nil + err := Options(opts).ApplyToRequest(&tmp) + if err != nil { + return err + } + if tmp.Template != nil { + err = ValidateSourceByScheme(tmp.Template, schemedata) + if err != nil { + return errors.Wrapf(err, "template %s", tmp.Template.Name()) + } + } + for _, s := range tmp.Stubs { + err = ValidateSourceByScheme(s, schemedata) + if err != nil { + return errors.Wrapf(err, "validating %s", s.Name()) + } + } + return Options(opts).ApplyToRequest(r) + }) +} diff --git a/pkg/spiff/spiff.go b/api/utils/spiff/spiff.go similarity index 98% rename from pkg/spiff/spiff.go rename to api/utils/spiff/spiff.go index 14821ee40..6199f13aa 100644 --- a/pkg/spiff/spiff.go +++ b/api/utils/spiff/spiff.go @@ -11,7 +11,7 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" "github.com/modern-go/reflect2" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" ) type Request struct { diff --git a/pkg/spiff/spiff_test.go b/api/utils/spiff/spiff_test.go similarity index 89% rename from pkg/spiff/spiff_test.go rename to api/utils/spiff/spiff_test.go index f4425644f..05d7ae42f 100644 --- a/pkg/spiff/spiff_test.go +++ b/api/utils/spiff/spiff_test.go @@ -7,7 +7,7 @@ import ( "github.com/mandelsoft/spiff/spiffing" - "github.com/open-component-model/ocm/pkg/spiff" + "ocm.software/ocm/api/utils/spiff" ) var _ = Describe("spiff", func() { diff --git a/pkg/spiff/suite_test.go b/api/utils/spiff/suite_test.go similarity index 100% rename from pkg/spiff/suite_test.go rename to api/utils/spiff/suite_test.go diff --git a/pkg/spiff/validate.go b/api/utils/spiff/validate.go similarity index 100% rename from pkg/spiff/validate.go rename to api/utils/spiff/validate.go diff --git a/api/utils/subst/subst.go b/api/utils/subst/subst.go new file mode 100644 index 000000000..55be2c96a --- /dev/null +++ b/api/utils/subst/subst.go @@ -0,0 +1,201 @@ +package subst + +import ( + "bytes" + "container/list" + "regexp" + "sync" + + "github.com/mandelsoft/goutils/errors" + mlog "github.com/mandelsoft/logging" + "github.com/mandelsoft/vfs/pkg/vfs" + "github.com/mikefarah/yq/v4/pkg/yqlib" + glog "gopkg.in/op/go-logging.v1" + "gopkg.in/yaml.v3" + + "ocm.software/ocm/api/utils" + ocmlog "ocm.software/ocm/api/utils/logging" + "ocm.software/ocm/api/utils/runtime" +) + +type SubstitutionTarget interface { + SubstituteByData(path string, value []byte) error + SubstituteByValue(path string, value interface{}) error + + Content() ([]byte, error) +} + +func ParseFile(file string, fss ...vfs.FileSystem) (SubstitutionTarget, error) { + fs := utils.FileSystem(fss...) + + data, err := utils.ReadFile(file, fs) + if err != nil { + return nil, errors.Wrapf(err, "cannot read file %q", file) + } + s, err := Parse(data) + if err != nil { + return nil, errors.Wrapf(err, "file %q", file) + } + return s, nil +} + +func Parse(data []byte) (SubstitutionTarget, error) { + sync.OnceFunc(func() { + var lvl glog.Level + switch ocmlog.Context().GetDefaultLevel() { + case mlog.None: + fallthrough + case mlog.ErrorLevel: + lvl = glog.ERROR + case mlog.WarnLevel: + lvl = glog.WARNING + case mlog.InfoLevel: + lvl = glog.INFO + case mlog.DebugLevel: + fallthrough + case mlog.TraceLevel: + lvl = glog.DEBUG + } + glog.SetLevel(lvl, "yq-lib") + })() + + var ( + err error + fi fileinfo + ) + + fi.json = true + rdr := bytes.NewBuffer(data) + jsnDcdr := yqlib.NewJSONDecoder() + + if err := jsnDcdr.Init(rdr); err != nil { + return nil, err + } + + if fi.content, err = jsnDcdr.Decode(); err != nil { + fi.json = false + ymlPrfs := yqlib.NewDefaultYamlPreferences() + ymlDcdr := yqlib.NewYamlDecoder(ymlPrfs) + rdr = bytes.NewBuffer(data) + if err := ymlDcdr.Init(rdr); err != nil { + return nil, err + } + if fi.content, err = ymlDcdr.Decode(); err != nil { + return nil, err + } + } + + fi.content.SetDocument(0) + fi.content.SetFilename("substitution-target") + fi.content.SetFileIndex(0) + + return &fi, nil +} + +type fileinfo struct { + content *yqlib.CandidateNode + json bool +} + +func (f *fileinfo) Content() ([]byte, error) { + var enc yqlib.Encoder + if f.json { + prfs := yqlib.NewDefaultJsonPreferences() + prfs.ColorsEnabled = false + enc = yqlib.NewJSONEncoder(prfs) + } else { + prfs := yqlib.NewDefaultYamlPreferences() + enc = yqlib.NewYamlEncoder(prfs) + } + + buf := bytes.NewBuffer([]byte{}) + pw := yqlib.NewSinglePrinterWriter(buf) + p := yqlib.NewPrinter(enc, pw) + inptLst := list.New() + inptLst.PushBack(f.content) + + if err := p.PrintResults(inptLst); err == nil { + return buf.Bytes(), nil + } else { + return nil, err + } +} + +var sniffJson = regexp.MustCompile(`^\s*(\{|\[|")`) + +func (f *fileinfo) SubstituteByData(path string, value []byte) error { + var err error + + if !f.json && sniffJson.Match(value) { + // yaml is generally a superset of json so we could just insert the json value + // into a yaml file and have a valid yaml. + // However having a yaml file that looks like a mix of yaml and json is off putting. + // So if the value looks like json and the target file is yaml we will first + // attempt to re-enode the value as yaml before inserting into the target document. + // However... we don't want to perform re-encoding for everything because if the + // value is actually yaml with some snippets in json style for readability + // purposes we don't want to unecessarily lose that styling. Hence the initial + // sniff test for json instead of always re-encoding. + var valueData interface{} + if err = runtime.DefaultJSONEncoding.Unmarshal(value, &valueData); err == nil { + if value, err = runtime.DefaultYAMLEncoding.Marshal(valueData); err != nil { + return err + } + } + } + + m := &yaml.Node{} + if err = yaml.Unmarshal(value, m); err != nil { + return err + } + + nd := &yqlib.CandidateNode{} + nd.SetDocument(0) + nd.SetFilename("value") + nd.SetFileIndex(0) + + if err = nd.UnmarshalYAML(m.Content[0], map[string]*yqlib.CandidateNode{}); err != nil { + return err + } + + return f.substituteByValue(path, nd) +} + +func (f *fileinfo) SubstituteByValue(path string, value interface{}) error { + var mrshl func(interface{}) ([]byte, error) + + if f.json { + mrshl = runtime.DefaultJSONEncoding.Marshal + } else { + mrshl = runtime.DefaultYAMLEncoding.Marshal + } + + if bval, err := mrshl(value); err != nil { + return err + } else { + return f.SubstituteByData(path, bval) + } +} + +func (f *fileinfo) substituteByValue(path string, value *yqlib.CandidateNode) error { + inptLst := list.New() + inptLst.PushBack(f.content) + + vlLst := list.New() + vlLst.PushBack(value) + + ctxt := yqlib.Context{MatchingNodes: inptLst} + ctxt.SetVariable("newValue", vlLst) + + yqlib.InitExpressionParser() + expr := "." + path + " |= $newValue" + + nd, err := yqlib.ExpressionParser.ParseExpression(expr) + if err != nil { + return err + } + + ngvtr := yqlib.NewDataTreeNavigator() + _, err = ngvtr.GetMatchingNodes(ctxt, nd) + return err +} diff --git a/pkg/utils/subst/subst_test.go b/api/utils/subst/subst_test.go similarity index 100% rename from pkg/utils/subst/subst_test.go rename to api/utils/subst/subst_test.go diff --git a/pkg/utils/subst/suite_test.go b/api/utils/subst/suite_test.go similarity index 100% rename from pkg/utils/subst/suite_test.go rename to api/utils/subst/suite_test.go diff --git a/pkg/utils/suite_test.go b/api/utils/suite_test.go similarity index 100% rename from pkg/utils/suite_test.go rename to api/utils/suite_test.go diff --git a/pkg/utils/tarutils/compress.go b/api/utils/tarutils/compress.go similarity index 100% rename from pkg/utils/tarutils/compress.go rename to api/utils/tarutils/compress.go diff --git a/pkg/utils/tarutils/extract.go b/api/utils/tarutils/extract.go similarity index 97% rename from pkg/utils/tarutils/extract.go rename to api/utils/tarutils/extract.go index 4a6b300a2..cffea8c0d 100644 --- a/pkg/utils/tarutils/extract.go +++ b/api/utils/tarutils/extract.go @@ -9,8 +9,8 @@ import ( "github.com/mandelsoft/vfs/pkg/osfs" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/common/compression" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/compression" ) // ExtractArchiveToFs wunpacks an archive to a filesystem. diff --git a/api/utils/tarutils/list.go b/api/utils/tarutils/list.go new file mode 100644 index 000000000..e02811456 --- /dev/null +++ b/api/utils/tarutils/list.go @@ -0,0 +1,53 @@ +package tarutils + +import ( + "archive/tar" + "io" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/vfs/pkg/osfs" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/compression" +) + +func ListArchiveContent(path string, fss ...vfs.FileSystem) ([]string, error) { + sfs := utils.OptionalDefaulted(osfs.New(), fss...) + + f, err := sfs.Open(path) + if err != nil { + return nil, errors.Wrapf(err, "cannot open %s", path) + } + defer f.Close() + return ListArchiveContentFromReader(f) +} + +func ListArchiveContentFromReader(r io.Reader) ([]string, error) { + in, _, err := compression.AutoDecompress(r) + if err != nil { + return nil, errors.Wrapf(err, "cannot determine compression") + } + + var result []string + + tr := tar.NewReader(in) + for { + header, err := tr.Next() + if err != nil { + if errors.Is(err, io.EOF) { + return result, nil + } + return nil, err + } + + switch header.Typeflag { + case tar.TypeDir: + result = append(result, header.Name) + case tar.TypeSymlink, tar.TypeLink: + result = append(result, header.Name) + case tar.TypeReg: + result = append(result, header.Name) + } + } +} diff --git a/pkg/utils/tarutils/pack.go b/api/utils/tarutils/pack.go similarity index 100% rename from pkg/utils/tarutils/pack.go rename to api/utils/tarutils/pack.go diff --git a/pkg/utils/tarutils/pack_test.go b/api/utils/tarutils/pack_test.go similarity index 96% rename from pkg/utils/tarutils/pack_test.go rename to api/utils/tarutils/pack_test.go index 6acc25c54..15008f9d5 100644 --- a/pkg/utils/tarutils/pack_test.go +++ b/api/utils/tarutils/pack_test.go @@ -12,7 +12,7 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/vfs/pkg/osfs" - "github.com/open-component-model/ocm/pkg/utils/tarutils" + "ocm.software/ocm/api/utils/tarutils" ) var _ = Describe("tar utils mapping", func() { diff --git a/pkg/utils/tarutils/suite_test.go b/api/utils/tarutils/suite_test.go similarity index 100% rename from pkg/utils/tarutils/suite_test.go rename to api/utils/tarutils/suite_test.go diff --git a/pkg/utils/tarutils/testdata/dir/dirlink b/api/utils/tarutils/testdata/dir/dirlink similarity index 100% rename from pkg/utils/tarutils/testdata/dir/dirlink rename to api/utils/tarutils/testdata/dir/dirlink diff --git a/pkg/utils/tarutils/testdata/dir/link b/api/utils/tarutils/testdata/dir/link similarity index 100% rename from pkg/utils/tarutils/testdata/dir/link rename to api/utils/tarutils/testdata/dir/link diff --git a/pkg/utils/tarutils/testdata/dir/regular b/api/utils/tarutils/testdata/dir/regular similarity index 100% rename from pkg/utils/tarutils/testdata/dir/regular rename to api/utils/tarutils/testdata/dir/regular diff --git a/pkg/utils/tarutils/testdata/dir/subdir/file b/api/utils/tarutils/testdata/dir/subdir/file similarity index 100% rename from pkg/utils/tarutils/testdata/dir/subdir/file rename to api/utils/tarutils/testdata/dir/subdir/file diff --git a/pkg/utils/tarutils/testdata/dir2/file2 b/api/utils/tarutils/testdata/dir2/file2 similarity index 100% rename from pkg/utils/tarutils/testdata/dir2/file2 rename to api/utils/tarutils/testdata/dir2/file2 diff --git a/pkg/utils/tarutils/testdata/file b/api/utils/tarutils/testdata/file similarity index 100% rename from pkg/utils/tarutils/testdata/file rename to api/utils/tarutils/testdata/file diff --git a/pkg/utils/template/gotmpl.go b/api/utils/template/gotmpl.go similarity index 100% rename from pkg/utils/template/gotmpl.go rename to api/utils/template/gotmpl.go diff --git a/pkg/utils/template/merge.go b/api/utils/template/merge.go similarity index 100% rename from pkg/utils/template/merge.go rename to api/utils/template/merge.go diff --git a/pkg/utils/template/none.go b/api/utils/template/none.go similarity index 100% rename from pkg/utils/template/none.go rename to api/utils/template/none.go diff --git a/api/utils/template/registry.go b/api/utils/template/registry.go new file mode 100644 index 000000000..44cee9731 --- /dev/null +++ b/api/utils/template/registry.go @@ -0,0 +1,114 @@ +package template + +import ( + "fmt" + "strings" + "sync" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/vfs/pkg/vfs" + + "ocm.software/ocm/api/utils" +) + +const KIND_TEMPLATER = "templater" + +type TemplaterFactory func(system vfs.FileSystem) Templater + +type Registry interface { + Register(name string, fac TemplaterFactory, desc string) + Create(name string, fs vfs.FileSystem) (Templater, error) + Describe(name string) (string, error) + KnownTypeNames() []string +} + +type templaterInfo struct { + templater TemplaterFactory + description string +} + +type registry struct { + lock sync.RWMutex + templaters map[string]templaterInfo +} + +func NewRegistry() Registry { + return ®istry{ + templaters: map[string]templaterInfo{}, + } +} + +func (r *registry) Register(name string, fac TemplaterFactory, desc string) { + r.lock.Lock() + defer r.lock.Unlock() + + r.templaters[name] = templaterInfo{ + templater: fac, + description: desc, + } +} + +func (r *registry) Create(name string, fs vfs.FileSystem) (Templater, error) { + r.lock.RLock() + defer r.lock.RUnlock() + + t, ok := r.templaters[name] + if !ok { + return nil, errors.ErrNotSupported(KIND_TEMPLATER, name) + } + return t.templater(fs), nil +} + +func (r *registry) Describe(name string) (string, error) { + r.lock.RLock() + defer r.lock.RUnlock() + + t, ok := r.templaters[name] + if !ok { + return "", errors.ErrNotSupported(KIND_TEMPLATER, name) + } + return t.description, nil +} + +func (r *registry) KnownTypeNames() []string { + r.lock.RLock() + defer r.lock.RUnlock() + + return utils.StringMapKeys(r.templaters) +} + +func Usage(scheme Registry) string { + s := ` +There are several templaters that can be selected by the --templater option: +` + for _, t := range scheme.KnownTypeNames() { + desc, err := scheme.Describe(t) + if err == nil { + var title string + idx := strings.Index(desc, "\n") + if idx >= 0 { + title = desc[:idx] + desc = desc[idx+1:] + } + if strings.TrimSpace(desc) == "" { + s = fmt.Sprintf("%s- %s %s\n\n", s, t, title) + } else { + s = fmt.Sprintf("%s- %s %s\n\n%s", s, t, title, utils.IndentLines(desc, " ")) + } + if !strings.HasSuffix(s, "\n") { + s += "\n" + } + } + } + return s +} + +var _registry = NewRegistry() + +func Register(name string, fac TemplaterFactory, desc string) { + _registry.Register(name, fac, desc) +} + +func DefaultRegistry() Registry { + return _registry +} diff --git a/pkg/utils/template/spiff.go b/api/utils/template/spiff.go similarity index 100% rename from pkg/utils/template/spiff.go rename to api/utils/template/spiff.go diff --git a/pkg/utils/template/subst.go b/api/utils/template/subst.go similarity index 100% rename from pkg/utils/template/subst.go rename to api/utils/template/subst.go diff --git a/pkg/utils/template/template.go b/api/utils/template/template.go similarity index 97% rename from pkg/utils/template/template.go rename to api/utils/template/template.go index 069bf6daa..399e42b30 100644 --- a/pkg/utils/template/template.go +++ b/api/utils/template/template.go @@ -13,8 +13,8 @@ import ( "github.com/spf13/pflag" "gopkg.in/yaml.v3" - "github.com/open-component-model/ocm/pkg/runtime" - utils2 "github.com/open-component-model/ocm/pkg/utils" + utils2 "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" ) type Values map[string]interface{} diff --git a/pkg/utils/template/template_test.go b/api/utils/template/template_test.go similarity index 99% rename from pkg/utils/template/template_test.go rename to api/utils/template/template_test.go index 5deca5edd..c84233a0b 100644 --- a/pkg/utils/template/template_test.go +++ b/api/utils/template/template_test.go @@ -10,7 +10,7 @@ import ( "github.com/mandelsoft/vfs/pkg/osfs" "gopkg.in/yaml.v3" - "github.com/open-component-model/ocm/pkg/utils/template" + "ocm.software/ocm/api/utils/template" ) func TestConfig(t *testing.T) { diff --git a/pkg/utils/template/testdata/env.values b/api/utils/template/testdata/env.values similarity index 100% rename from pkg/utils/template/testdata/env.values rename to api/utils/template/testdata/env.values diff --git a/pkg/testutils/doc.go b/api/utils/testutils/doc.go similarity index 100% rename from pkg/testutils/doc.go rename to api/utils/testutils/doc.go diff --git a/pkg/testutils/object.go b/api/utils/testutils/object.go similarity index 100% rename from pkg/testutils/object.go rename to api/utils/testutils/object.go diff --git a/pkg/testutils/package.go b/api/utils/testutils/package.go similarity index 100% rename from pkg/testutils/package.go rename to api/utils/testutils/package.go diff --git a/api/utils/testutils/package_test.go b/api/utils/testutils/package_test.go new file mode 100644 index 000000000..13cd3a28b --- /dev/null +++ b/api/utils/testutils/package_test.go @@ -0,0 +1,15 @@ +package testutils_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + me "ocm.software/ocm/api/utils/testutils" +) + +var _ = Describe("package tests", func() { + It("go module name", func() { + mod := me.Must(me.GetModuleName()) + Expect(mod).To(Equal("ocm.software/ocm")) + }) +}) diff --git a/api/utils/testutils/signing_test.go b/api/utils/testutils/signing_test.go new file mode 100644 index 000000000..ad88a136b --- /dev/null +++ b/api/utils/testutils/signing_test.go @@ -0,0 +1,21 @@ +package testutils_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/testutils" +) + +var _ = Describe("normalization", func() { + It("compares with substitution variables", func() { + exp := "A ${TEST}." + res := "A testcase." + vars := common.Properties{ + "TEST": "testcase", + } + Expect(res).To(testutils.StringEqualTrimmedWithContext(exp, common.Properties{}, vars)) + Expect(res).To(testutils.StringEqualTrimmedWithContext(exp, vars, common.Properties{})) + }) +}) diff --git a/pkg/testutils/string.go b/api/utils/testutils/string.go similarity index 100% rename from pkg/testutils/string.go rename to api/utils/testutils/string.go diff --git a/pkg/testutils/suite_test.go b/api/utils/testutils/suite_test.go similarity index 100% rename from pkg/testutils/suite_test.go rename to api/utils/testutils/suite_test.go diff --git a/pkg/testutils/tcp.go b/api/utils/testutils/tcp.go similarity index 100% rename from pkg/testutils/tcp.go rename to api/utils/testutils/tcp.go diff --git a/api/utils/testutils/utils.go b/api/utils/testutils/utils.go new file mode 100644 index 000000000..5fab78670 --- /dev/null +++ b/api/utils/testutils/utils.go @@ -0,0 +1,157 @@ +package testutils + +import ( + "encoding/json" + "fmt" + "io" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/onsi/gomega/types" + + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" +) + +func Close(c io.Closer, msg ...interface{}) { + DeferWithOffset(1, c.Close, msg...) +} + +func Defer(f func() error, msg ...interface{}) { + DeferWithOffset(1, f, msg...) +} + +func DeferWithOffset(o int, f func() error, msg ...interface{}) { + err := f() + if err != nil { + switch len(msg) { + case 0: + ExpectWithOffset(1+o, err).To(Succeed()) + case 1: + Fail(fmt.Sprintf("%s: %s", msg[0], err), 1+o) + default: + Fail(fmt.Sprintf("%s: %s", fmt.Sprintf(msg[0].(string), msg[1:]...), err), 1+o) + } + } +} + +func NotNil[T any](o T, extra ...interface{}) T { + ExpectWithOffset(1, o, extra...).NotTo(BeNil()) + return o +} + +func Must[T any](o T, err error) T { + ExpectWithOffset(1, err).To(Succeed()) + return o +} + +func Must2[T any, V any](a T, b V, err error) (T, V) { + ExpectWithOffset(1, err).To(Succeed()) + return a, b +} + +func Must3[T, U, V any](a T, b U, c V, err error) (T, U, V) { + ExpectWithOffset(1, err).To(Succeed()) + return a, b, c +} + +type result[T any] struct { + res T + err error +} + +func (r result[T]) Must(offset ...int) T { + ExpectWithOffset(utils.Optional(offset...)+1, r.err).To(Succeed()) + return r.res +} + +func R[T any](o T, err error) result[T] { + return Calling(o, err) +} + +func Calling[T any](o T, err error) result[T] { + return result[T]{o, err} +} + +func MustWithOffset[T any](offset int, res result[T]) T { + ExpectWithOffset(offset+1, res.err).To(Succeed()) + return res.res +} + +func MustBeNonNil[T any](o T) T { + ExpectWithOffset(1, o).NotTo(BeNil()) + return o +} + +func MustBeSuccessful(actual ...interface{}) { + if actual[len(actual)-1] == nil { + return + } + err, ok := actual[len(actual)-1].(error) + if !ok { + Fail("no errors return", 1) + } + ExpectWithOffset(1, err).To(Succeed()) +} + +func MustBeSuccessfulWithOffset(offset int, err error) { + ExpectWithOffset(offset+1, err).To(Succeed()) +} + +func MustFailWithMessage(err error, msg string) { + ExpectWithOffset(1, err).To(HaveOccurred()) + ExpectWithOffset(1, err.Error()).To(Equal(msg)) +} + +func ErrorFrom(args ...interface{}) error { + e, ok := args[len(args)-1].(error) + if !ok { + Fail("no errors return", 1) + } + return e +} + +func ExpectError(values ...interface{}) types.Assertion { + return Expect(values[len(values)-1]) +} + +func AsString(actual interface{}) (string, error) { + s, ok := actual.(string) + if !ok { + b, ok := actual.([]byte) + if !ok { + return "", fmt.Errorf("Actual value is no string (or byte array), but a %T.", actual) + } + s = string(b) + } + return s, nil +} + +func AsStructure(actual interface{}, substs ...Substitutions) (interface{}, error) { + var err error + + s, ok := actual.(string) + if !ok { + b, ok := actual.([]byte) + if !ok { + b, err = json.Marshal(actual) + if err != nil { + return "", fmt.Errorf("Actual value (%T) is no string, byte array, or serializable object.", actual) + } + } + s = string(b) + } + if subst := MergeSubst(substs...); len(subst) != 0 { + s, err = eval(s, subst) + if err != nil { + return nil, err + } + } + var value interface{} + err = runtime.DefaultYAMLEncoding.Unmarshal([]byte(s), &value) + if err != nil { + return nil, err + } + return value, nil +} diff --git a/pkg/testutils/yaml.go b/api/utils/testutils/yaml.go similarity index 96% rename from pkg/testutils/yaml.go rename to api/utils/testutils/yaml.go index 8af97b75c..f4ab69bd2 100644 --- a/pkg/testutils/yaml.go +++ b/api/utils/testutils/yaml.go @@ -9,7 +9,7 @@ import ( "github.com/onsi/gomega/format" "github.com/onsi/gomega/types" - "github.com/open-component-model/ocm/pkg/runtime" + "ocm.software/ocm/api/utils/runtime" ) // YAMLEqual compares two yaml structures. diff --git a/pkg/utils/time.go b/api/utils/time.go similarity index 100% rename from pkg/utils/time.go rename to api/utils/time.go diff --git a/pkg/utils/unwrap.go b/api/utils/unwrap.go similarity index 100% rename from pkg/utils/unwrap.go rename to api/utils/unwrap.go diff --git a/pkg/utils/url.go b/api/utils/url.go similarity index 100% rename from pkg/utils/url.go rename to api/utils/url.go diff --git a/api/utils/utils.go b/api/utils/utils.go new file mode 100644 index 000000000..b51a48bce --- /dev/null +++ b/api/utils/utils.go @@ -0,0 +1,357 @@ +package utils + +import ( + "archive/tar" + "bytes" + "compress/gzip" + crypto "crypto/rand" + "encoding/json" + "errors" + "fmt" + "io" + "math/big" + "math/rand" + "net/http" + "os" + "reflect" + "sort" + "strings" + "time" + + "github.com/mandelsoft/vfs/pkg/vfs" + "github.com/modern-go/reflect2" + "github.com/spf13/cobra" + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" + "sigs.k8s.io/yaml" + + ocmlog "ocm.software/ocm/api/utils/logging" +) + +// PrintPrettyYaml prints the given objects as yaml if enabled. +func PrintPrettyYaml(obj interface{}, enabled bool) { + if !enabled { + return + } + + data, err := yaml.Marshal(obj) + if err != nil { + //nolint: forbidigo // Intentional Println to not mess up potential output parsers. + fmt.Println(err) + return + } + + //nolint: forbidigo // Intentional Println. + fmt.Println(string(data)) +} + +// GetFileType returns the mimetype of a file. +func GetFileType(fs vfs.FileSystem, path string) (string, error) { + file, err := fs.Open(path) + if err != nil { + return "", err + } + defer file.Close() + // see http://golang.org/pkg/net/http/#DetectContentType for the 512 bytes + buf := make([]byte, 512) + _, err = file.Read(buf) + if err != nil { + return "", err + } + return http.DetectContentType(buf), nil +} + +// CleanMarkdownUsageFunc removes Markdown tags from the long usage of the command. +// With this func it is possible to generate the Markdown docs but still have readable commandline help func. +// Note: currently only "
" tags are removed.
+func CleanMarkdownUsageFunc(cmd *cobra.Command) {
+	defaultHelpFunc := cmd.HelpFunc()
+	cmd.SetHelpFunc(func(cmd *cobra.Command, s []string) {
+		cmd.Long = strings.ReplaceAll(cmd.Long, "
", "")
+		cmd.Long = strings.ReplaceAll(cmd.Long, "
", "") + defaultHelpFunc(cmd, s) + }) +} + +// RawJSON converts an arbitrary value to json.RawMessage. +func RawJSON(value interface{}) (*json.RawMessage, error) { + jsonval, err := json.Marshal(value) + if err != nil { + return nil, err + } + return (*json.RawMessage)(&jsonval), nil +} + +// Gzip applies gzip compression to an arbitrary byte slice. +func Gzip(data []byte, compressionLevel int) ([]byte, error) { + buf := bytes.NewBuffer([]byte{}) + gzipWriter, err := gzip.NewWriterLevel(buf, compressionLevel) + if err != nil { + return nil, fmt.Errorf("unable to create gzip writer: %w", err) + } + defer gzipWriter.Close() + + if _, err = gzipWriter.Write(data); err != nil { + return nil, fmt.Errorf("unable to write to stream: %w", err) + } + + if err = gzipWriter.Close(); err != nil { + return nil, fmt.Errorf("unable to close writer: %w", err) + } + + return buf.Bytes(), nil +} + +var chars = []rune("abcdefghijklmnopqrstuvwxyz1234567890") + +// RandomString creates a new random string with the given length. +func RandomString(n int) string { + b := make([]rune, n) + for i := range b { + var value int + if v, err := crypto.Int(crypto.Reader, big.NewInt(int64(len(chars)))); err == nil { + value = int(v.Int64()) + } else { + // insecure fallback to provide a valid result + ocmlog.Logger().Error("failed to generate random number", "error", err.Error()) + value = rand.Intn(len(chars)) //nolint: gosec // only used as fallback + } + b[i] = chars[value] + } + return string(b) +} + +// SafeConvert converts a byte slice to string. +// If the byte slice is nil, an empty string is returned. +func SafeConvert(bytes []byte) string { + if bytes == nil { + return "" + } + + return string(bytes) +} + +const ( + BYTE = 1.0 << (10 * iota) + KIBIBYTE + MEBIBYTE + GIBIBYTE +) + +// BytesString converts bytes into a human-readable string. +// This function is inspired by https://www.reddit.com/r/golang/comments/8micn7/review_bytes_to_human_readable_format/ +func BytesString(bytes uint64, accuracy int) string { + unit := "" + value := float32(bytes) + + switch { + case bytes >= GIBIBYTE: + unit = "GiB" + value /= GIBIBYTE + case bytes >= MEBIBYTE: + unit = "MiB" + value /= MEBIBYTE + case bytes >= KIBIBYTE: + unit = "KiB" + value /= KIBIBYTE + case bytes >= BYTE: + unit = "bytes" + case bytes == 0: + return "0" + } + + stringValue := strings.TrimSuffix( + fmt.Sprintf("%.2f", value), "."+strings.Repeat("0", accuracy), + ) + + return fmt.Sprintf("%s %s", stringValue, unit) +} + +// WriteFileToTARArchive writes a new file with name=filename and content=contentReader to archiveWriter. +func WriteFileToTARArchive(filename string, contentReader io.Reader, archiveWriter *tar.Writer) error { + if filename == "" { + return errors.New("filename must not be empty") + } + + if contentReader == nil { + return errors.New("contentReader must not be nil") + } + + if archiveWriter == nil { + return errors.New("archiveWriter must not be nil") + } + + tempfile, err := os.CreateTemp("", "") + if err != nil { + return fmt.Errorf("unable to create tempfile: %w", err) + } + defer func() { + tempfile.Close() + os.Remove(tempfile.Name()) + }() + + fsize, err := io.Copy(tempfile, contentReader) + if err != nil { + return fmt.Errorf("unable to copy content to tempfile: %w", err) + } + + if _, err := tempfile.Seek(0, io.SeekStart); err != nil { + return fmt.Errorf("unable to seek to beginning of tempfile: %w", err) + } + + header := tar.Header{ + Name: filename, + Size: fsize, + Mode: 0o600, + ModTime: time.Now(), + } + + if err := archiveWriter.WriteHeader(&header); err != nil { + return fmt.Errorf("unable to write tar header: %w", err) + } + + if _, err := io.Copy(archiveWriter, tempfile); err != nil { + return fmt.Errorf("unable to write file to tar archive: %w", err) + } + + return nil +} + +func IndentLines(orig string, gap string, skipfirst ...bool) string { + return JoinIndentLines(strings.Split(strings.TrimPrefix(orig, "\n"), "\n"), gap, skipfirst...) +} + +func JoinIndentLines(orig []string, gap string, skipfirst ...bool) string { + if len(orig) == 0 { + return "" + } + skip := false + for _, b := range skipfirst { + skip = skip || b + } + + s := "" + if !skip { + s = gap + } + return s + strings.Join(orig, "\n"+gap) +} + +func StringMapKeys[K ~string, E any](m map[K]E) []K { + if m == nil { + return nil + } + keys := maps.Keys(m) + slices.Sort(keys) + return keys +} + +type Comparable[K any] interface { + Compare(o K) int +} + +func Sort[K Comparable[K]](a []K) { + sort.Slice(a, func(i, j int) bool { return a[i].Compare(a[j]) < 0 }) +} + +func MapKeys[K comparable, E any](m map[K]E) []K { + if m == nil { + return nil + } + + keys := []K{} + for k := range m { + keys = append(keys, k) + } + return keys +} + +type ComparableMapKey[K any] interface { + Comparable[K] + comparable +} + +func SortedMapKeys[K ComparableMapKey[K], E any](m map[K]E) []K { + if m == nil { + return nil + } + + keys := []K{} + for k := range m { + keys = append(keys, k) + } + Sort(keys) + return keys +} + +// Optional returns the first optional non-zero element given as variadic argument, +// if given, or the zero element as default. +func Optional[T any](list ...T) T { + var zero T + for _, e := range list { + if !reflect.DeepEqual(e, zero) { + return e + } + } + return zero +} + +// OptionalDefaulted returns the first optional non-nil element given as variadic +// argument, or the given default element. For value types a given zero +// argument is excepted, also. +func OptionalDefaulted[T any](def T, list ...T) T { + for _, e := range list { + if !reflect2.IsNil(e) { + return e + } + } + return def +} + +// OptionalDefaultedBool checks all args for true. If arg is given +// the given default is returned. +func OptionalDefaultedBool(def bool, list ...bool) bool { + if len(list) == 0 { + return def + } + for _, e := range list { + if e { + return e + } + } + return false +} + +// GetOptionFlag returns the flag value used to set a bool option +// based on optionally specified explicit value(s). +// The default value is to enable the option (true). +func GetOptionFlag(list ...bool) bool { + return OptionalDefaultedBool(true, list...) +} + +func IsNil(o interface{}) bool { + return reflect2.IsNil(o) +} + +// Must expect a result to be provided without error. +func Must[T any](o T, err error) T { + if err != nil { + panic(fmt.Errorf("expected a %T, but got error %w", o, err)) + } + return o +} + +func IgnoreError(_ error) { +} + +func BoolP[T ~bool](b T) *bool { + v := bool(b) + return &v +} + +func AsBool(b *bool, def ...bool) bool { + if b == nil && len(def) > 0 { + return Optional(def...) + } + return b != nil && *b +} diff --git a/api/utils/utils_test.go b/api/utils/utils_test.go new file mode 100644 index 000000000..19af6c8aa --- /dev/null +++ b/api/utils/utils_test.go @@ -0,0 +1,79 @@ +package utils_test + +import ( + "archive/tar" + "bytes" + "io" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/utils" +) + +var _ = Describe("utils", func() { + Context("WriteFileToTARArchive", func() { + It("should write file", func() { + fname := "testfile" + content := []byte("testcontent") + + archiveBuf := bytes.NewBuffer([]byte{}) + tw := tar.NewWriter(archiveBuf) + + Expect(utils.WriteFileToTARArchive(fname, bytes.NewReader(content), tw)).To(Succeed()) + Expect(tw.Close()).To(Succeed()) + + tr := tar.NewReader(archiveBuf) + fheader, err := tr.Next() + Expect(err).ToNot(HaveOccurred()) + Expect(fheader.Name).To(Equal(fname)) + + actualContentBuf := bytes.NewBuffer([]byte{}) + _, err = io.Copy(actualContentBuf, tr) + Expect(err).ToNot(HaveOccurred()) + Expect(actualContentBuf.Bytes()).To(Equal(content)) + + _, err = tr.Next() + Expect(err).To(Equal(io.EOF)) + }) + + It("should write empty file", func() { + fname := "testfile" + + archiveBuf := bytes.NewBuffer([]byte{}) + tw := tar.NewWriter(archiveBuf) + + Expect(utils.WriteFileToTARArchive(fname, bytes.NewReader([]byte{}), tw)).To(Succeed()) + Expect(tw.Close()).To(Succeed()) + + tr := tar.NewReader(archiveBuf) + fheader, err := tr.Next() + Expect(err).ToNot(HaveOccurred()) + Expect(fheader.Name).To(Equal(fname)) + + actualContentBuf := bytes.NewBuffer([]byte{}) + contentLenght, err := io.Copy(actualContentBuf, tr) + Expect(err).ToNot(HaveOccurred()) + Expect(contentLenght).To(Equal(int64(0))) + + _, err = tr.Next() + Expect(err).To(Equal(io.EOF)) + }) + + It("should return error if filename is empty", func() { + tw := tar.NewWriter(bytes.NewBuffer([]byte{})) + contentReader := bytes.NewReader([]byte{}) + Expect(utils.WriteFileToTARArchive("", contentReader, tw)).To(MatchError("filename must not be empty")) + }) + + It("should return error if contentReader is nil", func() { + tw := tar.NewWriter(bytes.NewBuffer([]byte{})) + Expect(utils.WriteFileToTARArchive("testfile", nil, tw)).To(MatchError("contentReader must not be nil")) + }) + + It("should return error if outArchive is nil", func() { + contentReader := bytes.NewReader([]byte{}) + Expect(utils.WriteFileToTARArchive("testfile", contentReader, nil)).To(MatchError("archiveWriter must not be nil")) + }) + }) +}) diff --git a/pkg/utils/validate.go b/api/utils/validate.go similarity index 100% rename from pkg/utils/validate.go rename to api/utils/validate.go diff --git a/pkg/version/generate/release_generate.go b/api/version/generate/release_generate.go similarity index 100% rename from pkg/version/generate/release_generate.go rename to api/version/generate/release_generate.go diff --git a/api/version/version.go b/api/version/version.go new file mode 100644 index 000000000..74ea3d26f --- /dev/null +++ b/api/version/version.go @@ -0,0 +1,116 @@ +package version + +import ( + "fmt" + "runtime" + "strconv" + "strings" + + "github.com/Masterminds/semver/v3" + + "ocm.software/ocm" +) + +var ( + gitVersion = "0.0.0-dev" + gitCommit string + gitTreeState string + buildDate = "1970-01-01T00:00:00Z" +) + +func init() { + if gitVersion == "0.0.0-dev" { + // gitVersion = strings.TrimSpace(string(MustAsset("../../VERSION"))) + gitVersion = strings.TrimSpace(ocm.Version) + } +} + +type Info struct { + Major string `json:"major"` + Minor string `json:"minor"` + Patch string `json:"patch"` + PreRelease string `json:"prerelease"` + Meta string `json:"meta"` + GitVersion string `json:"gitVersion"` + GitCommit string `json:"gitCommit"` + GitTreeState string `json:"gitTreeState"` + BuildDate string `json:"buildDate"` + GoVersion string `json:"goVersion"` + Compiler string `json:"compiler"` + Platform string `json:"platform"` +} + +// String returns info as a human-friendly version string. +func (info Info) String() string { + return info.GitVersion +} + +// String returns info as a short semantic version string (0.8.15). +func (info Info) SemVer() string { + return info.Major + "." + info.Minor + "." + info.Patch +} + +// String returns current Release version. +func Current() string { + return Get().SemVer() +} + +// GetInterface returns the overall codebase version. It's for detecting +// what code a binary was built from. +// These variables typically come from -ldflags settings and in +// their absence fallback to the settings in pkg/version/base.go. +func Get() Info { + var ( + gitMajor string + gitMinor string + gitPatch string = "0" + gitPre string + gitMeta string + ) + + v, err := semver.NewVersion(gitVersion) + if err == nil { + gitMajor = strconv.Itoa(int(v.Major())) + gitMinor = strconv.Itoa(int(v.Minor())) + gitPatch = strconv.Itoa(int(v.Patch())) + gitPre = v.Prerelease() + gitMeta = v.Metadata() + } else { + version := gitVersion + if i := strings.Index(version, "-"); i >= 0 { + gitPre = version[i+1:] + version = version[:i] + } + if i := strings.Index(version, "+"); i >= 0 { + gitMeta = version[i+1:] + version = version[:i] + } + if i := strings.Index(gitPre, "+"); i >= 0 { + gitMeta = gitPre[i+1:] + gitPre = gitPre[:i] + } + v := strings.Split(version, ".") + if len(v) >= 2 { + gitMajor = v[0] + gitMinor = v[1] + if len(v) >= 3 { + gitPatch = v[2] + } + } + } + + return Info{ + Major: gitMajor, + Minor: gitMinor, + Patch: gitPatch, + PreRelease: gitPre, + Meta: gitMeta, + GitVersion: gitVersion, + GitCommit: gitCommit, + GitTreeState: gitTreeState, + BuildDate: buildDate, + GoVersion: runtime.Version(), + Compiler: runtime.Compiler, + Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), + } +} diff --git a/cmds/cliplugin/cmds/check/cmd.go b/cmds/cliplugin/cmds/check/cmd.go index a56de5975..d63969603 100644 --- a/cmds/cliplugin/cmds/check/cmd.go +++ b/cmds/cliplugin/cmds/check/cmd.go @@ -7,13 +7,13 @@ import ( "time" // bind OCM configuration. - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/config" + _ "ocm.software/ocm/api/ocm/plugin/ppi/config" "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/logging" "github.com/spf13/cobra" - "github.com/open-component-model/ocm/pkg/contexts/ocm" + "ocm.software/ocm/api/ocm" ) const Name = "check" diff --git a/cmds/cliplugin/cmds/check/config.go b/cmds/cliplugin/cmds/check/config.go index 7caed81da..457172713 100644 --- a/cmds/cliplugin/cmds/check/config.go +++ b/cmds/cliplugin/cmds/check/config.go @@ -1,8 +1,8 @@ package check import ( - cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/runtime" + cfgcpi "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/utils/runtime" ) const ( diff --git a/cmds/cliplugin/cmds/cmd_test.go b/cmds/cliplugin/cmds/cmd_test.go index 6799f19ba..50671fa3e 100644 --- a/cmds/cliplugin/cmds/cmd_test.go +++ b/cmds/cliplugin/cmds/cmd_test.go @@ -8,14 +8,14 @@ import ( . "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/plugin/testutils" + . "ocm.software/ocm/api/ocm/plugin/testutils" + . "ocm.software/ocm/cmds/ocm/testhelper" "github.com/mandelsoft/logging/logrusl" "github.com/mandelsoft/logging/utils" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugincacheattr" - "github.com/open-component-model/ocm/pkg/version" + "ocm.software/ocm/api/ocm/extensions/attrs/plugincacheattr" + "ocm.software/ocm/api/version" ) const KIND = "rhubarb" diff --git a/cmds/cliplugin/main.go b/cmds/cliplugin/main.go index 4a7f49006..8115bed27 100644 --- a/cmds/cliplugin/main.go +++ b/cmds/cliplugin/main.go @@ -4,13 +4,13 @@ import ( "os" // enable mandelsoft plugin logging configuration. - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/logging" + _ "ocm.software/ocm/api/ocm/plugin/ppi/logging" - "github.com/open-component-model/ocm/cmds/cliplugin/cmds/check" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/clicmd" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds" - "github.com/open-component-model/ocm/pkg/version" + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/ocm/plugin/ppi/clicmd" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds" + "ocm.software/ocm/api/version" + "ocm.software/ocm/cmds/cliplugin/cmds/check" ) func main() { diff --git a/cmds/demoplugin/accessmethods/demo.go b/cmds/demoplugin/accessmethods/demo.go index 83c136c22..07067d092 100644 --- a/cmds/demoplugin/accessmethods/demo.go +++ b/cmds/demoplugin/accessmethods/demo.go @@ -9,15 +9,15 @@ import ( "github.com/mandelsoft/filepath/pkg/filepath" "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/cmds/demoplugin/common" - "github.com/open-component-model/ocm/cmds/demoplugin/config" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/options" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/runtime" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/cmds/demoplugin/common" + "ocm.software/ocm/cmds/demoplugin/config" ) const ( diff --git a/cmds/demoplugin/main.go b/cmds/demoplugin/main.go index a0866c2bf..435dc00e1 100644 --- a/cmds/demoplugin/main.go +++ b/cmds/demoplugin/main.go @@ -3,13 +3,13 @@ package main import ( "os" - "github.com/open-component-model/ocm/cmds/demoplugin/accessmethods" - "github.com/open-component-model/ocm/cmds/demoplugin/config" - "github.com/open-component-model/ocm/cmds/demoplugin/uploaders" - "github.com/open-component-model/ocm/cmds/demoplugin/valuesets" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds" - "github.com/open-component-model/ocm/pkg/version" + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds" + "ocm.software/ocm/api/version" + "ocm.software/ocm/cmds/demoplugin/accessmethods" + "ocm.software/ocm/cmds/demoplugin/config" + "ocm.software/ocm/cmds/demoplugin/uploaders" + "ocm.software/ocm/cmds/demoplugin/valuesets" ) func main() { diff --git a/cmds/demoplugin/uploaders/demo.go b/cmds/demoplugin/uploaders/demo.go index 5e7654c92..ed75ae2a3 100644 --- a/cmds/demoplugin/uploaders/demo.go +++ b/cmds/demoplugin/uploaders/demo.go @@ -9,14 +9,14 @@ import ( "github.com/mandelsoft/filepath/pkg/filepath" "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/cmds/demoplugin/accessmethods" - "github.com/open-component-model/ocm/cmds/demoplugin/common" - "github.com/open-component-model/ocm/cmds/demoplugin/config" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/runtime" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/credentials/cpi" + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/cmds/demoplugin/accessmethods" + "ocm.software/ocm/cmds/demoplugin/common" + "ocm.software/ocm/cmds/demoplugin/config" ) const ( diff --git a/cmds/demoplugin/uploaders/writer.go b/cmds/demoplugin/uploaders/writer.go index 465e73e1e..1d08ea9ec 100644 --- a/cmds/demoplugin/uploaders/writer.go +++ b/cmds/demoplugin/uploaders/writer.go @@ -6,11 +6,11 @@ import ( "github.com/mandelsoft/filepath/pkg/filepath" "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/cmds/demoplugin/accessmethods" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/iotools" - "github.com/open-component-model/ocm/pkg/runtime" + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/utils/iotools" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/cmds/demoplugin/accessmethods" ) type writer = iotools.DigestWriter diff --git a/cmds/demoplugin/valuesets/check.go b/cmds/demoplugin/valuesets/check.go index f16b8129c..0e75372af 100644 --- a/cmds/demoplugin/valuesets/check.go +++ b/cmds/demoplugin/valuesets/check.go @@ -7,12 +7,12 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/goutils/set" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/options" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + "ocm.software/ocm/api/ocm/plugin/descriptor" + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/api/utils/runtime" ) const NAME = "check" diff --git a/cmds/demoplugin/valuesets/check_test.go b/cmds/demoplugin/valuesets/check_test.go index e4a414d8b..c43cc8bcd 100644 --- a/cmds/demoplugin/valuesets/check_test.go +++ b/cmds/demoplugin/valuesets/check_test.go @@ -6,17 +6,17 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/testutils" - . "github.com/open-component-model/ocm/pkg/env" - . "github.com/open-component-model/ocm/pkg/env/builder" + . "ocm.software/ocm/api/helper/builder" + . "ocm.software/ocm/api/helper/env" + . "ocm.software/ocm/api/ocm/plugin/testutils" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugincacheattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip" - "github.com/open-component-model/ocm/pkg/contexts/ocm/registration" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/composition" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" + "ocm.software/ocm/api/ocm/extensions/attrs/plugincacheattr" + "ocm.software/ocm/api/ocm/extensions/labels/routingslip" + "ocm.software/ocm/api/ocm/extensions/repositories/composition" + "ocm.software/ocm/api/ocm/plugin/registration" + "ocm.software/ocm/api/tech/signing/handlers/rsa" ) const ( diff --git a/cmds/ecrplugin/actions/action.go b/cmds/ecrplugin/actions/action.go index 46f8b217d..ca4e0d4c2 100644 --- a/cmds/ecrplugin/actions/action.go +++ b/cmds/ecrplugin/actions/action.go @@ -12,10 +12,10 @@ import ( "github.com/aws/aws-sdk-go-v2/service/ecr/types" "github.com/mandelsoft/goutils/errors" - ocmcreds "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/oci/actions/oci-repository-prepare" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/s3/identity" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" + ocmcreds "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/oci/extensions/actions/oci-repository-prepare" + "ocm.software/ocm/api/ocm/extensions/accessmethods/s3/identity" + "ocm.software/ocm/api/ocm/plugin/ppi" ) type Action struct{} diff --git a/cmds/ecrplugin/config/tweak.go b/cmds/ecrplugin/config/tweak.go index 3c8a30f65..010dc43d5 100644 --- a/cmds/ecrplugin/config/tweak.go +++ b/cmds/ecrplugin/config/tweak.go @@ -1,7 +1,7 @@ package config import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" + "ocm.software/ocm/api/ocm/plugin/ppi" ) func TweakDescriptor(d ppi.Descriptor, cfg *Config) ppi.Descriptor { diff --git a/cmds/ecrplugin/main.go b/cmds/ecrplugin/main.go index 5334e2374..7e4ce1737 100644 --- a/cmds/ecrplugin/main.go +++ b/cmds/ecrplugin/main.go @@ -3,11 +3,11 @@ package main import ( "os" - "github.com/open-component-model/ocm/cmds/ecrplugin/actions" - "github.com/open-component-model/ocm/cmds/ecrplugin/config" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds" - "github.com/open-component-model/ocm/pkg/version" + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds" + "ocm.software/ocm/api/version" + "ocm.software/ocm/cmds/ecrplugin/actions" + "ocm.software/ocm/cmds/ecrplugin/config" ) func main() { diff --git a/cmds/helminstaller/app/app.go b/cmds/helminstaller/app/app.go index 77edd7c21..2c6af5e7e 100644 --- a/cmds/helminstaller/app/app.go +++ b/cmds/helminstaller/app/app.go @@ -3,10 +3,10 @@ package app import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/helminstaller/app/driver" - "github.com/open-component-model/ocm/cmds/helminstaller/app/driver/helm" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/toi/support" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm/tools/toi/support" + "ocm.software/ocm/cmds/helminstaller/app/driver" + "ocm.software/ocm/cmds/helminstaller/app/driver/helm" ) func NewCliCommand(ctx clictx.Context, d driver.Driver) *cobra.Command { diff --git a/cmds/helminstaller/app/config.go b/cmds/helminstaller/app/config.go index c7a0dbf90..b488725f6 100644 --- a/cmds/helminstaller/app/config.go +++ b/cmds/helminstaller/app/config.go @@ -5,8 +5,8 @@ import ( "github.com/mandelsoft/goutils/errors" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils/localize" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/ocmutils/localize" ) type Config struct { diff --git a/cmds/helminstaller/app/driver/helm/driver.go b/cmds/helminstaller/app/driver/helm/driver.go index 2f1f66478..b41f70734 100644 --- a/cmds/helminstaller/app/driver/helm/driver.go +++ b/cmds/helminstaller/app/driver/helm/driver.go @@ -5,7 +5,7 @@ import ( "github.com/mandelsoft/logging" - "github.com/open-component-model/ocm/cmds/helminstaller/app/driver" + "ocm.software/ocm/cmds/helminstaller/app/driver" ) type Driver struct{} diff --git a/cmds/helminstaller/app/execute.go b/cmds/helminstaller/app/execute.go index 2819ca987..0e9e4748b 100644 --- a/cmds/helminstaller/app/execute.go +++ b/cmds/helminstaller/app/execute.go @@ -13,18 +13,18 @@ import ( "github.com/mandelsoft/vfs/pkg/projectionfs" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/cmds/helminstaller/app/driver" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/common/compression" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" - "github.com/open-component-model/ocm/pkg/helm/loader" - "github.com/open-component-model/ocm/pkg/out" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/toi/support" - "github.com/open-component-model/ocm/pkg/utils/tarutils" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/download" + utils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/ocm/tools/toi/support" + "ocm.software/ocm/api/tech/helm/loader" + "ocm.software/ocm/api/utils/compression" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/api/utils/tarutils" + "ocm.software/ocm/cmds/helminstaller/app/driver" ) func Merge(values ...map[string]interface{}) map[string]interface{} { diff --git a/cmds/helminstaller/app/executor.go b/cmds/helminstaller/app/executor.go index e57479079..7cd9d670b 100644 --- a/cmds/helminstaller/app/executor.go +++ b/cmds/helminstaller/app/executor.go @@ -4,9 +4,9 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/vfs/pkg/osfs" - "github.com/open-component-model/ocm/cmds/helminstaller/app/driver" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/toi/support" + "ocm.software/ocm/api/ocm/tools/toi/support" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/cmds/helminstaller/app/driver" ) func New(d driver.Driver) func(o *support.ExecutorOptions) error { diff --git a/cmds/helminstaller/main.go b/cmds/helminstaller/main.go index 47f4dd909..6a4a759bc 100644 --- a/cmds/helminstaller/main.go +++ b/cmds/helminstaller/main.go @@ -3,8 +3,8 @@ package main import ( "os" - "github.com/open-component-model/ocm/cmds/helminstaller/app" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/helminstaller/app" ) func main() { diff --git a/cmds/helminstaller/testhelper/env.go b/cmds/helminstaller/testhelper/env.go index f91613618..e975072b8 100644 --- a/cmds/helminstaller/testhelper/env.go +++ b/cmds/helminstaller/testhelper/env.go @@ -5,17 +5,17 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/cmds/helminstaller/app" - "github.com/open-component-model/ocm/cmds/helminstaller/app/driver" - "github.com/open-component-model/ocm/cmds/helminstaller/app/driver/helm" - "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/config" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/env" - "github.com/open-component-model/ocm/pkg/env/builder" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/helper/builder" + "ocm.software/ocm/api/helper/env" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/cmds/helminstaller/app" + "ocm.software/ocm/cmds/helminstaller/app/driver" + "ocm.software/ocm/cmds/helminstaller/app/driver/helm" ) type CLI struct { diff --git a/cmds/ocm/app/README.md b/cmds/ocm/app/README.md index ec4118045..1e0153b3d 100644 --- a/cmds/ocm/app/README.md +++ b/cmds/ocm/app/README.md @@ -6,7 +6,7 @@ The command line tool can be configured by a configuration file. If not specified on the command line, the file `~/.ocmconfig` is read. The configuration file is a yaml file following format by the -[configuration context](../../../pkg/contexts/config/README.md). +[configuration context](../../../api/config/README.md). It consists of list of configuration specifications according to the registered configurations types provided by the used library. diff --git a/cmds/ocm/app/app.go b/cmds/ocm/app/app.go index c32383889..d3dc0ccdb 100644 --- a/cmds/ocm/app/app.go +++ b/cmds/ocm/app/app.go @@ -7,71 +7,71 @@ import ( "strings" "unicode" - _ "github.com/open-component-model/ocm/pkg/contexts/clictx/config" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs" + _ "ocm.software/ocm/api/cli/config" + _ "ocm.software/ocm/api/ocm/extensions/attrs" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/pflag" - config2 "github.com/open-component-model/ocm/cmds/ocm/clippi/config" - "github.com/open-component-model/ocm/cmds/ocm/commands/cachecmds" - "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/keyoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/misccmds/action" - creds "github.com/open-component-model/ocm/cmds/ocm/commands/misccmds/credentials" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/componentarchive" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/components" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/plugins" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/pubsub" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/references" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/resources" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/routingslips" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/sources" - "github.com/open-component-model/ocm/cmds/ocm/commands/plugin" - "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs/add" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs/bootstrap" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs/check" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs/clean" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs/controller" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs/create" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs/describe" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs/download" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs/execute" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs/get" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs/hash" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs/install" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs/list" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs/set" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs/show" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs/sign" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs/transfer" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs/verify" - cmdutils "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/cmds/ocm/topics/common/attributes" - topicconfig "github.com/open-component-model/ocm/cmds/ocm/topics/common/config" - topiccredentials "github.com/open-component-model/ocm/cmds/ocm/topics/common/credentials" - topiclogging "github.com/open-component-model/ocm/cmds/ocm/topics/common/logging" - topicocirefs "github.com/open-component-model/ocm/cmds/ocm/topics/oci/refs" - topicocmaccessmethods "github.com/open-component-model/ocm/cmds/ocm/topics/ocm/accessmethods" - topicocmdownloaders "github.com/open-component-model/ocm/cmds/ocm/topics/ocm/downloadhandlers" - topicocmlabels "github.com/open-component-model/ocm/cmds/ocm/topics/ocm/labels" - topicocmpubsub "github.com/open-component-model/ocm/cmds/ocm/topics/ocm/pubsub" - topicocmrefs "github.com/open-component-model/ocm/cmds/ocm/topics/ocm/refs" - topicocmuploaders "github.com/open-component-model/ocm/cmds/ocm/topics/ocm/uploadhandlers" - topicbootstrap "github.com/open-component-model/ocm/cmds/ocm/topics/toi/bootstrapping" - "github.com/open-component-model/ocm/pkg/cobrautils" - "github.com/open-component-model/ocm/pkg/cobrautils/logopts" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/clicfgattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugincacheattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/registration" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils/defaultconfigregistry" - "github.com/open-component-model/ocm/pkg/out" - "github.com/open-component-model/ocm/pkg/version" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/datacontext/attrs/clicfgattr" + "ocm.software/ocm/api/ocm/extensions/attrs/plugincacheattr" + "ocm.software/ocm/api/ocm/ocmutils/defaultconfigregistry" + "ocm.software/ocm/api/ocm/plugin/registration" + "ocm.software/ocm/api/utils/cobrautils" + "ocm.software/ocm/api/utils/cobrautils/logopts" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/api/version" + config2 "ocm.software/ocm/cmds/ocm/clippi/config" + "ocm.software/ocm/cmds/ocm/commands/cachecmds" + "ocm.software/ocm/cmds/ocm/commands/common/options/keyoption" + "ocm.software/ocm/cmds/ocm/commands/misccmds/action" + creds "ocm.software/ocm/cmds/ocm/commands/misccmds/credentials" + "ocm.software/ocm/cmds/ocm/commands/ocicmds" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/componentarchive" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/components" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/plugins" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/pubsub" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/references" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/resources" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/routingslips" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/sources" + "ocm.software/ocm/cmds/ocm/commands/plugin" + "ocm.software/ocm/cmds/ocm/commands/toicmds" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/commands/verbs/add" + "ocm.software/ocm/cmds/ocm/commands/verbs/bootstrap" + "ocm.software/ocm/cmds/ocm/commands/verbs/check" + "ocm.software/ocm/cmds/ocm/commands/verbs/clean" + "ocm.software/ocm/cmds/ocm/commands/verbs/controller" + "ocm.software/ocm/cmds/ocm/commands/verbs/create" + "ocm.software/ocm/cmds/ocm/commands/verbs/describe" + "ocm.software/ocm/cmds/ocm/commands/verbs/download" + "ocm.software/ocm/cmds/ocm/commands/verbs/execute" + "ocm.software/ocm/cmds/ocm/commands/verbs/get" + "ocm.software/ocm/cmds/ocm/commands/verbs/hash" + "ocm.software/ocm/cmds/ocm/commands/verbs/install" + "ocm.software/ocm/cmds/ocm/commands/verbs/list" + "ocm.software/ocm/cmds/ocm/commands/verbs/set" + "ocm.software/ocm/cmds/ocm/commands/verbs/show" + "ocm.software/ocm/cmds/ocm/commands/verbs/sign" + "ocm.software/ocm/cmds/ocm/commands/verbs/transfer" + "ocm.software/ocm/cmds/ocm/commands/verbs/verify" + cmdutils "ocm.software/ocm/cmds/ocm/common/utils" + "ocm.software/ocm/cmds/ocm/topics/common/attributes" + topicconfig "ocm.software/ocm/cmds/ocm/topics/common/config" + topiccredentials "ocm.software/ocm/cmds/ocm/topics/common/credentials" + topiclogging "ocm.software/ocm/cmds/ocm/topics/common/logging" + topicocirefs "ocm.software/ocm/cmds/ocm/topics/oci/refs" + topicocmaccessmethods "ocm.software/ocm/cmds/ocm/topics/ocm/accessmethods" + topicocmdownloaders "ocm.software/ocm/cmds/ocm/topics/ocm/downloadhandlers" + topicocmlabels "ocm.software/ocm/cmds/ocm/topics/ocm/labels" + topicocmpubsub "ocm.software/ocm/cmds/ocm/topics/ocm/pubsub" + topicocmrefs "ocm.software/ocm/cmds/ocm/topics/ocm/refs" + topicocmuploaders "ocm.software/ocm/cmds/ocm/topics/ocm/uploadhandlers" + topicbootstrap "ocm.software/ocm/cmds/ocm/topics/toi/bootstrapping" ) type CLIOptions struct { diff --git a/cmds/ocm/app/app_test.go b/cmds/ocm/app/app_test.go index 088c1b5cc..318972c6d 100644 --- a/cmds/ocm/app/app_test.go +++ b/cmds/ocm/app/app_test.go @@ -9,17 +9,17 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" "github.com/mandelsoft/logging" "github.com/mandelsoft/vfs/pkg/vfs" "github.com/spf13/cobra" "github.com/tonglil/buflogr" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/mapocirepoattr" - ocmlog "github.com/open-component-model/ocm/pkg/logging" - "github.com/open-component-model/ocm/pkg/logging/testhelper" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm/extensions/attrs/mapocirepoattr" + ocmlog "ocm.software/ocm/api/utils/logging" + "ocm.software/ocm/api/utils/logging/testhelper" ) var realm = logging.NewRealm("test") diff --git a/cmds/ocm/app/prepare.go b/cmds/ocm/app/prepare.go index 4bbbc1b36..e995efa66 100644 --- a/cmds/ocm/app/prepare.go +++ b/cmds/ocm/app/prepare.go @@ -5,7 +5,7 @@ import ( "github.com/spf13/pflag" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" ) // Prepare pre-prepares CLI options by evaluation the main options diff --git a/cmds/ocm/clippi/config/evaluated.go b/cmds/ocm/clippi/config/evaluated.go index ba4dabeda..e8939308c 100644 --- a/cmds/ocm/clippi/config/evaluated.go +++ b/cmds/ocm/clippi/config/evaluated.go @@ -1,9 +1,9 @@ package config import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/keyoption" - "github.com/open-component-model/ocm/pkg/cobrautils/logopts" - "github.com/open-component-model/ocm/pkg/contexts/config" + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/utils/cobrautils/logopts" + "ocm.software/ocm/cmds/ocm/commands/common/options/keyoption" ) type EvaluatedOptions struct { diff --git a/cmds/ocm/clippi/config/type.go b/cmds/ocm/clippi/config/type.go index bfdeac596..ac31b7dce 100644 --- a/cmds/ocm/clippi/config/type.go +++ b/cmds/ocm/clippi/config/type.go @@ -6,25 +6,25 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/keyoption" - common2 "github.com/open-component-model/ocm/pkg/clisupport" - "github.com/open-component-model/ocm/pkg/cobrautils/logopts" - logdata "github.com/open-component-model/ocm/pkg/cobrautils/logopts/logging" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/config" - config2 "github.com/open-component-model/ocm/pkg/contexts/config/config" - cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "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/vfsattr" - datacfg "github.com/open-component-model/ocm/pkg/contexts/datacontext/config/attrs" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" - "github.com/open-component-model/ocm/pkg/logging" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/signing" + "ocm.software/ocm/api/config" + cfgcpi "ocm.software/ocm/api/config/cpi" + config2 "ocm.software/ocm/api/config/extensions/config" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + datacfg "ocm.software/ocm/api/datacontext/config/attrs" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" + utils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/tech/signing" + common2 "ocm.software/ocm/api/utils/clisupport" + "ocm.software/ocm/api/utils/cobrautils/logopts" + logdata "ocm.software/ocm/api/utils/cobrautils/logopts/logging" + "ocm.software/ocm/api/utils/logging" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/cmds/ocm/commands/common/options/keyoption" ) const ( diff --git a/cmds/ocm/commands/cachecmds/clean/cmd.go b/cmds/ocm/commands/cachecmds/clean/cmd.go index 6fefebc66..30f44265b 100644 --- a/cmds/ocm/commands/cachecmds/clean/cmd.go +++ b/cmds/ocm/commands/cachecmds/clean/cmd.go @@ -8,15 +8,15 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/commands/cachecmds/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/common" - "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/oci/attrs/cacheattr" - "github.com/open-component-model/ocm/pkg/out" - utils2 "github.com/open-component-model/ocm/pkg/utils" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/oci/extensions/attrs/cacheattr" + utils2 "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/accessio" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/cmds/ocm/commands/cachecmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/cachecmds/cmd.go b/cmds/ocm/commands/cachecmds/cmd.go index 53bc85722..0cd5e2609 100644 --- a/cmds/ocm/commands/cachecmds/cmd.go +++ b/cmds/ocm/commands/cachecmds/cmd.go @@ -3,10 +3,10 @@ package cachecmds import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/cachecmds/clean" - "github.com/open-component-model/ocm/cmds/ocm/commands/cachecmds/describe" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/cachecmds/clean" + "ocm.software/ocm/cmds/ocm/commands/cachecmds/describe" + "ocm.software/ocm/cmds/ocm/common/utils" ) // NewCommand creates a new cache command. diff --git a/cmds/ocm/commands/cachecmds/describe/cmd.go b/cmds/ocm/commands/cachecmds/describe/cmd.go index 8d595bb0e..4a10cc37d 100644 --- a/cmds/ocm/commands/cachecmds/describe/cmd.go +++ b/cmds/ocm/commands/cachecmds/describe/cmd.go @@ -4,13 +4,13 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/cachecmds/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/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/contexts/oci/attrs/cacheattr" - "github.com/open-component-model/ocm/pkg/out" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/oci/extensions/attrs/cacheattr" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/cmds/ocm/commands/cachecmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/common/elements/components/cmd.go b/cmds/ocm/commands/common/elements/components/cmd.go index 577c6f3e3..42d178ea1 100644 --- a/cmds/ocm/commands/common/elements/components/cmd.go +++ b/cmds/ocm/commands/common/elements/components/cmd.go @@ -3,11 +3,11 @@ package components import ( "github.com/spf13/cobra" - ocmcomp "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/components" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - toicomp "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds/package" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + ocmcomp "ocm.software/ocm/cmds/ocm/commands/ocmcmds/components" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + toicomp "ocm.software/ocm/cmds/ocm/commands/toicmds/package" + "ocm.software/ocm/cmds/ocm/common/utils" ) var Names = names.Components diff --git a/cmds/ocm/commands/common/options/closureoption/option.go b/cmds/ocm/commands/common/options/closureoption/option.go index ef148e4c9..9b6f23a06 100644 --- a/cmds/ocm/commands/common/options/closureoption/option.go +++ b/cmds/ocm/commands/common/options/closureoption/option.go @@ -6,14 +6,14 @@ import ( "github.com/modern-go/reflect2" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" - "github.com/open-component-model/ocm/pkg/cobrautils/flag" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/cobrautils/flag" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/cmds/ocm/common/options" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/processing" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/common/options/destoption/option.go b/cmds/ocm/commands/common/options/destoption/option.go index 4f7ad402e..6ff96faf8 100644 --- a/cmds/ocm/commands/common/options/destoption/option.go +++ b/cmds/ocm/commands/common/options/destoption/option.go @@ -4,8 +4,8 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/common/options/failonerroroption/option.go b/cmds/ocm/commands/common/options/failonerroroption/option.go index 5b322ada8..c49a766c9 100644 --- a/cmds/ocm/commands/common/options/failonerroroption/option.go +++ b/cmds/ocm/commands/common/options/failonerroroption/option.go @@ -4,7 +4,7 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/common/options/formatoption/option.go b/cmds/ocm/commands/common/options/formatoption/option.go index 8e552b32f..afb862242 100644 --- a/cmds/ocm/commands/common/options/formatoption/option.go +++ b/cmds/ocm/commands/common/options/formatoption/option.go @@ -8,10 +8,10 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "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/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/common/options/keyoption/config.go b/cmds/ocm/commands/common/options/keyoption/config.go index 72ea17d46..d1ead6dda 100644 --- a/cmds/ocm/commands/common/options/keyoption/config.go +++ b/cmds/ocm/commands/common/options/keyoption/config.go @@ -9,12 +9,12 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/vfsattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/signutils" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/signutils" + "ocm.software/ocm/api/utils" ) type ConfigFragment struct { diff --git a/cmds/ocm/commands/common/options/keyoption/option.go b/cmds/ocm/commands/common/options/keyoption/option.go index 28f3d6711..37c37e2f4 100644 --- a/cmds/ocm/commands/common/options/keyoption/option.go +++ b/cmds/ocm/commands/common/options/keyoption/option.go @@ -3,11 +3,11 @@ package keyoption import ( "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - ocmsign "github.com/open-component-model/ocm/pkg/contexts/ocm/signing" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/signutils" + "ocm.software/ocm/api/ocm" + ocmsign "ocm.software/ocm/api/ocm/tools/signing" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/signutils" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/controllercmds/common/log.go b/cmds/ocm/commands/controllercmds/common/log.go index 7793b26cd..3e836a06b 100644 --- a/cmds/ocm/commands/controllercmds/common/log.go +++ b/cmds/ocm/commands/controllercmds/common/log.go @@ -3,7 +3,7 @@ package common import ( "fmt" - "github.com/open-component-model/ocm/pkg/out" + "ocm.software/ocm/api/utils/out" ) func Outf(ctx out.Context, dryRun bool, msg string, args ...any) (int, error) { diff --git a/cmds/ocm/commands/controllercmds/common/manifests.go b/cmds/ocm/commands/controllercmds/common/manifests.go index c54bc5a7c..18eb435b6 100644 --- a/cmds/ocm/commands/controllercmds/common/manifests.go +++ b/cmds/ocm/commands/controllercmds/common/manifests.go @@ -9,8 +9,8 @@ import ( "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" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/utils/out" ) 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 25427a1ba..e888beb4f 100644 --- a/cmds/ocm/commands/controllercmds/install/cmd.go +++ b/cmds/ocm/commands/controllercmds/install/cmd.go @@ -14,11 +14,11 @@ 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" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/controllercmds/common" + "ocm.software/ocm/cmds/ocm/commands/controllercmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( @@ -60,7 +60,7 @@ func (o *Command) ForName(name string) *cobra.Command { func (o *Command) AddFlags(set *pflag.FlagSet) { set.StringVarP(&o.Version, "version", "v", "latest", "the version of the controller to install") - set.StringVarP(&o.BaseURL, "base-url", "u", "https://github.com/open-component-model/ocm-controller/releases", "the base url to the ocm-controller's release page") + set.StringVarP(&o.BaseURL, "base-url", "u", "https://ocm.software/ocm-controller/releases", "the base url to the ocm-controller's release page") set.StringVarP(&o.ReleaseAPIURL, "release-api-url", "a", "https://api.github.com/repos/open-component-model/ocm-controller/releases", "the base url to the ocm-controller's API release page") set.StringVar(&o.CertManagerBaseURL, "cert-manager-base-url", "https://github.com/cert-manager/cert-manager/releases", "the base url to the cert-manager's release page") set.StringVar(&o.CertManagerReleaseAPIURL, "cert-manager-release-api-url", "https://api.github.com/repos/cert-manager/cert-manager/releases", "the base url to the cert-manager's API release page") diff --git a/cmds/ocm/commands/controllercmds/install/cmd_test.go b/cmds/ocm/commands/controllercmds/install/cmd_test.go index eb0f64a3b..e0362b2b6 100644 --- a/cmds/ocm/commands/controllercmds/install/cmd_test.go +++ b/cmds/ocm/commands/controllercmds/install/cmd_test.go @@ -11,7 +11,7 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" "github.com/mandelsoft/filepath/pkg/filepath" ) diff --git a/cmds/ocm/commands/controllercmds/install/install_cert_manager.go b/cmds/ocm/commands/controllercmds/install/install_cert_manager.go index 089ec79fc..c2afed126 100644 --- a/cmds/ocm/commands/controllercmds/install/install_cert_manager.go +++ b/cmds/ocm/commands/controllercmds/install/install_cert_manager.go @@ -10,7 +10,7 @@ import ( "github.com/fluxcd/pkg/ssa" "github.com/mandelsoft/filepath/pkg/filepath" - "github.com/open-component-model/ocm/cmds/ocm/commands/controllercmds/common" + "ocm.software/ocm/cmds/ocm/commands/controllercmds/common" ) //go:embed issuer/registry_certificate.yaml diff --git a/cmds/ocm/commands/controllercmds/uninstall/cmd.go b/cmds/ocm/commands/controllercmds/uninstall/cmd.go index 47c06530c..4261b2277 100644 --- a/cmds/ocm/commands/controllercmds/uninstall/cmd.go +++ b/cmds/ocm/commands/controllercmds/uninstall/cmd.go @@ -10,12 +10,12 @@ import ( "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" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/cmds/ocm/commands/controllercmds/common" + "ocm.software/ocm/cmds/ocm/commands/controllercmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( @@ -57,7 +57,7 @@ func (o *Command) ForName(name string) *cobra.Command { // AddFlags for the known item to delete. func (o *Command) AddFlags(set *pflag.FlagSet) { set.StringVarP(&o.Version, "version", "v", "latest", "the version of the controller to install") - set.StringVarP(&o.BaseURL, "base-url", "u", "https://github.com/open-component-model/ocm-controller/releases", "the base url to the ocm-controller's release page") + set.StringVarP(&o.BaseURL, "base-url", "u", "https://ocm.software/ocm-controller/releases", "the base url to the ocm-controller's release page") set.StringVarP(&o.ReleaseAPIURL, "release-api-url", "a", "https://api.github.com/repos/open-component-model/ocm-controller/releases", "the base url to the ocm-controller's API release page") set.StringVar(&o.CertManagerBaseURL, "cert-manager-base-url", "https://github.com/cert-manager/cert-manager/releases", "the base url to the cert-manager's release page") set.StringVar(&o.CertManagerReleaseAPIURL, "cert-manager-release-api-url", "https://api.github.com/repos/cert-manager/cert-manager/releases", "the base url to the cert-manager's API release page") diff --git a/cmds/ocm/commands/controllercmds/uninstall/cmd_test.go b/cmds/ocm/commands/controllercmds/uninstall/cmd_test.go index 878655c51..f20036ecd 100644 --- a/cmds/ocm/commands/controllercmds/uninstall/cmd_test.go +++ b/cmds/ocm/commands/controllercmds/uninstall/cmd_test.go @@ -11,7 +11,7 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" "github.com/mandelsoft/filepath/pkg/filepath" ) diff --git a/cmds/ocm/commands/controllercmds/uninstall/uninstall_cert_manager.go b/cmds/ocm/commands/controllercmds/uninstall/uninstall_cert_manager.go index e0380e264..a06f0d463 100644 --- a/cmds/ocm/commands/controllercmds/uninstall/uninstall_cert_manager.go +++ b/cmds/ocm/commands/controllercmds/uninstall/uninstall_cert_manager.go @@ -10,8 +10,8 @@ import ( "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" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/cmds/ocm/commands/controllercmds/common" ) //go:embed issuer/registry_certificate.yaml diff --git a/cmds/ocm/commands/misccmds/action/cmd.go b/cmds/ocm/commands/misccmds/action/cmd.go index 2419e61cc..76e57304b 100644 --- a/cmds/ocm/commands/misccmds/action/cmd.go +++ b/cmds/ocm/commands/misccmds/action/cmd.go @@ -3,10 +3,10 @@ package action import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/misccmds/action/execute" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/misccmds/action/execute" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/common/utils" ) var Names = names.Action diff --git a/cmds/ocm/commands/misccmds/action/execute/cmd.go b/cmds/ocm/commands/misccmds/action/execute/cmd.go index 4e2b23e97..98cbb3f98 100644 --- a/cmds/ocm/commands/misccmds/action/execute/cmd.go +++ b/cmds/ocm/commands/misccmds/action/execute/cmd.go @@ -8,16 +8,16 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/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/common" - "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/contexts/datacontext/action" - "github.com/open-component-model/ocm/pkg/out" - "github.com/open-component-model/ocm/pkg/runtime" - utils2 "github.com/open-component-model/ocm/pkg/utils" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext/action" + utils2 "ocm.software/ocm/api/utils" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/misccmds/config/cmd.go b/cmds/ocm/commands/misccmds/config/cmd.go index d50ff7b3e..2d925cfe6 100644 --- a/cmds/ocm/commands/misccmds/config/cmd.go +++ b/cmds/ocm/commands/misccmds/config/cmd.go @@ -3,10 +3,10 @@ package credentials import ( "github.com/spf13/cobra" - config "github.com/open-component-model/ocm/cmds/ocm/commands/misccmds/config/get" - "github.com/open-component-model/ocm/cmds/ocm/commands/misccmds/names" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + config "ocm.software/ocm/cmds/ocm/commands/misccmds/config/get" + "ocm.software/ocm/cmds/ocm/commands/misccmds/names" + "ocm.software/ocm/cmds/ocm/common/utils" ) var Names = names.Config diff --git a/cmds/ocm/commands/misccmds/config/get/cmd.go b/cmds/ocm/commands/misccmds/config/get/cmd.go index 94af2d854..625c04e94 100644 --- a/cmds/ocm/commands/misccmds/config/get/cmd.go +++ b/cmds/ocm/commands/misccmds/config/get/cmd.go @@ -6,14 +6,14 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/destoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/misccmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "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" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/clicfgattr" - "github.com/open-component-model/ocm/pkg/out" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/datacontext/attrs/clicfgattr" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/cmds/ocm/commands/common/options/destoption" + "ocm.software/ocm/cmds/ocm/commands/misccmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/misccmds/config/get/cmd_test.go b/cmds/ocm/commands/misccmds/config/get/cmd_test.go index 68da28556..c03d73f34 100644 --- a/cmds/ocm/commands/misccmds/config/get/cmd_test.go +++ b/cmds/ocm/commands/misccmds/config/get/cmd_test.go @@ -7,7 +7,7 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" "github.com/mandelsoft/vfs/pkg/vfs" ) diff --git a/cmds/ocm/commands/misccmds/credentials/cmd.go b/cmds/ocm/commands/misccmds/credentials/cmd.go index 29dbaa1e7..9ad20d7da 100644 --- a/cmds/ocm/commands/misccmds/credentials/cmd.go +++ b/cmds/ocm/commands/misccmds/credentials/cmd.go @@ -3,10 +3,10 @@ package credentials import ( "github.com/spf13/cobra" - credentials "github.com/open-component-model/ocm/cmds/ocm/commands/misccmds/credentials/get" - "github.com/open-component-model/ocm/cmds/ocm/commands/misccmds/names" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + credentials "ocm.software/ocm/cmds/ocm/commands/misccmds/credentials/get" + "ocm.software/ocm/cmds/ocm/commands/misccmds/names" + "ocm.software/ocm/cmds/ocm/common/utils" ) var Names = names.Credentials diff --git a/cmds/ocm/commands/misccmds/credentials/get/cmd.go b/cmds/ocm/commands/misccmds/credentials/get/cmd.go index a634692cb..881e0254d 100644 --- a/cmds/ocm/commands/misccmds/credentials/get/cmd.go +++ b/cmds/ocm/commands/misccmds/credentials/get/cmd.go @@ -8,14 +8,14 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/commands/misccmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "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" - "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" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/utils/listformat" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/cmds/ocm/commands/misccmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/misccmds/credentials/get/cmd_test.go b/cmds/ocm/commands/misccmds/credentials/get/cmd_test.go index 7f44b3666..74dd49640 100644 --- a/cmds/ocm/commands/misccmds/credentials/get/cmd_test.go +++ b/cmds/ocm/commands/misccmds/credentials/get/cmd_test.go @@ -6,11 +6,11 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/credentials/cpi" ) var _ = Describe("Test Environment", func() { diff --git a/cmds/ocm/commands/misccmds/hash/cmd.go b/cmds/ocm/commands/misccmds/hash/cmd.go index f4aca12d3..31893730b 100644 --- a/cmds/ocm/commands/misccmds/hash/cmd.go +++ b/cmds/ocm/commands/misccmds/hash/cmd.go @@ -3,10 +3,10 @@ package credentials import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/misccmds/hash/sign" - "github.com/open-component-model/ocm/cmds/ocm/commands/misccmds/names" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/misccmds/hash/sign" + "ocm.software/ocm/cmds/ocm/commands/misccmds/names" + "ocm.software/ocm/cmds/ocm/common/utils" ) var Names = names.Hash diff --git a/cmds/ocm/commands/misccmds/hash/sign/cmd.go b/cmds/ocm/commands/misccmds/hash/sign/cmd.go index d2ee64f48..86b1c65b5 100644 --- a/cmds/ocm/commands/misccmds/hash/sign/cmd.go +++ b/cmds/ocm/commands/misccmds/hash/sign/cmd.go @@ -10,17 +10,17 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/commands/misccmds/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/contexts/ocm/attrs/signingattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - "github.com/open-component-model/ocm/pkg/out" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" - "github.com/open-component-model/ocm/pkg/signing/signutils" - utils2 "github.com/open-component-model/ocm/pkg/utils" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/api/tech/signing/signutils" + utils2 "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/cmds/ocm/commands/misccmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/misccmds/hash/sign/cmd_test.go b/cmds/ocm/commands/misccmds/hash/sign/cmd_test.go index 07ece2ede..3291641fe 100644 --- a/cmds/ocm/commands/misccmds/hash/sign/cmd_test.go +++ b/cmds/ocm/commands/misccmds/hash/sign/cmd_test.go @@ -6,9 +6,9 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" - "github.com/open-component-model/ocm/pkg/contexts/ocm/signing/signingtest" + "ocm.software/ocm/api/ocm/tools/signing/signingtest" ) const ISSUER = "mandelsoft" diff --git a/cmds/ocm/commands/misccmds/rsakeypair/cmd.go b/cmds/ocm/commands/misccmds/rsakeypair/cmd.go index cbbfb0f29..c819fe2f7 100644 --- a/cmds/ocm/commands/misccmds/rsakeypair/cmd.go +++ b/cmds/ocm/commands/misccmds/rsakeypair/cmd.go @@ -14,20 +14,20 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/commands/misccmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" - "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/cobrautils/flag" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/rootcertsattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" - "github.com/open-component-model/ocm/pkg/encrypt" - "github.com/open-component-model/ocm/pkg/out" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" - "github.com/open-component-model/ocm/pkg/signing/signutils" - utils2 "github.com/open-component-model/ocm/pkg/utils" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/datacontext/attrs/rootcertsattr" + "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/api/tech/signing/signutils" + utils2 "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/cobrautils/flag" + "ocm.software/ocm/api/utils/encrypt" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/cmds/ocm/commands/misccmds/names" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/misccmds/rsakeypair/cmd_test.go b/cmds/ocm/commands/misccmds/rsakeypair/cmd_test.go index abd3e64d3..064c5f4b4 100644 --- a/cmds/ocm/commands/misccmds/rsakeypair/cmd_test.go +++ b/cmds/ocm/commands/misccmds/rsakeypair/cmd_test.go @@ -8,16 +8,16 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" "github.com/opencontainers/go-digest" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" - "github.com/open-component-model/ocm/pkg/encrypt" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" - "github.com/open-component-model/ocm/pkg/signing/signutils" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/api/tech/signing/signutils" + "ocm.software/ocm/api/utils/encrypt" ) var ISSUER = &pkix.Name{CommonName: "mandelsoft"} diff --git a/cmds/ocm/commands/ocicmds/artifacts/cmd.go b/cmds/ocm/commands/ocicmds/artifacts/cmd.go index a1579d7dc..79fce2381 100644 --- a/cmds/ocm/commands/ocicmds/artifacts/cmd.go +++ b/cmds/ocm/commands/ocicmds/artifacts/cmd.go @@ -3,13 +3,13 @@ package artifacts import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/artifacts/describe" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/artifacts/download" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/artifacts/get" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/artifacts/transfer" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/names" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/artifacts/describe" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/artifacts/download" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/artifacts/get" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/artifacts/transfer" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/names" + "ocm.software/ocm/cmds/ocm/common/utils" ) var Names = names.Artifacts diff --git a/cmds/ocm/commands/ocicmds/artifacts/describe/cmd.go b/cmds/ocm/commands/ocicmds/artifacts/describe/cmd.go index 7627d4bc4..c063a910d 100644 --- a/cmds/ocm/commands/ocicmds/artifacts/describe/cmd.go +++ b/cmds/ocm/commands/ocicmds/artifacts/describe/cmd.go @@ -7,19 +7,19 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/common/options/repooption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - common2 "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/ociutils" - "github.com/open-component-model/ocm/pkg/out" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/ociutils" + common2 "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/processing" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocicmds/artifacts/download/cmd.go b/cmds/ocm/commands/ocicmds/artifacts/download/cmd.go index d7e44c667..621ced55f 100644 --- a/cmds/ocm/commands/ocicmds/artifacts/download/cmd.go +++ b/cmds/ocm/commands/ocicmds/artifacts/download/cmd.go @@ -11,24 +11,24 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/destoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/formatoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/common/options/repooption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "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/common/accessio" - "github.com/open-component-model/ocm/pkg/common/accessobj" - "github.com/open-component-model/ocm/pkg/common/compression" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download/handlers/dirtree" - "github.com/open-component-model/ocm/pkg/out" - "github.com/open-component-model/ocm/pkg/utils/tarutils" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/ocm/extensions/download/handlers/dirtree" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/compression" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/api/utils/tarutils" + "ocm.software/ocm/cmds/ocm/commands/common/options/destoption" + "ocm.software/ocm/cmds/ocm/commands/common/options/formatoption" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocicmds/artifacts/download/cmd_test.go b/cmds/ocm/commands/ocicmds/artifacts/download/cmd_test.go index 79c110c2d..299e53e6c 100644 --- a/cmds/ocm/commands/ocicmds/artifacts/download/cmd_test.go +++ b/cmds/ocm/commands/ocicmds/artifacts/download/cmd_test.go @@ -6,12 +6,12 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/oci/grammar" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/mime" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/oci/grammar" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/mime" ) const ( diff --git a/cmds/ocm/commands/ocicmds/artifacts/download/option.go b/cmds/ocm/commands/ocicmds/artifacts/download/option.go index 9b1a62b19..174e60727 100644 --- a/cmds/ocm/commands/ocicmds/artifacts/download/option.go +++ b/cmds/ocm/commands/ocicmds/artifacts/download/option.go @@ -3,7 +3,7 @@ package download import ( "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/ocicmds/artifacts/get/cmd.go b/cmds/ocm/commands/ocicmds/artifacts/get/cmd.go index 8e67c24b1..761da9e73 100644 --- a/cmds/ocm/commands/ocicmds/artifacts/get/cmd.go +++ b/cmds/ocm/commands/ocicmds/artifacts/get/cmd.go @@ -5,18 +5,18 @@ import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/closureoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/common/options/repooption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" - "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/oci" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/cmds/ocm/commands/common/options/closureoption" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/options" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/processing" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocicmds/artifacts/get/cmd_test.go b/cmds/ocm/commands/ocicmds/artifacts/get/cmd_test.go index cc8216f4b..8f1d3cdb2 100644 --- a/cmds/ocm/commands/ocicmds/artifacts/get/cmd_test.go +++ b/cmds/ocm/commands/ocicmds/artifacts/get/cmd_test.go @@ -6,12 +6,12 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/mime" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr" ) const ( diff --git a/cmds/ocm/commands/ocicmds/artifacts/get/options.go b/cmds/ocm/commands/ocicmds/artifacts/get/options.go index dcf4964fa..1948b83dd 100644 --- a/cmds/ocm/commands/ocicmds/artifacts/get/options.go +++ b/cmds/ocm/commands/ocicmds/artifacts/get/options.go @@ -3,7 +3,7 @@ package get import ( "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" + "ocm.software/ocm/cmds/ocm/common/options" ) func AttachedFrom(o options.OptionSetProvider) *Attached { diff --git a/cmds/ocm/commands/ocicmds/artifacts/transfer/cmd.go b/cmds/ocm/commands/ocicmds/artifacts/transfer/cmd.go index 8b54aed92..2896d6c26 100644 --- a/cmds/ocm/commands/ocicmds/artifacts/transfer/cmd.go +++ b/cmds/ocm/commands/ocicmds/artifacts/transfer/cmd.go @@ -9,17 +9,17 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/common/options/repooption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/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/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/oci/transfer" - "github.com/open-component-model/ocm/pkg/out" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/extensions/repositories/ctf" + "ocm.software/ocm/api/oci/tools/transfer" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocicmds/artifacts/transfer/cmd_test.go b/cmds/ocm/commands/ocicmds/artifacts/transfer/cmd_test.go index ce2547755..0ca832ed7 100644 --- a/cmds/ocm/commands/ocicmds/artifacts/transfer/cmd_test.go +++ b/cmds/ocm/commands/ocicmds/artifacts/transfer/cmd_test.go @@ -6,11 +6,11 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf" - "github.com/open-component-model/ocm/pkg/mime" + "ocm.software/ocm/api/oci/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/mime" ) const ( diff --git a/cmds/ocm/commands/ocicmds/cmd.go b/cmds/ocm/commands/ocicmds/cmd.go index 4ebb6afb8..d03434c58 100644 --- a/cmds/ocm/commands/ocicmds/cmd.go +++ b/cmds/ocm/commands/ocicmds/cmd.go @@ -3,12 +3,12 @@ package ocicmds import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/artifacts" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/ctf" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/tags" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - topicocirefs "github.com/open-component-model/ocm/cmds/ocm/topics/oci/refs" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/artifacts" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/ctf" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/tags" + "ocm.software/ocm/cmds/ocm/common/utils" + topicocirefs "ocm.software/ocm/cmds/ocm/topics/oci/refs" ) // NewCommand creates a new command. diff --git a/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr/attached.go b/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr/attached.go index 1479361e3..4597bfdf0 100644 --- a/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr/attached.go +++ b/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr/attached.go @@ -7,9 +7,9 @@ import ( "github.com/mandelsoft/logging" "github.com/opencontainers/go-digest" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" - "github.com/open-component-model/ocm/pkg/common" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/processing" ) func Attachment(d digest.Digest, suffix string) string { diff --git a/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr/clean.go b/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr/clean.go index 77d7fa00f..1d10cafc5 100644 --- a/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr/clean.go +++ b/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr/clean.go @@ -3,10 +3,10 @@ package artifacthdlr import ( "github.com/opencontainers/go-digest" - "github.com/open-component-model/ocm/cmds/ocm/pkg/data" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" - "github.com/open-component-model/ocm/pkg/common" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/cmds/ocm/common/data" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/processing" ) //////////////////////////////////////////////////////////////////////////////// diff --git a/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr/closure.go b/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr/closure.go index 28f67009c..978515dbe 100644 --- a/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr/closure.go +++ b/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr/closure.go @@ -1,10 +1,10 @@ package artifacthdlr import ( - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/out" + "ocm.software/ocm/api/oci" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/cmds/ocm/common/output" ) //////////////////////////////////////////////////////////////////////////////// diff --git a/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr/convert.go b/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr/convert.go index 82cbe2b99..8c18ff3e1 100644 --- a/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr/convert.go +++ b/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr/convert.go @@ -1,7 +1,7 @@ package artifacthdlr import ( - "github.com/open-component-model/ocm/cmds/ocm/pkg/data" + "ocm.software/ocm/cmds/ocm/common/data" ) type Objects []*Object diff --git a/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr/sort.go b/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr/sort.go index 57fa2963d..924e2e407 100644 --- a/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr/sort.go +++ b/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr/sort.go @@ -3,8 +3,8 @@ package artifacthdlr import ( "strings" - "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" - "github.com/open-component-model/ocm/pkg/common" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/cmds/ocm/common/processing" ) func Compare(a, b interface{}) int { diff --git a/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr/typehandler.go b/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr/typehandler.go index ae611292f..aa8aa3480 100644 --- a/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr/typehandler.go +++ b/cmds/ocm/commands/ocicmds/common/handlers/artifacthdlr/typehandler.go @@ -7,13 +7,13 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/logging" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - "github.com/open-component-model/ocm/cmds/ocm/pkg/tree" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/tree" + "ocm.software/ocm/cmds/ocm/common/utils" ) func Elem(e interface{}) oci.ArtifactAccess { diff --git a/cmds/ocm/commands/ocicmds/common/options/repooption/option.go b/cmds/ocm/commands/ocicmds/common/options/repooption/option.go index 7cd49a5b7..c5f6e9305 100644 --- a/cmds/ocm/commands/ocicmds/common/options/repooption/option.go +++ b/cmds/ocm/commands/ocicmds/common/options/repooption/option.go @@ -3,11 +3,11 @@ package repooption import ( "github.com/spf13/pflag" - "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/oci" - "github.com/open-component-model/ocm/pkg/listformat" - "github.com/open-component-model/ocm/pkg/runtime" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/utils/listformat" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/ocicmds/common/utils.go b/cmds/ocm/commands/ocicmds/common/utils.go index 11c4bbd3c..0bdb3d9a6 100644 --- a/cmds/ocm/commands/ocicmds/common/utils.go +++ b/cmds/ocm/commands/ocicmds/common/utils.go @@ -1,9 +1,9 @@ package common import ( - "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/oci" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/cmds/ocm/common/options" ) type OptionCompleter interface { diff --git a/cmds/ocm/commands/ocicmds/ctf/cmd.go b/cmds/ocm/commands/ocicmds/ctf/cmd.go index b5c70431f..02546a724 100644 --- a/cmds/ocm/commands/ocicmds/ctf/cmd.go +++ b/cmds/ocm/commands/ocicmds/ctf/cmd.go @@ -3,10 +3,10 @@ package ctf import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/ctf/create" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/names" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/ctf/create" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/names" + "ocm.software/ocm/cmds/ocm/common/utils" ) var Names = names.TransportArchive diff --git a/cmds/ocm/commands/ocicmds/ctf/create/cmd.go b/cmds/ocm/commands/ocicmds/ctf/create/cmd.go index 32a7d18e2..089140e2b 100644 --- a/cmds/ocm/commands/ocicmds/ctf/create/cmd.go +++ b/cmds/ocm/commands/ocicmds/ctf/create/cmd.go @@ -6,14 +6,14 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/formatoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/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/common/accessio" - "github.com/open-component-model/ocm/pkg/common/accessobj" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/oci/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/cmds/ocm/commands/common/options/formatoption" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocicmds/ctf/create/cmd_test.go b/cmds/ocm/commands/ocicmds/ctf/create/cmd_test.go index b99e6ad5a..dfa1a1948 100644 --- a/cmds/ocm/commands/ocicmds/ctf/create/cmd_test.go +++ b/cmds/ocm/commands/ocicmds/ctf/create/cmd_test.go @@ -3,9 +3,9 @@ package create_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf" + "ocm.software/ocm/api/oci/extensions/repositories/ctf" ) const ARCH = "/tmp/ctf" diff --git a/cmds/ocm/commands/ocicmds/tags/cmd.go b/cmds/ocm/commands/ocicmds/tags/cmd.go index 77b26d800..0d74d9b72 100644 --- a/cmds/ocm/commands/ocicmds/tags/cmd.go +++ b/cmds/ocm/commands/ocicmds/tags/cmd.go @@ -3,10 +3,10 @@ package tags import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/tags/show" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/names" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/tags/show" + "ocm.software/ocm/cmds/ocm/common/utils" ) var Names = names.Tags diff --git a/cmds/ocm/commands/ocicmds/tags/show/cmd.go b/cmds/ocm/commands/ocicmds/tags/show/cmd.go index 16a5180d1..db7c0f3e5 100644 --- a/cmds/ocm/commands/ocicmds/tags/show/cmd.go +++ b/cmds/ocm/commands/ocicmds/tags/show/cmd.go @@ -9,14 +9,14 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - ocmcommon "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/common/options/repooption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/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/contexts/oci" - "github.com/open-component-model/ocm/pkg/out" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/utils/out" + ocmcommon "ocm.software/ocm/cmds/ocm/commands/ocicmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/ocicmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocicmds/tags/show/cmd_test.go b/cmds/ocm/commands/ocicmds/tags/show/cmd_test.go index b641b4f81..6b36dbeeb 100644 --- a/cmds/ocm/commands/ocicmds/tags/show/cmd_test.go +++ b/cmds/ocm/commands/ocicmds/tags/show/cmd_test.go @@ -6,10 +6,10 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/mime" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/mime" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/cli/cmd.go b/cmds/ocm/commands/ocmcmds/cli/cmd.go index aea68ce26..59b378741 100644 --- a/cmds/ocm/commands/ocmcmds/cli/cmd.go +++ b/cmds/ocm/commands/ocmcmds/cli/cmd.go @@ -3,10 +3,10 @@ package cli import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/cli/download" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/cli/download" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/common/utils" ) var Names = names.Components diff --git a/cmds/ocm/commands/ocmcmds/cli/download/cmd.go b/cmds/ocm/commands/ocmcmds/cli/download/cmd.go index 4f952ae83..7f4b757c6 100644 --- a/cmds/ocm/commands/ocmcmds/cli/download/cmd.go +++ b/cmds/ocm/commands/ocmcmds/cli/download/cmd.go @@ -10,23 +10,23 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/destoption" - ocmcommon "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/resources/common" - downloadcmd "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/resources/download" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "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" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/extraid" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/out" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extraid" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/cmds/ocm/commands/common/options/destoption" + ocmcommon "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/resources/common" + downloadcmd "ocm.software/ocm/cmds/ocm/commands/ocmcmds/resources/download" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/cli/download/cmd_test.go b/cmds/ocm/commands/ocmcmds/cli/download/cmd_test.go index df4218ffd..7b6ae3bb6 100644 --- a/cmds/ocm/commands/ocmcmds/cli/download/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/cli/download/cmd_test.go @@ -9,15 +9,15 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/cli/download" - "github.com/open-component-model/ocm/pkg/common/accessio" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/extraid" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - env2 "github.com/open-component-model/ocm/pkg/env" - "github.com/open-component-model/ocm/pkg/mime" + env2 "ocm.software/ocm/api/helper/env" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extraid" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/cli/download" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/cmd.go b/cmds/ocm/commands/ocmcmds/cmd.go index 813c2184e..76d662566 100644 --- a/cmds/ocm/commands/ocmcmds/cmd.go +++ b/cmds/ocm/commands/ocmcmds/cmd.go @@ -3,24 +3,24 @@ package ocmcmds import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/componentarchive" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/components" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/ctf" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/plugins" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/pubsub" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/references" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/resourceconfig" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/resources" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/routingslips" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/sourceconfig" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/sources" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/versions" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - topicocmaccessmethods "github.com/open-component-model/ocm/cmds/ocm/topics/ocm/accessmethods" - topicocmdownloaders "github.com/open-component-model/ocm/cmds/ocm/topics/ocm/downloadhandlers" - topicocmrefs "github.com/open-component-model/ocm/cmds/ocm/topics/ocm/refs" - topicocmuploaders "github.com/open-component-model/ocm/cmds/ocm/topics/ocm/uploadhandlers" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/componentarchive" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/components" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/ctf" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/plugins" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/pubsub" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/references" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/resourceconfig" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/resources" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/routingslips" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/sourceconfig" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/sources" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/versions" + "ocm.software/ocm/cmds/ocm/common/utils" + topicocmaccessmethods "ocm.software/ocm/cmds/ocm/topics/ocm/accessmethods" + topicocmdownloaders "ocm.software/ocm/cmds/ocm/topics/ocm/downloadhandlers" + topicocmrefs "ocm.software/ocm/cmds/ocm/topics/ocm/refs" + topicocmuploaders "ocm.software/ocm/cmds/ocm/topics/ocm/uploadhandlers" ) // NewCommand creates a new command. diff --git a/cmds/ocm/commands/ocmcmds/common/addconfig.go b/cmds/ocm/commands/ocmcmds/common/addconfig.go index 8f391b31e..cb4614986 100644 --- a/cmds/ocm/commands/ocmcmds/common/addconfig.go +++ b/cmds/ocm/commands/ocmcmds/common/addconfig.go @@ -10,14 +10,14 @@ import ( "github.com/spf13/pflag" "sigs.k8s.io/yaml" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/templateroption" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - utils2 "github.com/open-component-model/ocm/pkg/utils" + clictx "ocm.software/ocm/api/cli" + utils2 "ocm.software/ocm/api/utils" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/templateroption" + "ocm.software/ocm/cmds/ocm/common/options" + "ocm.software/ocm/cmds/ocm/common/utils" ) type ModifiedResourceSpecificationsFile struct { diff --git a/cmds/ocm/commands/ocmcmds/common/addhdlrs/base.go b/cmds/ocm/commands/ocmcmds/common/addhdlrs/base.go index 4f5726194..f772472ac 100644 --- a/cmds/ocm/commands/ocmcmds/common/addhdlrs/base.go +++ b/cmds/ocm/commands/ocmcmds/common/addhdlrs/base.go @@ -3,8 +3,8 @@ package addhdlrs import ( "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/pkg/contexts/ocm" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/cmds/ocm/common/options" ) type ResourceSpecHandlerBase struct { diff --git a/cmds/ocm/commands/ocmcmds/common/addhdlrs/comp/components.go b/cmds/ocm/commands/ocmcmds/common/addhdlrs/comp/components.go index 29f513a0d..7f423468f 100644 --- a/cmds/ocm/commands/ocmcmds/common/addhdlrs/comp/components.go +++ b/cmds/ocm/commands/ocmcmds/common/addhdlrs/comp/components.go @@ -6,14 +6,14 @@ import ( "github.com/mandelsoft/goutils/set" "github.com/mandelsoft/goutils/sliceutils" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" - "github.com/open-component-model/ocm/pkg/common" - "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/contexts/ocm/transfer" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler" - "github.com/open-component-model/ocm/pkg/out" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/tools/transfer" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" ) func ProcessComponents(ctx clictx.Context, ictx inputs.Context, repo ocm.Repository, complete ocm.ComponentVersionResolver, thdlr transferhandler.TransferHandler, h *ResourceSpecHandler, elems []addhdlrs.Element) (err error) { diff --git a/cmds/ocm/commands/ocmcmds/common/addhdlrs/comp/elements.go b/cmds/ocm/commands/ocmcmds/common/addhdlrs/comp/elements.go index 7331225b9..f9dfd1156 100644 --- a/cmds/ocm/commands/ocmcmds/common/addhdlrs/comp/elements.go +++ b/cmds/ocm/commands/ocmcmds/common/addhdlrs/comp/elements.go @@ -8,21 +8,21 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs/refs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs/rscs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs/srcs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "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/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/compatattr" - "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/runtime" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/attrs/compatattr" + "ocm.software/ocm/api/utils/errkind" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs/refs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs/rscs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs/srcs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/common/options" + "ocm.software/ocm/cmds/ocm/common/utils" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/common/addhdlrs/interface.go b/cmds/ocm/commands/ocmcmds/common/addhdlrs/interface.go index cf4cc2eca..8a969e6d0 100644 --- a/cmds/ocm/commands/ocmcmds/common/addhdlrs/interface.go +++ b/cmds/ocm/commands/ocmcmds/common/addhdlrs/interface.go @@ -7,10 +7,10 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/goutils/sliceutils" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" + clictx "ocm.software/ocm/api/cli" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" ) // ResourceInput describe the source for the content of diff --git a/cmds/ocm/commands/ocmcmds/common/addhdlrs/options.go b/cmds/ocm/commands/ocmcmds/common/addhdlrs/options.go index 459ee221d..e9614db21 100644 --- a/cmds/ocm/commands/ocmcmds/common/addhdlrs/options.go +++ b/cmds/ocm/commands/ocmcmds/common/addhdlrs/options.go @@ -4,7 +4,7 @@ import ( "github.com/mandelsoft/goutils/generics" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/pkg/contexts/ocm" + "ocm.software/ocm/api/ocm" ) type Options struct { diff --git a/cmds/ocm/commands/ocmcmds/common/addhdlrs/refs/elements.go b/cmds/ocm/commands/ocmcmds/common/addhdlrs/refs/elements.go index 86ede5cd5..edea237dd 100644 --- a/cmds/ocm/commands/ocmcmds/common/addhdlrs/refs/elements.go +++ b/cmds/ocm/commands/ocmcmds/common/addhdlrs/refs/elements.go @@ -5,15 +5,15 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" - "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" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - compdescv2 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/versions/v2" - "github.com/open-component-model/ocm/pkg/runtime" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + compdescv2 "ocm.software/ocm/api/ocm/compdesc/versions/v2" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" + "ocm.software/ocm/cmds/ocm/common/options" ) type ResourceSpecHandler struct { diff --git a/cmds/ocm/commands/ocmcmds/common/addhdlrs/rscs/elements.go b/cmds/ocm/commands/ocmcmds/common/addhdlrs/rscs/elements.go index aa1aaa732..1cd98d239 100644 --- a/cmds/ocm/commands/ocmcmds/common/addhdlrs/rscs/elements.go +++ b/cmds/ocm/commands/ocmcmds/common/addhdlrs/rscs/elements.go @@ -6,16 +6,16 @@ import ( "github.com/mandelsoft/goutils/errors" "k8s.io/apimachinery/pkg/util/validation/field" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/skipdigestoption" - "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" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - compdescv2 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/versions/v2" - "github.com/open-component-model/ocm/pkg/runtime" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + compdescv2 "ocm.software/ocm/api/ocm/compdesc/versions/v2" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/skipdigestoption" + "ocm.software/ocm/cmds/ocm/common/options" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/common/addhdlrs/srcs/elements.go b/cmds/ocm/commands/ocmcmds/common/addhdlrs/srcs/elements.go index ed082ab09..6ab83fca1 100644 --- a/cmds/ocm/commands/ocmcmds/common/addhdlrs/srcs/elements.go +++ b/cmds/ocm/commands/ocmcmds/common/addhdlrs/srcs/elements.go @@ -5,15 +5,15 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" - "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" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - compdescv2 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/versions/v2" - "github.com/open-component-model/ocm/pkg/runtime" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + compdescv2 "ocm.software/ocm/api/ocm/compdesc/versions/v2" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" + "ocm.software/ocm/cmds/ocm/common/options" ) type ResourceSpecHandler struct { diff --git a/cmds/ocm/commands/ocmcmds/common/addhdlrs/utils.go b/cmds/ocm/commands/ocmcmds/common/addhdlrs/utils.go index 7833a3a07..c47f272ee 100644 --- a/cmds/ocm/commands/ocmcmds/common/addhdlrs/utils.go +++ b/cmds/ocm/commands/ocmcmds/common/addhdlrs/utils.go @@ -11,14 +11,14 @@ import ( "gopkg.in/yaml.v3" "k8s.io/apimachinery/pkg/util/validation/field" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" - cliutils "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - common2 "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/errkind" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/utils/template" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/errkind" + common2 "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/api/utils/template" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + cliutils "ocm.software/ocm/cmds/ocm/common/utils" ) func ProcessDescriptions(ctx clictx.Context, printer common2.Printer, templ template.Options, h ElementSpecHandler, sources []ElementSource) ([]Element, inputs.Context, error) { diff --git a/cmds/ocm/commands/ocmcmds/common/cmds/signing/cmd.go b/cmds/ocm/commands/ocmcmds/common/cmds/signing/cmd.go index 3c215a3cd..7980618ac 100644 --- a/cmds/ocm/commands/ocmcmds/common/cmds/signing/cmd.go +++ b/cmds/ocm/commands/ocmcmds/common/cmds/signing/cmd.go @@ -6,20 +6,20 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/spf13/cobra" - 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" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/signoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" - "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/common" - "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/contexts/ocm/compdesc" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/signing" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/tools/signing" + common "ocm.software/ocm/api/utils/misc" + ocmcommon "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/signoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/utils" ) type SignatureCommand struct { diff --git a/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/closure.go b/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/closure.go index 4560c93b8..25cf44f68 100644 --- a/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/closure.go +++ b/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/closure.go @@ -3,11 +3,11 @@ package comphdlr import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/out" + "ocm.software/ocm/api/ocm" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" + "ocm.software/ocm/cmds/ocm/common/output" ) //////////////////////////////////////////////////////////////////////////////// diff --git a/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/convert.go b/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/convert.go index ffc82cdd7..e627f37c4 100644 --- a/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/convert.go +++ b/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/convert.go @@ -1,7 +1,7 @@ package comphdlr import ( - "github.com/open-component-model/ocm/cmds/ocm/pkg/data" + "ocm.software/ocm/cmds/ocm/common/data" ) type Objects []*Object diff --git a/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/options.go b/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/options.go index 5fd9674a7..e30f5c460 100644 --- a/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/options.go +++ b/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/options.go @@ -3,11 +3,11 @@ package comphdlr import ( "github.com/Masterminds/semver/v3" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" + "ocm.software/ocm/cmds/ocm/common/options" ) type Option interface { diff --git a/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/sort.go b/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/sort.go index aad35bd61..7b6b0ab6b 100644 --- a/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/sort.go +++ b/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/sort.go @@ -3,8 +3,8 @@ package comphdlr import ( "strings" - "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" - "github.com/open-component-model/ocm/pkg/semverutils" + "ocm.software/ocm/api/utils/semverutils" + "ocm.software/ocm/cmds/ocm/common/processing" ) func Compare(a, b interface{}) int { diff --git a/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/typehandler.go b/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/typehandler.go index 340f0cb0f..3112ba5c9 100644 --- a/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/typehandler.go +++ b/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/typehandler.go @@ -8,15 +8,15 @@ import ( "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/tree" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/common" - "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/contexts/ocm/compdesc" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/semverutils" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/semverutils" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/tree" + "ocm.software/ocm/cmds/ocm/common/utils" ) func Elem(e interface{}) ocm.ComponentVersionAccess { diff --git a/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/util.go b/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/util.go index 21932a2ac..35bc0e894 100644 --- a/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/util.go +++ b/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr/util.go @@ -3,11 +3,11 @@ package comphdlr import ( "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/pkg/output" - "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/ocm" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/cmds/ocm/commands/common/options/closureoption" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/utils" ) func Evaluate(octx clictx.OCM, session ocm.Session, repobase ocm.Repository, compspecs []string, oopts *output.Options, opts ...Option) (Objects, error) { diff --git a/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr/convert.go b/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr/convert.go index 71151e96b..f3e1da631 100644 --- a/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr/convert.go +++ b/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr/convert.go @@ -1,7 +1,7 @@ package elemhdlr import ( - "github.com/open-component-model/ocm/cmds/ocm/pkg/data" + "ocm.software/ocm/cmds/ocm/common/data" ) type Objects []*Object diff --git a/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr/options.go b/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr/options.go index d13f50f46..4b2fd7917 100644 --- a/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr/options.go +++ b/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr/options.go @@ -3,11 +3,11 @@ package elemhdlr import ( "github.com/Masterminds/semver/v3" - "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" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/pkg/contexts/ocm" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" + "ocm.software/ocm/cmds/ocm/common/options" ) type Option interface { diff --git a/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr/output.go b/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr/output.go index 02e7c61e7..4656ad508 100644 --- a/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr/output.go +++ b/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr/output.go @@ -3,11 +3,11 @@ package elemhdlr import ( "encoding/json" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - "github.com/open-component-model/ocm/cmds/ocm/pkg/tree" - "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/runtime" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/tree" ) var MetaOutput = []string{"NAME", "VERSION", "IDENTITY"} diff --git a/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr/sort.go b/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr/sort.go index 8127338d8..8c27aebd8 100644 --- a/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr/sort.go +++ b/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr/sort.go @@ -1,7 +1,7 @@ package elemhdlr import ( - "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" + "ocm.software/ocm/cmds/ocm/common/processing" ) func Compare(a, b interface{}) int { diff --git a/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr/typehandler.go b/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr/typehandler.go index f6dbbc42e..dd335e9ad 100644 --- a/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr/typehandler.go +++ b/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr/typehandler.go @@ -4,15 +4,15 @@ import ( "fmt" "strings" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - "github.com/open-component-model/ocm/cmds/ocm/pkg/tree" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/common" - "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/contexts/ocm/compdesc" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/tree" + "ocm.software/ocm/cmds/ocm/common/utils" ) type Object struct { diff --git a/cmds/ocm/commands/ocmcmds/common/handlers/pluginhdlr/typehandler.go b/cmds/ocm/commands/ocmcmds/common/handlers/pluginhdlr/typehandler.go index 60ac67414..03bb04509 100644 --- a/cmds/ocm/commands/ocmcmds/common/handlers/pluginhdlr/typehandler.go +++ b/cmds/ocm/commands/ocmcmds/common/handlers/pluginhdlr/typehandler.go @@ -5,13 +5,13 @@ import ( "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" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugincacheattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/plugins" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm/extensions/attrs/plugincacheattr" + "ocm.software/ocm/api/ocm/plugin" + "ocm.software/ocm/api/ocm/plugin/descriptor" + "ocm.software/ocm/api/ocm/plugin/plugins" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/utils" ) func Elem(e interface{}) plugin.Plugin { diff --git a/cmds/ocm/commands/ocmcmds/common/handlers/vershdlr/options.go b/cmds/ocm/commands/ocmcmds/common/handlers/vershdlr/options.go index d1d84a0a9..79f7355d9 100644 --- a/cmds/ocm/commands/ocmcmds/common/handlers/vershdlr/options.go +++ b/cmds/ocm/commands/ocmcmds/common/handlers/vershdlr/options.go @@ -3,11 +3,11 @@ package vershdlr import ( "github.com/Masterminds/semver/v3" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" + "ocm.software/ocm/cmds/ocm/common/options" ) type Option interface { diff --git a/cmds/ocm/commands/ocmcmds/common/handlers/vershdlr/sort.go b/cmds/ocm/commands/ocmcmds/common/handlers/vershdlr/sort.go index 941e6aed4..d40d9fc48 100644 --- a/cmds/ocm/commands/ocmcmds/common/handlers/vershdlr/sort.go +++ b/cmds/ocm/commands/ocmcmds/common/handlers/vershdlr/sort.go @@ -3,8 +3,8 @@ package vershdlr import ( "strings" - "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" - "github.com/open-component-model/ocm/pkg/semverutils" + "ocm.software/ocm/api/utils/semverutils" + "ocm.software/ocm/cmds/ocm/common/processing" ) func Compare(a, b interface{}) int { diff --git a/cmds/ocm/commands/ocmcmds/common/handlers/vershdlr/typehandler.go b/cmds/ocm/commands/ocmcmds/common/handlers/vershdlr/typehandler.go index 83967edd1..5f61d4ff2 100644 --- a/cmds/ocm/commands/ocmcmds/common/handlers/vershdlr/typehandler.go +++ b/cmds/ocm/commands/ocmcmds/common/handlers/vershdlr/typehandler.go @@ -8,11 +8,11 @@ import ( "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" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/semverutils" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/utils/semverutils" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/utils" ) func Elem(e interface{}) *Object { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/cpi/helper.go b/cmds/ocm/commands/ocmcmds/common/inputs/cpi/helper.go index ae596e25e..2646ae99a 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/cpi/helper.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/cpi/helper.go @@ -10,13 +10,13 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" "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/options" - "github.com/open-component-model/ocm/pkg/blobaccess" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/runtime" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" ) type PathSpec struct { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/inputfields.go b/cmds/ocm/commands/ocmcmds/common/inputs/inputfields.go index 1e94bd2b5..b3ae07ad6 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/inputfields.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/inputfields.go @@ -4,8 +4,8 @@ import ( "fmt" "strings" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/cobrautils/flagsets" ) type FieldSetter func(opts flagsets.ConfigOptions, opt flagsets.ConfigOptionType, config flagsets.Config) error diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/inputtype.go b/cmds/ocm/commands/ocmcmds/common/inputs/inputtype.go index 1ea930581..39a233d97 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/inputtype.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/inputtype.go @@ -10,13 +10,13 @@ import ( "github.com/modern-go/reflect2" "k8s.io/apimachinery/pkg/util/validation/field" - "github.com/open-component-model/ocm/pkg/blobaccess" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/runtime" ) const KIND_INPUTTYPE = "input type" @@ -255,7 +255,7 @@ func CreateRepositorySpec(t runtime.TypedObject) (InputSpec, error) { //////////////////////////////////////////////////////////////////////////////// -const ATTR_INPUT_TYPES = "github.com/open-component-model/ocm/cmds/ocm/common/inputs" +const ATTR_INPUT_TYPES = "ocm.software/ocm/cmds/ocm/common/inputs" func For(ctx datacontext.Context) InputTypeScheme { if ctx == nil { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/inputtype_test.go b/cmds/ocm/commands/ocmcmds/common/inputs/inputtype_test.go index 5ffbf2554..a124ee0a3 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/inputtype_test.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/inputtype_test.go @@ -6,9 +6,9 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/file" - "github.com/open-component-model/ocm/pkg/mime" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/file" ) var _ = Describe("Blob Inputs", func() { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/options/standard.go b/cmds/ocm/commands/ocmcmds/common/inputs/options/standard.go index 32115ce96..1d900ad8b 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/options/standard.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/options/standard.go @@ -1,8 +1,8 @@ package options import ( - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/options" + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + "ocm.software/ocm/api/utils/cobrautils/flagsets" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/testutils/testutils.go b/cmds/ocm/commands/ocmcmds/common/inputs/testutils/testutils.go index da70ac057..f8bc4b55d 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/testutils/testutils.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/testutils/testutils.go @@ -8,9 +8,9 @@ import ( "github.com/mandelsoft/goutils/testutils" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/runtime" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" ) type NameProvider interface { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/cli.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/cli.go index b195b2810..e68a6d0b6 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/cli.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/cli.go @@ -1,9 +1,9 @@ package binary import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" ) func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/inputtype_test.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/inputtype_test.go index ee4e761fc..eb381a2de 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/inputtype_test.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/inputtype_test.go @@ -2,11 +2,11 @@ package binary import ( . "github.com/onsi/ginkgo/v2" - . "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/testutils" + . "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/testutils" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" - "github.com/open-component-model/ocm/pkg/runtime" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" ) var _ = Describe("Input Type", func() { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/spec.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/spec.go index 2869075a2..48ed375fc 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/spec.go @@ -3,10 +3,10 @@ package binary import ( "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" - "github.com/open-component-model/ocm/pkg/runtime" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" ) type Spec struct { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/type.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/type.go index 10d4b76cd..abf4431cd 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/type.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/type.go @@ -1,8 +1,8 @@ package binary import ( - "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" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" ) const TYPE = "binary" diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/cli.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/cli.go index fe3b007bb..ae78d2fbb 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/cli.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/cli.go @@ -1,9 +1,9 @@ package directory import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" ) func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/inputtype_test.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/inputtype_test.go index 2840871e2..6679c7905 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/inputtype_test.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/inputtype_test.go @@ -2,10 +2,10 @@ package directory import ( . "github.com/onsi/ginkgo/v2" - . "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/testutils" + . "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/testutils" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" ) var _ = Describe("Input Type", func() { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/spec.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/spec.go index d37eff103..be736935e 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/spec.go @@ -5,11 +5,11 @@ import ( "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" - "github.com/open-component-model/ocm/pkg/blobaccess/dirtree" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/dirtree" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" ) type Spec struct { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/type.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/type.go index 94b92fce4..9250ee957 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/type.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/type.go @@ -1,8 +1,8 @@ package directory import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" - "github.com/open-component-model/ocm/pkg/mime" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" ) const TYPE = "dir" diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/docker/cli.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/docker/cli.go index fae0c599b..7fb90c479 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/docker/cli.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/docker/cli.go @@ -1,9 +1,9 @@ package docker import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" ) func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/docker/spec.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/docker/spec.go index 75e5c2899..42d7f391a 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/docker/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/docker/spec.go @@ -3,13 +3,13 @@ package docker import ( "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" - ociartifact2 "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact" - "github.com/open-component-model/ocm/pkg/blobaccess" - "github.com/open-component-model/ocm/pkg/blobaccess/dockerdaemon" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/docker" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" + "ocm.software/ocm/api/oci/extensions/repositories/docker" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/dockerdaemon" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" + ociartifact2 "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact" ) type Spec struct { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/docker/type.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/docker/type.go index 789b8282d..c5596be53 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/docker/type.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/docker/type.go @@ -1,8 +1,8 @@ package docker import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" - "github.com/open-component-model/ocm/pkg/contexts/oci/annotations" + "ocm.software/ocm/api/oci/annotations" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" ) const TYPE = "docker" diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/dockermulti/cli.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/dockermulti/cli.go index ba884b6fb..6dba2ac6d 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/dockermulti/cli.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/dockermulti/cli.go @@ -1,8 +1,8 @@ package dockermulti import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" ) func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/dockermulti/spec.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/dockermulti/spec.go index 65186a3de..2f93977b5 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/dockermulti/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/dockermulti/spec.go @@ -5,13 +5,13 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" - ociartifact2 "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact" - "github.com/open-component-model/ocm/pkg/blobaccess" - "github.com/open-component-model/ocm/pkg/blobaccess/dockermulti" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/docker" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "github.com/open-component-model/ocm/pkg/runtime" + "ocm.software/ocm/api/oci/extensions/repositories/docker" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/dockermulti" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + ociartifact2 "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact" ) type Spec struct { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/dockermulti/type.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/dockermulti/type.go index 87e09c039..780a00d23 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/dockermulti/type.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/dockermulti/type.go @@ -1,8 +1,8 @@ package dockermulti import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" - "github.com/open-component-model/ocm/pkg/contexts/oci/annotations" + "ocm.software/ocm/api/oci/annotations" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" ) const TYPE = "dockermulti" diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/file/cli.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/file/cli.go index 35715de76..e7ec97702 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/file/cli.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/file/cli.go @@ -1,8 +1,8 @@ package file import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" ) func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/file/spec.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/file/spec.go index c1caefec9..aa31882e1 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/file/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/file/spec.go @@ -3,9 +3,9 @@ package file import ( "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" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" ) type Spec struct { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/file/support.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/file/support.go index 237cf4033..44dbdfffa 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/file/support.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/file/support.go @@ -9,10 +9,10 @@ import ( "github.com/mandelsoft/goutils/errors" "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" - "github.com/open-component-model/ocm/pkg/mime" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" ) type FileProcessSpec struct { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/file/type.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/file/type.go index ef38730c1..1193534fd 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/file/type.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/file/type.go @@ -1,7 +1,7 @@ package file import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" ) const TYPE = "file" diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/helm/cli.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/helm/cli.go index cee174318..9d7d33633 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/helm/cli.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/helm/cli.go @@ -1,9 +1,9 @@ package helm import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" ) func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/helm/spec.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/helm/spec.go index 82bb85502..53b3c055a 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/helm/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/helm/spec.go @@ -4,11 +4,11 @@ import ( "github.com/mandelsoft/goutils/errors" "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" - "github.com/open-component-model/ocm/pkg/blobaccess/helm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/helm" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" ) type Spec struct { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/helm/type.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/helm/type.go index ddb5cead7..785fb7746 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/helm/type.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/helm/type.go @@ -1,7 +1,7 @@ package helm import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" ) const TYPE = "helm" diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/init.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/init.go index 8c42a5781..8e3230b35 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/init.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/init.go @@ -1,15 +1,15 @@ package handlers import ( - _ "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/binary" - _ "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/directory" - _ "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/docker" - _ "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" - _ "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/wget" + _ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/binary" + _ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/directory" + _ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/docker" + _ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/dockermulti" + _ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/file" + _ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/helm" + _ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/maven" + _ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact" + _ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff" + _ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8" + _ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/wget" ) diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/cli.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/cli.go index 88b6fcbc6..ab7827ebd 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/cli.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/cli.go @@ -1,8 +1,8 @@ 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" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" ) func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { 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 index 98c07166b..6f58d66f4 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/input_test.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/input_test.go @@ -6,14 +6,14 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/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/maven/maventest" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/utils/tarutils" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/repositories/comparch" + "ocm.software/ocm/api/tech/maven/maventest" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/api/utils/tarutils" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/spec.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/spec.go index 7d3414444..4ffb28085 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/spec.go @@ -5,12 +5,12 @@ import ( "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" + "ocm.software/ocm/api/datacontext/attrs/vfsattr" + "ocm.software/ocm/api/tech/maven" + "ocm.software/ocm/api/utils/blobaccess" + mavenblob "ocm.software/ocm/api/utils/blobaccess/maven" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" ) type Spec struct { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/type.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/type.go index 7c80db8e3..0c97f1c45 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/type.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/type.go @@ -1,7 +1,7 @@ package maven import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" ) const TYPE = "maven" diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/cli.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/cli.go index 7e4733296..043fd1f1f 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/cli.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/cli.go @@ -1,9 +1,9 @@ package ociartifact import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" ) func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/input_test.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/input_test.go index 65e1f80a7..27d6350d1 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/input_test.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/input_test.go @@ -7,21 +7,21 @@ import ( . "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/oci/testhelper" + . "ocm.software/ocm/api/oci/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" - me "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "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/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" + me "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/spec.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/spec.go index af1ff65a3..587109c81 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/spec.go @@ -5,14 +5,14 @@ import ( "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" - ociartifactblob "github.com/open-component-model/ocm/pkg/blobaccess/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/oci/grammar" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/docker" - "github.com/open-component-model/ocm/pkg/contexts/oci/transfer/filters" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" + "ocm.software/ocm/api/oci/extensions/repositories/docker" + "ocm.software/ocm/api/oci/grammar" + "ocm.software/ocm/api/oci/tools/transfer/filters" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + "ocm.software/ocm/api/utils/blobaccess" + ociartifactblob "ocm.software/ocm/api/utils/blobaccess/ociartifact" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" ) type Spec struct { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/type.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/type.go index af0c0e3c7..4c5ae3473 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/type.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact/type.go @@ -1,7 +1,7 @@ package ociartifact import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/cli.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/cli.go index 3e9a96ea9..f495e9f50 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/cli.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/cli.go @@ -1,9 +1,9 @@ package spiff import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" ) func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/spec.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/spec.go index 444b97a1c..d99ec7fbf 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/spec.go @@ -9,11 +9,11 @@ import ( "github.com/mandelsoft/vfs/pkg/cwdfs" "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/cmds/ocm/commands/ocmcmds/common/inputs/types/file" - "github.com/open-component-model/ocm/pkg/blobaccess" - "github.com/open-component-model/ocm/pkg/runtime" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/file" ) type Spec struct { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/spiff_test.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/spiff_test.go index f32af1955..b7d7b9934 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/spiff_test.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/spiff_test.go @@ -3,11 +3,11 @@ package spiff_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff" - "github.com/open-component-model/ocm/pkg/common" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff" ) var _ = Describe("spiff processing", func() { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/type.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/type.go index 40e860dff..20c08d0d4 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/type.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/type.go @@ -1,8 +1,8 @@ package spiff import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/file" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/file" ) const TYPE = "spiff" diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/cli.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/cli.go index 166a4c43d..ce350821f 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/cli.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/cli.go @@ -1,9 +1,9 @@ package utf8 import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" ) func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/inputtype_test.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/inputtype_test.go index dbd585481..1afad536f 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/inputtype_test.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/inputtype_test.go @@ -4,12 +4,12 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/testutils" + . "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/testutils" - "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/cmds/ocm/commands/ocmcmds/common/inputs/options" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" ) var _ = Describe("Input Type", func() { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/spec.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/spec.go index 22a7b4977..00179e50d 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/spec.go @@ -5,10 +5,10 @@ import ( "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" - "github.com/open-component-model/ocm/pkg/runtime" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" ) type Spec struct { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/type.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/type.go index 8606f59d5..ab2936c68 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/type.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/type.go @@ -1,8 +1,8 @@ package utf8 import ( - "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" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/cpi" ) const TYPE = "utf8" diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/cli.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/cli.go index 21c6f72fd..e8e16f098 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/cli.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/cli.go @@ -1,8 +1,8 @@ package wget import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" ) func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/input_test.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/input_test.go index 30bb607f0..6c20995db 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/input_test.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/input_test.go @@ -4,12 +4,12 @@ import ( "net/http" . "github.com/onsi/ginkgo/v2" - . "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/testutils" + . "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/testutils" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/wget" - "github.com/open-component-model/ocm/pkg/mime" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/wget" ) var _ = Describe("Input Type", func() { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/spec.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/spec.go index 2b58d1590..7b6f8b3c8 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/spec.go @@ -5,10 +5,10 @@ import ( "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/pkg/blobaccess" - "github.com/open-component-model/ocm/pkg/blobaccess/wget" - "github.com/open-component-model/ocm/pkg/runtime" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/wget" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" ) type Spec struct { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/type.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/type.go index b1b9236a0..9c966a12b 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/type.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/type.go @@ -1,8 +1,8 @@ package wget import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" - "github.com/open-component-model/ocm/pkg/mime" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" ) const TYPE = "wget" diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/utils.go b/cmds/ocm/commands/ocmcmds/common/inputs/utils.go index b1ee922cf..2f30e164f 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/utils.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/utils.go @@ -7,7 +7,7 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" ) func FileInfo(ctx clictx.Context, path string, inputFilePath string) (os.FileInfo, string, error) { diff --git a/cmds/ocm/commands/ocmcmds/common/options/comppathopt/comppath_test.go b/cmds/ocm/commands/ocmcmds/common/options/comppathopt/comppath_test.go index 7ad6e31b5..45db4a188 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/comppathopt/comppath_test.go +++ b/cmds/ocm/commands/ocmcmds/common/options/comppathopt/comppath_test.go @@ -8,8 +8,8 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/comppathopt" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/comppathopt" ) func TestConfig(t *testing.T) { diff --git a/cmds/ocm/commands/ocmcmds/common/options/comppathopt/option.go b/cmds/ocm/commands/ocmcmds/common/options/comppathopt/option.go index 67a113465..d25df13c3 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/comppathopt/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/comppathopt/option.go @@ -3,9 +3,9 @@ package comppathopt import ( "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/common/output" ) func From(o *output.Options) *Option { diff --git a/cmds/ocm/commands/ocmcmds/common/options/downloaderoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/downloaderoption/option.go index 4f8cb8353..e01327d29 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/downloaderoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/downloaderoption/option.go @@ -1,13 +1,13 @@ package downloaderoption import ( - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/download/handlers" + _ "ocm.software/ocm/api/ocm/extensions/download/handlers" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/optutils" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "github.com/open-component-model/ocm/pkg/listformat" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/download" + "ocm.software/ocm/api/utils/listformat" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/optutils" + "ocm.software/ocm/cmds/ocm/common/options" ) type Registration = optutils.Registration diff --git a/cmds/ocm/commands/ocmcmds/common/options/dryrunoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/dryrunoption/option.go index fc90ca944..0b7880617 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/dryrunoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/dryrunoption/option.go @@ -5,7 +5,7 @@ import ( "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/ocmcmds/common/options/fileoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/fileoption/option.go index 082ae8cbe..69a089474 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/fileoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/fileoption/option.go @@ -7,10 +7,10 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/pkg/cobrautils/flag" - "github.com/open-component-model/ocm/pkg/common/compression" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/cobrautils/flag" + "ocm.software/ocm/api/utils/compression" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/ocmcmds/common/options/hashoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/hashoption/option.go index f9bb38401..4eb900f44 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/hashoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/hashoption/option.go @@ -4,15 +4,15 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/spf13/pflag" - "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/attrs/signingattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/normalizations/jsonv1" - ocmsign "github.com/open-component-model/ocm/pkg/contexts/ocm/signing" - "github.com/open-component-model/ocm/pkg/listformat" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/hasher/sha256" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/compdesc/normalizations/jsonv1" + "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" + ocmsign "ocm.software/ocm/api/ocm/tools/signing" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/hasher/sha256" + "ocm.software/ocm/api/utils/listformat" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/ocmcmds/common/options/keepglobaloption/option.go b/cmds/ocm/commands/ocmcmds/common/options/keepglobaloption/option.go index b2f4ea019..ecc39ab01 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/keepglobaloption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/keepglobaloption/option.go @@ -3,9 +3,9 @@ package keepglobaloption import ( "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/ocmcmds/common/options/lookupoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/lookupoption/option.go index 38fb8738f..5a106b871 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/lookupoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/lookupoption/option.go @@ -3,11 +3,11 @@ package lookupoption import ( "github.com/spf13/pflag" - "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" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/ocmcmds/common/options/omitaccesstypeoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/omitaccesstypeoption/option.go index 8329e58d7..b08846420 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/omitaccesstypeoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/omitaccesstypeoption/option.go @@ -3,9 +3,9 @@ package omitaccesstypeoption import ( "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/ocmcmds/common/options/optutils/reg_test.go b/cmds/ocm/commands/ocmcmds/common/options/optutils/reg_test.go index 34f6dfd13..edca672cd 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/optutils/reg_test.go +++ b/cmds/ocm/commands/ocmcmds/common/options/optutils/reg_test.go @@ -9,9 +9,9 @@ import ( "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/optutils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/env" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/helper/env" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/optutils" ) var _ = Describe("registration options", func() { diff --git a/cmds/ocm/commands/ocmcmds/common/options/optutils/registration.go b/cmds/ocm/commands/ocmcmds/common/options/optutils/registration.go index ce1566e96..aaa704041 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/optutils/registration.go +++ b/cmds/ocm/commands/ocmcmds/common/options/optutils/registration.go @@ -9,9 +9,9 @@ import ( "github.com/spf13/pflag" "sigs.k8s.io/yaml" - "github.com/open-component-model/ocm/pkg/cobrautils/flag" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/utils" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/cobrautils/flag" ) type Registration struct { diff --git a/cmds/ocm/commands/ocmcmds/common/options/overwriteoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/overwriteoption/option.go index 3bdf0c67c..a930ad6d2 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/overwriteoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/overwriteoption/option.go @@ -3,10 +3,10 @@ package overwriteoption import ( "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/pkg/cobrautils/flag" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/api/utils/cobrautils/flag" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/ocmcmds/common/options/repooption/option.go b/cmds/ocm/commands/ocmcmds/common/options/repooption/option.go index f92239878..51134b6c7 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/repooption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/repooption/option.go @@ -3,14 +3,14 @@ package repooption import ( "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" - "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/oci" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/genericocireg" - "github.com/open-component-model/ocm/pkg/listformat" - "github.com/open-component-model/ocm/pkg/runtime" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/repositories/genericocireg" + "ocm.software/ocm/api/utils/listformat" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/ocmcmds/common/options/rscbyvalueoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/rscbyvalueoption/option.go index 1d259f33a..fa7b20a82 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/rscbyvalueoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/rscbyvalueoption/option.go @@ -3,10 +3,10 @@ package rscbyvalueoption import ( "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/pkg/cobrautils/flag" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/api/utils/cobrautils/flag" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/ocmcmds/common/options/schemaoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/schemaoption/option.go index 5e463f005..2ee7bf480 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/schemaoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/schemaoption/option.go @@ -4,12 +4,12 @@ import ( "github.com/mandelsoft/goutils/errors" "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" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + utils2 "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/errkind" + "ocm.software/ocm/api/utils/listformat" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/ocmcmds/common/options/scriptoption/config.go b/cmds/ocm/commands/ocmcmds/common/options/scriptoption/config.go index b85962310..819d0b725 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/scriptoption/config.go +++ b/cmds/ocm/commands/ocmcmds/common/options/scriptoption/config.go @@ -6,9 +6,9 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/vfs/pkg/vfs" - cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" + cfgcpi "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/common/options/scriptoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/scriptoption/option.go index a6d493e31..ff8acf3c7 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/scriptoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/scriptoption/option.go @@ -5,12 +5,12 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/spiff" - "github.com/open-component-model/ocm/pkg/utils" + clictx "ocm.software/ocm/api/cli" + cfgcpi "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/spiff" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go index 131f1a900..6e27d1d0d 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/signoption/option.go @@ -7,19 +7,19 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/keyoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/hashoption" - "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/attrs/signingattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/normalizations/jsonv1" - ocmsign "github.com/open-component-model/ocm/pkg/contexts/ocm/signing" - "github.com/open-component-model/ocm/pkg/listformat" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" - "github.com/open-component-model/ocm/pkg/signing/hasher/sha256" - "github.com/open-component-model/ocm/pkg/signing/signutils" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/compdesc/normalizations/jsonv1" + "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" + ocmsign "ocm.software/ocm/api/ocm/tools/signing" + "ocm.software/ocm/api/tech/signing" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/api/tech/signing/hasher/sha256" + "ocm.software/ocm/api/tech/signing/signutils" + "ocm.software/ocm/api/utils/listformat" + "ocm.software/ocm/cmds/ocm/commands/common/options/keyoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/hashoption" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/ocmcmds/common/options/skipdigestoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/skipdigestoption/option.go index e6052a34b..0eb27e72d 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/skipdigestoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/skipdigestoption/option.go @@ -3,9 +3,9 @@ package skipdigestoption import ( "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/pkg/cobrautils/flag" - "github.com/open-component-model/ocm/pkg/contexts/ocm" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/utils/cobrautils/flag" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/ocmcmds/common/options/skipupdateoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/skipupdateoption/option.go index 39fece1c6..cf16b1710 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/skipupdateoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/skipupdateoption/option.go @@ -3,10 +3,10 @@ package skipupdateoption import ( "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/pkg/cobrautils/flag" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/api/utils/cobrautils/flag" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/ocmcmds/common/options/srcbyvalueoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/srcbyvalueoption/option.go index e6f3fd8fd..25e5f0af2 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/srcbyvalueoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/srcbyvalueoption/option.go @@ -3,10 +3,10 @@ package srcbyvalueoption import ( "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/pkg/cobrautils/flag" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/api/utils/cobrautils/flag" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/ocmcmds/common/options/stoponexistingoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/stoponexistingoption/option.go index 9a55a3666..34363f414 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/stoponexistingoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/stoponexistingoption/option.go @@ -3,10 +3,10 @@ package stoponexistingoption import ( "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/pkg/cobrautils/flag" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/api/utils/cobrautils/flag" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/ocmcmds/common/options/templateroption/option.go b/cmds/ocm/commands/ocmcmds/common/options/templateroption/option.go index efe98c4ca..376dddddb 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/templateroption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/templateroption/option.go @@ -1,9 +1,9 @@ package templateroption import ( - "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/utils/template" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/utils/template" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/ocmcmds/common/options/uploaderoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/uploaderoption/option.go index be11fc982..7c716bc7f 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/uploaderoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/uploaderoption/option.go @@ -1,11 +1,11 @@ package uploaderoption import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/optutils" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler" - "github.com/open-component-model/ocm/pkg/listformat" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/blobhandler" + "ocm.software/ocm/api/utils/listformat" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/optutils" + "ocm.software/ocm/cmds/ocm/common/options" ) type Registration = optutils.Registration diff --git a/cmds/ocm/commands/ocmcmds/common/options/uploaderoption/uploader_test.go b/cmds/ocm/commands/ocmcmds/common/options/uploaderoption/uploader_test.go index 72885ff35..8dbd4404e 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/uploaderoption/uploader_test.go +++ b/cmds/ocm/commands/ocmcmds/common/options/uploaderoption/uploader_test.go @@ -9,8 +9,8 @@ import ( "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/optutils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/optutils" ) var _ = Describe("uploader option", func() { diff --git a/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption/option.go index 183ef8243..fb8d140a3 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption/option.go @@ -4,9 +4,9 @@ import ( "github.com/Masterminds/semver/v3" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/pkg/cobrautils/flag" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/cobrautils/flag" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/ocmcmds/common/resources.go b/cmds/ocm/commands/ocmcmds/common/resources.go index db8b5697b..404720cd8 100644 --- a/cmds/ocm/commands/ocmcmds/common/resources.go +++ b/cmds/ocm/commands/ocmcmds/common/resources.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" - _ "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types" + _ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types" "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/goutils/sliceutils" @@ -13,26 +13,26 @@ import ( "golang.org/x/text/cases" "golang.org/x/text/language" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/dryrunoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/fileoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/templateroption" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/common" - "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/clictx" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "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" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/repositories/comparch" + utils2 "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/api/utils/logging" + "ocm.software/ocm/api/utils/mime" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/dryrunoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/fileoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/templateroption" + "ocm.software/ocm/cmds/ocm/common/options" + "ocm.software/ocm/cmds/ocm/common/utils" ) const ComponentVersionTag = "" diff --git a/cmds/ocm/commands/ocmcmds/common/resources_test.go b/cmds/ocm/commands/ocmcmds/common/resources_test.go index 015146532..8f22b6821 100644 --- a/cmds/ocm/commands/ocmcmds/common/resources_test.go +++ b/cmds/ocm/commands/ocmcmds/common/resources_test.go @@ -4,7 +4,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" ) var _ = Describe("Blob Inputs", func() { diff --git a/cmds/ocm/commands/ocmcmds/common/utils.go b/cmds/ocm/commands/ocmcmds/common/utils.go index 5985d9468..5537d634b 100644 --- a/cmds/ocm/commands/ocmcmds/common/utils.go +++ b/cmds/ocm/commands/ocmcmds/common/utils.go @@ -6,12 +6,12 @@ import ( "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" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - utils2 "github.com/open-component-model/ocm/pkg/utils" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + utils2 "ocm.software/ocm/api/utils" + "ocm.software/ocm/cmds/ocm/common/options" ) func ConsumeIdentities(pattern bool, args []string, stop ...string) ([]metav1.Identity, []string, error) { diff --git a/cmds/ocm/commands/ocmcmds/componentarchive/cmd.go b/cmds/ocm/commands/ocmcmds/componentarchive/cmd.go index 0d055b37c..a053aa71e 100644 --- a/cmds/ocm/commands/ocmcmds/componentarchive/cmd.go +++ b/cmds/ocm/commands/ocmcmds/componentarchive/cmd.go @@ -3,11 +3,11 @@ package componentarchive import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/componentarchive/create" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/componentarchive/transfer" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/componentarchive/create" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/componentarchive/transfer" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/common/utils" ) var Names = names.ComponentArchive diff --git a/cmds/ocm/commands/ocmcmds/componentarchive/create/cmd.go b/cmds/ocm/commands/ocmcmds/componentarchive/create/cmd.go index 8f0c5e8ce..accfe0b1d 100644 --- a/cmds/ocm/commands/ocmcmds/componentarchive/create/cmd.go +++ b/cmds/ocm/commands/ocmcmds/componentarchive/create/cmd.go @@ -9,20 +9,20 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/formatoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/fileoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/schemaoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/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/clisupport" - "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/clictx" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/compatattr" - "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/contexts/ocm/repositories/comparch" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/attrs/compatattr" + "ocm.software/ocm/api/ocm/extensions/repositories/comparch" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/clisupport" + "ocm.software/ocm/cmds/ocm/commands/common/options/formatoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/fileoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/schemaoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/componentarchive/create/cmd_test.go b/cmds/ocm/commands/ocmcmds/componentarchive/create/cmd_test.go index 25e10efb1..3dc5cb858 100644 --- a/cmds/ocm/commands/ocmcmds/componentarchive/create/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/componentarchive/create/cmd_test.go @@ -3,12 +3,12 @@ package create_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - compdescv3 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/comparch" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + compdescv3 "ocm.software/ocm/api/ocm/compdesc/versions/ocm.software/v3alpha1" + "ocm.software/ocm/api/ocm/extensions/repositories/comparch" ) var _ = Describe("Test Environment", func() { diff --git a/cmds/ocm/commands/ocmcmds/componentarchive/transfer/cmd.go b/cmds/ocm/commands/ocmcmds/componentarchive/transfer/cmd.go index c5604bc0e..a2944b349 100644 --- a/cmds/ocm/commands/ocmcmds/componentarchive/transfer/cmd.go +++ b/cmds/ocm/commands/ocmcmds/componentarchive/transfer/cmd.go @@ -3,25 +3,25 @@ package transfer import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/formatoption" - ocmcommon "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/overwriteoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/rscbyvalueoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/skipupdateoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/srcbyvalueoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/common/accessobj" - "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/contexts/ocm/repositories/comparch" - "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/standard" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/repositories/comparch" + "ocm.software/ocm/api/ocm/tools/transfer" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/api/utils/accessobj" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/cmds/ocm/commands/common/options/formatoption" + ocmcommon "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/overwriteoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/rscbyvalueoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/skipupdateoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/srcbyvalueoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/options" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/components/add/cmd.go b/cmds/ocm/commands/ocmcmds/components/add/cmd.go index 9f70c383a..c6a62bb69 100644 --- a/cmds/ocm/commands/ocmcmds/components/add/cmd.go +++ b/cmds/ocm/commands/ocmcmds/components/add/cmd.go @@ -9,29 +9,29 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "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" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs/comp" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/dryrunoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/fileoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/rscbyvalueoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/schemaoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/templateroption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/uploaderoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - topicocmlabels "github.com/open-component-model/ocm/cmds/ocm/topics/ocm/labels" - common2 "github.com/open-component-model/ocm/pkg/common" - "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/clictx" - "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/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + common2 "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/cmds/ocm/commands/common/options/formatoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs/comp" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/dryrunoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/fileoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/rscbyvalueoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/schemaoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/templateroption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/uploaderoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" + topicocmlabels "ocm.software/ocm/cmds/ocm/topics/ocm/labels" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/components/add/cmd_test.go b/cmds/ocm/commands/ocmcmds/components/add/cmd_test.go index f22681e75..22435d1af 100644 --- a/cmds/ocm/commands/ocmcmds/components/add/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/components/add/cmd_test.go @@ -4,23 +4,23 @@ import ( . "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/oci/testhelper" - - "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/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "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/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - ocmutils "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/handlers/defaultmerge" - "github.com/open-component-model/ocm/pkg/mime" + . "ocm.software/ocm/api/oci/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + ocmutils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/ocm/valuemergehandler/handlers/defaultmerge" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/mime" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/components/check/cmd.go b/cmds/ocm/commands/ocmcmds/components/check/cmd.go index 3fed9536b..73e7b7152 100644 --- a/cmds/ocm/commands/ocmcmds/components/check/cmd.go +++ b/cmds/ocm/commands/ocmcmds/components/check/cmd.go @@ -7,20 +7,20 @@ import ( "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/util/json" - "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/failonerroroption" - 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/repooption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/common" - "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/contexts/ocm/utils/check" - utils2 "github.com/open-component-model/ocm/pkg/utils" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/ocmutils/check" + utils2 "ocm.software/ocm/api/utils" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/cmds/ocm/commands/common/options/failonerroroption" + ocmcommon "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/processing" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/components/check/cmd_test.go b/cmds/ocm/commands/ocmcmds/components/check/cmd_test.go index 8de3772eb..e2afb691b 100644 --- a/cmds/ocm/commands/ocmcmds/components/check/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/components/check/cmd_test.go @@ -6,13 +6,13 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" + "ocm.software/ocm/api/ocm" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/utils/accessio" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/components/check/options.go b/cmds/ocm/commands/ocmcmds/components/check/options.go index 83c6260be..b8d19e0f3 100644 --- a/cmds/ocm/commands/ocmcmds/components/check/options.go +++ b/cmds/ocm/commands/ocmcmds/components/check/options.go @@ -4,8 +4,8 @@ import ( "github.com/mandelsoft/goutils/optionutils" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils/check" + "ocm.software/ocm/api/ocm/ocmutils/check" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/ocmcmds/components/cmd.go b/cmds/ocm/commands/ocmcmds/components/cmd.go index 597cc1f09..b1406855f 100644 --- a/cmds/ocm/commands/ocmcmds/components/cmd.go +++ b/cmds/ocm/commands/ocmcmds/components/cmd.go @@ -3,18 +3,18 @@ package components import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/components/add" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/components/check" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/components/download" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/components/get" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/components/hash" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/components/list" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/components/sign" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/components/transfer" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/components/verify" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/components/add" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/components/check" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/components/download" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/components/get" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/components/hash" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/components/list" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/components/sign" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/components/transfer" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/components/verify" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/common/utils" ) var Names = names.Components diff --git a/cmds/ocm/commands/ocmcmds/components/download/cmd.go b/cmds/ocm/commands/ocmcmds/components/download/cmd.go index ce7ddbbfd..1254cf7b5 100644 --- a/cmds/ocm/commands/ocmcmds/components/download/cmd.go +++ b/cmds/ocm/commands/ocmcmds/components/download/cmd.go @@ -7,23 +7,23 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/spf13/cobra" - "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" - "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/repooption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "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/common" - "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/clictx" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/comparch" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer" - "github.com/open-component-model/ocm/pkg/out" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/repositories/comparch" + "ocm.software/ocm/api/ocm/tools/transfer" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/cmds/ocm/commands/common/options/destoption" + "ocm.software/ocm/cmds/ocm/commands/common/options/formatoption" + ocmcommon "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/components/download/cmd_test.go b/cmds/ocm/commands/ocmcmds/components/download/cmd_test.go index 6a971168c..285cff04a 100644 --- a/cmds/ocm/commands/ocmcmds/components/download/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/components/download/cmd_test.go @@ -6,17 +6,17 @@ import ( . "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/testhelper" + . "ocm.software/ocm/api/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/common/accessio" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/grammar" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/comparch" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/mime" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/repositories/comparch" + "ocm.software/ocm/api/ocm/grammar" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/mime" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/components/get/cmd.go b/cmds/ocm/commands/ocmcmds/components/get/cmd.go index 09fbed63b..8b95482fa 100644 --- a/cmds/ocm/commands/ocmcmds/components/get/cmd.go +++ b/cmds/ocm/commands/ocmcmds/components/get/cmd.go @@ -5,23 +5,23 @@ import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/closureoption" - 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" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/schemaoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" - "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/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - compdescv2 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/versions/v2" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + compdescv2 "ocm.software/ocm/api/ocm/compdesc/versions/v2" + "ocm.software/ocm/cmds/ocm/commands/common/options/closureoption" + ocmcommon "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/schemaoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/options" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/processing" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/components/get/cmd_test.go b/cmds/ocm/commands/ocmcmds/components/get/cmd_test.go index c47627e93..9ac4a9901 100644 --- a/cmds/ocm/commands/ocmcmds/components/get/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/components/get/cmd_test.go @@ -7,10 +7,10 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" - "github.com/open-component-model/ocm/pkg/common/accessio" - compdescv3 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1" + compdescv3 "ocm.software/ocm/api/ocm/compdesc/versions/ocm.software/v3alpha1" + "ocm.software/ocm/api/utils/accessio" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/components/hash/cmd.go b/cmds/ocm/commands/ocmcmds/components/hash/cmd.go index 902359e76..7a2c5a829 100644 --- a/cmds/ocm/commands/ocmcmds/components/hash/cmd.go +++ b/cmds/ocm/commands/ocmcmds/components/hash/cmd.go @@ -8,22 +8,22 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/closureoption" - 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/hashoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/common" - "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/contexts/ocm/compdesc" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/cmds/ocm/commands/common/options/closureoption" + ocmcommon "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/hashoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/processing" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/components/hash/cmd_test.go b/cmds/ocm/commands/ocmcmds/components/hash/cmd_test.go index 45e037a7e..baa23fd4c 100644 --- a/cmds/ocm/commands/ocmcmds/components/hash/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/components/hash/cmd_test.go @@ -6,13 +6,13 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" - "github.com/open-component-model/ocm/pkg/common/accessio" - "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/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/mime" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/mime" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/components/hash/options.go b/cmds/ocm/commands/ocmcmds/components/hash/options.go index edc82961b..d3165c732 100644 --- a/cmds/ocm/commands/ocmcmds/components/hash/options.go +++ b/cmds/ocm/commands/ocmcmds/components/hash/options.go @@ -3,13 +3,13 @@ package hash import ( "github.com/spf13/pflag" - signingcmd "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/cmds/signing" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/hashoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm/signing" + "ocm.software/ocm/api/ocm/tools/signing" + common "ocm.software/ocm/api/utils/misc" + signingcmd "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/cmds/signing" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/hashoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/ocmcmds/components/list/cmd.go b/cmds/ocm/commands/ocmcmds/components/list/cmd.go index 3d3b20652..647284454 100644 --- a/cmds/ocm/commands/ocmcmds/components/list/cmd.go +++ b/cmds/ocm/commands/ocmcmds/components/list/cmd.go @@ -5,19 +5,19 @@ import ( "github.com/spf13/cobra" - ocmcommon "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/handlers/vershdlr" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/schemaoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" - "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/ocm" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + ocmcommon "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/vershdlr" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/schemaoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/processing" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/components/list/cmd_test.go b/cmds/ocm/commands/ocmcmds/components/list/cmd_test.go index 09fc29368..d5a453deb 100644 --- a/cmds/ocm/commands/ocmcmds/components/list/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/components/list/cmd_test.go @@ -7,9 +7,9 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" - "github.com/open-component-model/ocm/pkg/common/accessio" + "ocm.software/ocm/api/utils/accessio" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/components/sign/cmd.go b/cmds/ocm/commands/ocmcmds/components/sign/cmd.go index e38503bd3..682bf5a58 100644 --- a/cmds/ocm/commands/ocmcmds/components/sign/cmd.go +++ b/cmds/ocm/commands/ocmcmds/components/sign/cmd.go @@ -3,11 +3,11 @@ package sign import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/cmds/signing" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/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" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/cmds/signing" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/components/sign/cmd_test.go b/cmds/ocm/commands/ocmcmds/components/sign/cmd_test.go index e8fdad0b6..df586218a 100644 --- a/cmds/ocm/commands/ocmcmds/components/sign/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/components/sign/cmd_test.go @@ -7,23 +7,23 @@ import ( . "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/oci/testhelper" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/testhelper" + . "ocm.software/ocm/api/oci/testhelper" + . "ocm.software/ocm/api/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" "github.com/mandelsoft/vfs/pkg/vfs" - "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/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" - "github.com/open-component-model/ocm/pkg/signing/signutils" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/oci" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/api/tech/signing/signutils" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/mime" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/components/transfer/cmd.go b/cmds/ocm/commands/ocmcmds/components/transfer/cmd.go index 30aa49688..805760dc0 100644 --- a/cmds/ocm/commands/ocmcmds/components/transfer/cmd.go +++ b/cmds/ocm/commands/ocmcmds/components/transfer/cmd.go @@ -10,33 +10,33 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "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" - "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" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/omitaccesstypeoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/overwriteoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/rscbyvalueoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/scriptoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/skipupdateoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/srcbyvalueoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/stoponexistingoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/uploaderoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "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/common" - "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/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" - "github.com/open-component-model/ocm/pkg/out" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/tools/transfer" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/spiff" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/cmds/ocm/commands/common/options/closureoption" + "ocm.software/ocm/cmds/ocm/commands/common/options/formatoption" + ocmcommon "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/omitaccesstypeoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/overwriteoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/rscbyvalueoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/scriptoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/skipupdateoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/srcbyvalueoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/stoponexistingoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/uploaderoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/options" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/components/transfer/cmd_test.go b/cmds/ocm/commands/ocmcmds/components/transfer/cmd_test.go index 835f12c97..51948a7ad 100644 --- a/cmds/ocm/commands/ocmcmds/components/transfer/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/components/transfer/cmd_test.go @@ -7,27 +7,27 @@ import ( . "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/oci/testhelper" + . "ocm.software/ocm/api/oci/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" "github.com/spf13/cobra" - "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/clictx" - "github.com/open-component-model/ocm/pkg/contexts/config/config" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - ctfocm "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - handlercfg "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/config" - ocmutils "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/utils" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/config/extensions/config" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + ctfocm "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + ocmutils "ocm.software/ocm/api/ocm/ocmutils" + handlercfg "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/config" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/mime" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/components/transfer/disableupload_test.go b/cmds/ocm/commands/ocmcmds/components/transfer/disableupload_test.go index 7bb791b0c..3a13c5fc9 100644 --- a/cmds/ocm/commands/ocmcmds/components/transfer/disableupload_test.go +++ b/cmds/ocm/commands/ocmcmds/components/transfer/disableupload_test.go @@ -6,22 +6,22 @@ import ( . "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/oci/testhelper" - - "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/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - ocictf "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - storagecontext "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/oci" - "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/oci/ocirepo" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - ctfocm "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" + . "ocm.software/ocm/api/oci/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + ocictf "ocm.software/ocm/api/oci/extensions/repositories/ctf" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + storagecontext "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/oci" + "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/oci/ocirepo" + ctfocm "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" ) const BASEURL = "baseurl.io" diff --git a/cmds/ocm/commands/ocmcmds/components/transfer/upload_test.go b/cmds/ocm/commands/ocmcmds/components/transfer/upload_test.go index 53224d6c1..181793344 100644 --- a/cmds/ocm/commands/ocmcmds/components/transfer/upload_test.go +++ b/cmds/ocm/commands/ocmcmds/components/transfer/upload_test.go @@ -8,23 +8,23 @@ import ( . "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/oci/testhelper" - - "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/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/grammar" - ctfoci "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/ociuploadattr" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/comparch" - ctfocm "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" - "github.com/open-component-model/ocm/pkg/mime" + . "ocm.software/ocm/api/oci/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" + + "ocm.software/ocm/api/oci" + ctfoci "ocm.software/ocm/api/oci/extensions/repositories/ctf" + "ocm.software/ocm/api/oci/grammar" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/attrs/ociuploadattr" + "ocm.software/ocm/api/ocm/extensions/repositories/comparch" + ctfocm "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/tools/transfer" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/mime" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/components/verify/cmd.go b/cmds/ocm/commands/ocmcmds/components/verify/cmd.go index 048452a19..c161a091c 100644 --- a/cmds/ocm/commands/ocmcmds/components/verify/cmd.go +++ b/cmds/ocm/commands/ocmcmds/components/verify/cmd.go @@ -3,11 +3,11 @@ package verify import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/cmds/signing" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/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" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/cmds/signing" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/components/verify/cmd_test.go b/cmds/ocm/commands/ocmcmds/components/verify/cmd_test.go index 7e483b8a3..14e1dd409 100644 --- a/cmds/ocm/commands/ocmcmds/components/verify/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/components/verify/cmd_test.go @@ -7,25 +7,25 @@ import ( . "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/oci/testhelper" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/signing" + . "ocm.software/ocm/api/oci/testhelper" + . "ocm.software/ocm/api/ocm/tools/signing" + . "ocm.software/ocm/cmds/ocm/testhelper" "github.com/mandelsoft/logging" "github.com/mandelsoft/vfs/pkg/vfs" - "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/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/mime" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/ctf/cmd.go b/cmds/ocm/commands/ocmcmds/ctf/cmd.go index 657963d9a..c2c66daea 100644 --- a/cmds/ocm/commands/ocmcmds/ctf/cmd.go +++ b/cmds/ocm/commands/ocmcmds/ctf/cmd.go @@ -3,10 +3,10 @@ package ctf import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/ctf/transfer" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/ctf/transfer" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/common/utils" ) var Names = names.CommonTransportArchive diff --git a/cmds/ocm/commands/ocmcmds/ctf/transfer/cmd.go b/cmds/ocm/commands/ocmcmds/ctf/transfer/cmd.go index d57aefdae..269183828 100644 --- a/cmds/ocm/commands/ocmcmds/ctf/transfer/cmd.go +++ b/cmds/ocm/commands/ocmcmds/ctf/transfer/cmd.go @@ -4,30 +4,30 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/spf13/cobra" - "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" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/omitaccesstypeoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/overwriteoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/rscbyvalueoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/scriptoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/skipupdateoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/srcbyvalueoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/stoponexistingoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/uploaderoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/common/accessobj" - "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/contexts/ocm/repositories/ctf" - "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" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/tools/transfer" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/spiff" + "ocm.software/ocm/api/utils/accessobj" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/cmds/ocm/commands/common/options/closureoption" + "ocm.software/ocm/cmds/ocm/commands/common/options/formatoption" + ocmcommon "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/omitaccesstypeoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/overwriteoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/rscbyvalueoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/scriptoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/skipupdateoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/srcbyvalueoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/stoponexistingoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/uploaderoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/options" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/ctf/transfer/cmd_test.go b/cmds/ocm/commands/ocmcmds/ctf/transfer/cmd_test.go index 0075c3b0d..6fbb4906d 100644 --- a/cmds/ocm/commands/ocmcmds/ctf/transfer/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/ctf/transfer/cmd_test.go @@ -7,18 +7,18 @@ import ( . "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/oci/testhelper" - - "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/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - ctfocm "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/mime" + . "ocm.software/ocm/api/oci/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" + + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/oci/artdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + ctfocm "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/mime" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/init.go b/cmds/ocm/commands/ocmcmds/init.go index aa571e56c..d4919614f 100644 --- a/cmds/ocm/commands/ocmcmds/init.go +++ b/cmds/ocm/commands/ocmcmds/init.go @@ -1,5 +1,5 @@ package ocmcmds import ( - _ "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types" + _ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types" ) diff --git a/cmds/ocm/commands/ocmcmds/plugins/cmd.go b/cmds/ocm/commands/ocmcmds/plugins/cmd.go index e7b3bb929..5b7a8e823 100644 --- a/cmds/ocm/commands/ocmcmds/plugins/cmd.go +++ b/cmds/ocm/commands/ocmcmds/plugins/cmd.go @@ -3,12 +3,12 @@ package plugins import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/plugins/describe" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/plugins/get" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/plugins/install" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/plugins/describe" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/plugins/get" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/plugins/install" + "ocm.software/ocm/cmds/ocm/common/utils" ) var Names = names.Plugins diff --git a/cmds/ocm/commands/ocmcmds/plugins/describe/cmd.go b/cmds/ocm/commands/ocmcmds/plugins/describe/cmd.go index ea568cf15..e65d06c0e 100644 --- a/cmds/ocm/commands/ocmcmds/plugins/describe/cmd.go +++ b/cmds/ocm/commands/ocmcmds/plugins/describe/cmd.go @@ -5,13 +5,13 @@ import ( "github.com/spf13/cobra" - handler "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/handlers/pluginhdlr" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/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/cobrautils" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/utils/cobrautils" + common "ocm.software/ocm/api/utils/misc" + handler "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/pluginhdlr" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/plugins/describe/cmd_test.go b/cmds/ocm/commands/ocmcmds/plugins/describe/cmd_test.go index 28e6c866e..3c7dd03c4 100644 --- a/cmds/ocm/commands/ocmcmds/plugins/describe/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/plugins/describe/cmd_test.go @@ -8,8 +8,8 @@ import ( . "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/plugin/testutils" + . "ocm.software/ocm/api/ocm/plugin/testutils" + . "ocm.software/ocm/cmds/ocm/testhelper" ) const PLUGINS = "/testdata" diff --git a/cmds/ocm/commands/ocmcmds/plugins/describe/describe.go b/cmds/ocm/commands/ocmcmds/plugins/describe/describe.go index 8bd6969f7..bc667eac5 100644 --- a/cmds/ocm/commands/ocmcmds/plugins/describe/describe.go +++ b/cmds/ocm/commands/ocmcmds/plugins/describe/describe.go @@ -3,9 +3,9 @@ package describe import ( "encoding/json" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin" - plugincommon "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/common" + "ocm.software/ocm/api/ocm/plugin" + plugincommon "ocm.software/ocm/api/ocm/plugin/common" + common "ocm.software/ocm/api/utils/misc" ) func DescribePlugin(p plugin.Plugin, out common.Printer) { diff --git a/cmds/ocm/commands/ocmcmds/plugins/get/cmd.go b/cmds/ocm/commands/ocmcmds/plugins/get/cmd.go index 18da58a2a..8f8edee00 100644 --- a/cmds/ocm/commands/ocmcmds/plugins/get/cmd.go +++ b/cmds/ocm/commands/ocmcmds/plugins/get/cmd.go @@ -8,16 +8,16 @@ import ( "github.com/mandelsoft/goutils/set" "github.com/spf13/cobra" - handler "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/handlers/pluginhdlr" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" - "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/ocm/plugin" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/common" - utils2 "github.com/open-component-model/ocm/pkg/utils" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm/plugin" + "ocm.software/ocm/api/ocm/plugin/common" + utils2 "ocm.software/ocm/api/utils" + handler "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/pluginhdlr" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/processing" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/plugins/get/cmd_test.go b/cmds/ocm/commands/ocmcmds/plugins/get/cmd_test.go index 4ffb886a3..92af1c7f9 100644 --- a/cmds/ocm/commands/ocmcmds/plugins/get/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/plugins/get/cmd_test.go @@ -8,8 +8,8 @@ import ( . "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/plugin/testutils" + . "ocm.software/ocm/api/ocm/plugin/testutils" + . "ocm.software/ocm/cmds/ocm/testhelper" ) const PLUGINS = "/testdata" diff --git a/cmds/ocm/commands/ocmcmds/plugins/install/cmd.go b/cmds/ocm/commands/ocmcmds/plugins/install/cmd.go index d39692ef1..51403dba7 100644 --- a/cmds/ocm/commands/ocmcmds/plugins/install/cmd.go +++ b/cmds/ocm/commands/ocmcmds/plugins/install/cmd.go @@ -7,19 +7,19 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - 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" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/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/cobrautils/flag" - "github.com/open-component-model/ocm/pkg/common" - "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/contexts/ocm/attrs/plugincacheattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/cache" - "github.com/open-component-model/ocm/pkg/out" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/attrs/plugincacheattr" + "ocm.software/ocm/api/ocm/plugin/cache" + "ocm.software/ocm/api/utils/cobrautils/flag" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/out" + ocmcommon "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/pluginhdlr" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/plugins/tests/accessmethods/cmd_test.go b/cmds/ocm/commands/ocmcmds/plugins/tests/accessmethods/cmd_test.go index d6f3c973b..bd316ea2d 100644 --- a/cmds/ocm/commands/ocmcmds/plugins/tests/accessmethods/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/plugins/tests/accessmethods/cmd_test.go @@ -6,16 +6,16 @@ import ( . "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/plugin/testutils" + . "ocm.software/ocm/api/ocm/plugin/testutils" + . "ocm.software/ocm/cmds/ocm/testhelper" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "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/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/plugins" - "github.com/open-component-model/ocm/pkg/contexts/ocm/registration" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/comparch" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/repositories/comparch" + "ocm.software/ocm/api/ocm/plugin/plugins" + "ocm.software/ocm/api/ocm/plugin/registration" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/plugins/tests/routingslips/cmd_test.go b/cmds/ocm/commands/ocmcmds/plugins/tests/routingslips/cmd_test.go index 4d8ac3574..ae99f10e9 100644 --- a/cmds/ocm/commands/ocmcmds/plugins/tests/routingslips/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/plugins/tests/routingslips/cmd_test.go @@ -6,15 +6,15 @@ import ( . "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/plugin/testutils" + . "ocm.software/ocm/api/ocm/plugin/testutils" + . "ocm.software/ocm/cmds/ocm/testhelper" - "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/ocm/attrs/plugincacheattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip" - "github.com/open-component-model/ocm/pkg/contexts/ocm/registration" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" + "ocm.software/ocm/api/ocm/extensions/attrs/plugincacheattr" + "ocm.software/ocm/api/ocm/extensions/labels/routingslip" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/plugin/registration" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/pubsub/cmd.go b/cmds/ocm/commands/ocmcmds/pubsub/cmd.go index 478e40da3..2beb43c93 100644 --- a/cmds/ocm/commands/ocmcmds/pubsub/cmd.go +++ b/cmds/ocm/commands/ocmcmds/pubsub/cmd.go @@ -3,11 +3,11 @@ package pubsub import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/pubsub/get" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/pubsub/set" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/pubsub/get" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/pubsub/set" + "ocm.software/ocm/cmds/ocm/common/utils" ) var Names = names.PubSub diff --git a/cmds/ocm/commands/ocmcmds/pubsub/get/cmd.go b/cmds/ocm/commands/ocmcmds/pubsub/get/cmd.go index c314bb608..94c36e614 100644 --- a/cmds/ocm/commands/ocmcmds/pubsub/get/cmd.go +++ b/cmds/ocm/commands/ocmcmds/pubsub/get/cmd.go @@ -6,15 +6,15 @@ import ( "github.com/mandelsoft/goutils/sliceutils" "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" - "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/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/pubsub" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/processing" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/pubsub/get/cmd_test.go b/cmds/ocm/commands/ocmcmds/pubsub/get/cmd_test.go index 7f55a5fc2..7dab7842a 100644 --- a/cmds/ocm/commands/ocmcmds/pubsub/get/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/pubsub/get/cmd_test.go @@ -7,14 +7,14 @@ import ( . "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/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub" - "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub/providers/ocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/runtime" + . "ocm.software/ocm/cmds/ocm/testhelper" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/pubsub" + "ocm.software/ocm/api/ocm/extensions/pubsub/providers/ocireg" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/runtime" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/pubsub/set/cmd.go b/cmds/ocm/commands/ocmcmds/pubsub/set/cmd.go index ae958e84a..e49f68323 100644 --- a/cmds/ocm/commands/ocmcmds/pubsub/set/cmd.go +++ b/cmds/ocm/commands/ocmcmds/pubsub/set/cmd.go @@ -6,15 +6,15 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/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/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub" - "github.com/open-component-model/ocm/pkg/out" - utils2 "github.com/open-component-model/ocm/pkg/utils" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/pubsub" + utils2 "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/pubsub/set/cmd_test.go b/cmds/ocm/commands/ocmcmds/pubsub/set/cmd_test.go index 1c27c943f..3c7a71b7d 100644 --- a/cmds/ocm/commands/ocmcmds/pubsub/set/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/pubsub/set/cmd_test.go @@ -8,14 +8,14 @@ import ( . "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/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub" - "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub/providers/ocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/runtime" + . "ocm.software/ocm/cmds/ocm/testhelper" + + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/pubsub" + "ocm.software/ocm/api/ocm/extensions/pubsub/providers/ocireg" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/runtime" ) const ARCH = "ctf" diff --git a/cmds/ocm/commands/ocmcmds/references/add/cmd.go b/cmds/ocm/commands/ocmcmds/references/add/cmd.go index 77f2952c9..89e44388f 100644 --- a/cmds/ocm/commands/ocmcmds/references/add/cmd.go +++ b/cmds/ocm/commands/ocmcmds/references/add/cmd.go @@ -3,14 +3,14 @@ package add import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs/refs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/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/utils/template" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/utils/template" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs/refs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/references/add/cmd_test.go b/cmds/ocm/commands/ocmcmds/references/add/cmd_test.go index 45bd0774b..cde4c0db6 100644 --- a/cmds/ocm/commands/ocmcmds/references/add/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/references/add/cmd_test.go @@ -5,13 +5,13 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" "github.com/mandelsoft/goutils/testutils" - "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/contexts/ocm/repositories/comparch" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/repositories/comparch" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/references/add/provider.go b/cmds/ocm/commands/ocmcmds/references/add/provider.go index a0a948c58..f1fd431dc 100644 --- a/cmds/ocm/commands/ocmcmds/references/add/provider.go +++ b/cmds/ocm/commands/ocmcmds/references/add/provider.go @@ -3,9 +3,9 @@ package add import ( "encoding/json" - ocmcomm "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + ocmcomm "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" ) type ReferenceResourceSpecificationProvider struct { diff --git a/cmds/ocm/commands/ocmcmds/references/cmd.go b/cmds/ocm/commands/ocmcmds/references/cmd.go index 798a0503b..c04fd090c 100644 --- a/cmds/ocm/commands/ocmcmds/references/cmd.go +++ b/cmds/ocm/commands/ocmcmds/references/cmd.go @@ -3,11 +3,11 @@ package references import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/references/add" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/references/get" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/references/add" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/references/get" + "ocm.software/ocm/cmds/ocm/common/utils" ) var Names = names.References diff --git a/cmds/ocm/commands/ocmcmds/references/common/typehandler.go b/cmds/ocm/commands/ocmcmds/references/common/typehandler.go index 1d603f50c..5bb267644 100644 --- a/cmds/ocm/commands/ocmcmds/references/common/typehandler.go +++ b/cmds/ocm/commands/ocmcmds/references/common/typehandler.go @@ -1,12 +1,12 @@ package common import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr" - "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" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/utils" ) func Elem(e interface{}) *compdesc.ComponentReference { diff --git a/cmds/ocm/commands/ocmcmds/references/get/cmd.go b/cmds/ocm/commands/ocmcmds/references/get/cmd.go index 3f61c030b..ce56a5f10 100644 --- a/cmds/ocm/commands/ocmcmds/references/get/cmd.go +++ b/cmds/ocm/commands/ocmcmds/references/get/cmd.go @@ -3,22 +3,22 @@ package get import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/closureoption" - ocmcommon "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/references/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "github.com/open-component-model/ocm/cmds/ocm/pkg/data" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" - "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/ocm" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/cmds/ocm/commands/common/options/closureoption" + ocmcommon "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/references/common" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/data" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/processing" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/references/get/cmd_test.go b/cmds/ocm/commands/ocmcmds/references/get/cmd_test.go index 873347e84..d35fdf631 100644 --- a/cmds/ocm/commands/ocmcmds/references/get/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/references/get/cmd_test.go @@ -6,9 +6,9 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" - "github.com/open-component-model/ocm/pkg/common/accessio" + "ocm.software/ocm/api/utils/accessio" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/resourceconfig/add/cmd.go b/cmds/ocm/commands/ocmcmds/resourceconfig/add/cmd.go index 826cc8278..f4d32c2f1 100644 --- a/cmds/ocm/commands/ocmcmds/resourceconfig/add/cmd.go +++ b/cmds/ocm/commands/ocmcmds/resourceconfig/add/cmd.go @@ -3,16 +3,16 @@ package add import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs/rscs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - rscadd "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/resources/add" - "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/contexts/ocm" - "github.com/open-component-model/ocm/pkg/utils/template" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/utils/template" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs/rscs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + rscadd "ocm.software/ocm/cmds/ocm/commands/ocmcmds/resources/add" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/resourceconfig/add/cmd_test.go b/cmds/ocm/commands/ocmcmds/resourceconfig/add/cmd_test.go index b3b7b812d..2262dfa41 100644 --- a/cmds/ocm/commands/ocmcmds/resourceconfig/add/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/resourceconfig/add/cmd_test.go @@ -3,7 +3,7 @@ package add_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" "github.com/mandelsoft/goutils/testutils" ) diff --git a/cmds/ocm/commands/ocmcmds/resourceconfig/cmd.go b/cmds/ocm/commands/ocmcmds/resourceconfig/cmd.go index 9aff8887e..6eb6266a1 100644 --- a/cmds/ocm/commands/ocmcmds/resourceconfig/cmd.go +++ b/cmds/ocm/commands/ocmcmds/resourceconfig/cmd.go @@ -3,10 +3,10 @@ package resourceconfig import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/resourceconfig/add" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/resourceconfig/add" + "ocm.software/ocm/cmds/ocm/common/utils" ) var Names = names.ResourceConfig diff --git a/cmds/ocm/commands/ocmcmds/resources/add/cmd.go b/cmds/ocm/commands/ocmcmds/resources/add/cmd.go index 67f6d4b27..ffc430a9a 100644 --- a/cmds/ocm/commands/ocmcmds/resources/add/cmd.go +++ b/cmds/ocm/commands/ocmcmds/resources/add/cmd.go @@ -3,16 +3,16 @@ package add import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs/rscs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/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/contexts/ocm" - "github.com/open-component-model/ocm/pkg/utils/template" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/utils/template" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs/rscs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/resources/add/cmd_test.go b/cmds/ocm/commands/ocmcmds/resources/add/cmd_test.go index 4d54b08c1..bc3284214 100644 --- a/cmds/ocm/commands/ocmcmds/resources/add/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/resources/add/cmd_test.go @@ -8,26 +8,26 @@ import ( . "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/oci/testhelper" + . "ocm.software/ocm/api/oci/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" "github.com/mandelsoft/vfs/pkg/vfs" "github.com/opencontainers/go-digest" "helm.sh/helm/v3/pkg/chart/loader" - "github.com/open-component-model/ocm/pkg/blobaccess" - "github.com/open-component-model/ocm/pkg/common" - "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/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/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/contexts/ocm/repositories/comparch" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/mime" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/repositories/comparch" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/mime" + common "ocm.software/ocm/api/utils/misc" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/resources/add/provider.go b/cmds/ocm/commands/ocmcmds/resources/add/provider.go index 5224a9bee..c0d12405c 100644 --- a/cmds/ocm/commands/ocmcmds/resources/add/provider.go +++ b/cmds/ocm/commands/ocmcmds/resources/add/provider.go @@ -1,10 +1,10 @@ package add import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + clictx "ocm.software/ocm/api/cli" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" ) type ResourceSpecificationsProvider struct { diff --git a/cmds/ocm/commands/ocmcmds/resources/cmd.go b/cmds/ocm/commands/ocmcmds/resources/cmd.go index eab7809ec..07b3cc3dd 100644 --- a/cmds/ocm/commands/ocmcmds/resources/cmd.go +++ b/cmds/ocm/commands/ocmcmds/resources/cmd.go @@ -3,12 +3,12 @@ package resources import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/resources/add" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/resources/download" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/resources/get" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/resources/add" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/resources/download" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/resources/get" + "ocm.software/ocm/cmds/ocm/common/utils" ) var Names = names.Resources diff --git a/cmds/ocm/commands/ocmcmds/resources/common/typehandler.go b/cmds/ocm/commands/ocmcmds/resources/common/typehandler.go index 3e57e12e3..648463d3c 100644 --- a/cmds/ocm/commands/ocmcmds/resources/common/typehandler.go +++ b/cmds/ocm/commands/ocmcmds/resources/common/typehandler.go @@ -1,12 +1,12 @@ package common import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr" - "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" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/utils" ) func Elem(e interface{}) *compdesc.Resource { diff --git a/cmds/ocm/commands/ocmcmds/resources/download/action.go b/cmds/ocm/commands/ocmcmds/resources/download/action.go index 98f1464e4..27d36e72a 100644 --- a/cmds/ocm/commands/ocmcmds/resources/download/action.go +++ b/cmds/ocm/commands/ocmcmds/resources/download/action.go @@ -9,16 +9,16 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/destoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/resources/common" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - common2 "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "github.com/open-component-model/ocm/pkg/out" + "ocm.software/ocm/api/ocm" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/download" + common2 "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/cmds/ocm/commands/common/options/destoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/resources/common" + "ocm.software/ocm/cmds/ocm/common/options" + "ocm.software/ocm/cmds/ocm/common/output" ) //////////////////////////////////////////////////////////////////////////////// diff --git a/cmds/ocm/commands/ocmcmds/resources/download/cmd.go b/cmds/ocm/commands/ocmcmds/resources/download/cmd.go index 38272f7a9..40cad63a8 100644 --- a/cmds/ocm/commands/ocmcmds/resources/download/cmd.go +++ b/cmds/ocm/commands/ocmcmds/resources/download/cmd.go @@ -6,24 +6,24 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/closureoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/destoption" - ocmcommon "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/downloaderoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/resources/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "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" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/extraid" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extraid" + "ocm.software/ocm/cmds/ocm/commands/common/options/closureoption" + "ocm.software/ocm/cmds/ocm/commands/common/options/destoption" + ocmcommon "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/downloaderoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/resources/common" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/resources/download/cmd_test.go b/cmds/ocm/commands/ocmcmds/resources/download/cmd_test.go index c522a5429..f7ed44fe2 100644 --- a/cmds/ocm/commands/ocmcmds/resources/download/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/resources/download/cmd_test.go @@ -6,13 +6,13 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/common/accessio" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/mime" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/mime" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/resources/download/dirtree_test.go b/cmds/ocm/commands/ocmcmds/resources/download/dirtree_test.go index ab764316d..64df2827f 100644 --- a/cmds/ocm/commands/ocmcmds/resources/download/dirtree_test.go +++ b/cmds/ocm/commands/ocmcmds/resources/download/dirtree_test.go @@ -7,18 +7,18 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" "github.com/mandelsoft/vfs/pkg/projectionfs" "github.com/mandelsoft/vfs/pkg/vfs" ociv1 "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - env2 "github.com/open-component-model/ocm/pkg/env" - "github.com/open-component-model/ocm/pkg/utils/tarutils" + env2 "ocm.software/ocm/api/helper/env" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/tarutils" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/resources/download/options.go b/cmds/ocm/commands/ocmcmds/resources/download/options.go index a23a78f64..f411d4b4c 100644 --- a/cmds/ocm/commands/ocmcmds/resources/download/options.go +++ b/cmds/ocm/commands/ocmcmds/resources/download/options.go @@ -3,8 +3,8 @@ package download import ( "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/cmds/ocm/common/output" ) func From(o *output.Options) *Option { diff --git a/cmds/ocm/commands/ocmcmds/resources/get/cmd.go b/cmds/ocm/commands/ocmcmds/resources/get/cmd.go index f5985d271..40265a6fc 100644 --- a/cmds/ocm/commands/ocmcmds/resources/get/cmd.go +++ b/cmds/ocm/commands/ocmcmds/resources/get/cmd.go @@ -4,21 +4,21 @@ import ( "github.com/mandelsoft/goutils/sliceutils" "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/closureoption" - ocmcommon "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/resources/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" - "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/ocm" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/cmds/ocm/commands/common/options/closureoption" + ocmcommon "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/resources/common" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/processing" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/resources/get/cmd_test.go b/cmds/ocm/commands/ocmcmds/resources/get/cmd_test.go index cceb039c7..3714de7cf 100644 --- a/cmds/ocm/commands/ocmcmds/resources/get/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/resources/get/cmd_test.go @@ -6,11 +6,11 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" - "github.com/open-component-model/ocm/pkg/common/accessio" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/mime" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/mime" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/resources/get/deep_test.go b/cmds/ocm/commands/ocmcmds/resources/get/deep_test.go index 5074fbf3e..9ceef72e0 100644 --- a/cmds/ocm/commands/ocmcmds/resources/get/deep_test.go +++ b/cmds/ocm/commands/ocmcmds/resources/get/deep_test.go @@ -6,11 +6,11 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" - "github.com/open-component-model/ocm/pkg/common/accessio" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/mime" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/mime" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/routingslips/add/cmd.go b/cmds/ocm/commands/ocmcmds/routingslips/add/cmd.go index 3ea5aa42c..21ecf271d 100644 --- a/cmds/ocm/commands/ocmcmds/routingslips/add/cmd.go +++ b/cmds/ocm/commands/ocmcmds/routingslips/add/cmd.go @@ -9,21 +9,21 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - 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" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "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/cobrautils/flagsets" - "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/contexts/ocm/labels/routingslip" - "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip/spi" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/labels/routingslip" + "ocm.software/ocm/api/ocm/extensions/labels/routingslip/spi" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/api/utils/runtime" + ocmcommon "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/utils" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/routingslips/add/cmd_test.go b/cmds/ocm/commands/ocmcmds/routingslips/add/cmd_test.go index 4c3e28583..5fbaa6e0c 100644 --- a/cmds/ocm/commands/ocmcmds/routingslips/add/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/routingslips/add/cmd_test.go @@ -6,13 +6,13 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" - "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/ocm/labels/routingslip" - "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip/types/comment" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" + "ocm.software/ocm/api/ocm/extensions/labels/routingslip" + "ocm.software/ocm/api/ocm/extensions/labels/routingslip/types/comment" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/routingslips/cmd.go b/cmds/ocm/commands/ocmcmds/routingslips/cmd.go index 3af64eca3..fdf4b8d67 100644 --- a/cmds/ocm/commands/ocmcmds/routingslips/cmd.go +++ b/cmds/ocm/commands/ocmcmds/routingslips/cmd.go @@ -3,11 +3,11 @@ package routingslips import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/routingslips/add" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/routingslips/get" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/routingslips/add" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/routingslips/get" + "ocm.software/ocm/cmds/ocm/common/utils" ) var Names = names.RoutingSlips diff --git a/cmds/ocm/commands/ocmcmds/routingslips/common/options.go b/cmds/ocm/commands/ocmcmds/routingslips/common/options.go index f6d1039f2..a3f3ee1c9 100644 --- a/cmds/ocm/commands/ocmcmds/routingslips/common/options.go +++ b/cmds/ocm/commands/ocmcmds/routingslips/common/options.go @@ -3,12 +3,12 @@ package common import ( "github.com/Masterminds/semver/v3" - "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" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" + "ocm.software/ocm/cmds/ocm/common/options" ) type Option interface { diff --git a/cmds/ocm/commands/ocmcmds/routingslips/common/typehandler.go b/cmds/ocm/commands/ocmcmds/routingslips/common/typehandler.go index 27f73da58..dfaa6fc60 100644 --- a/cmds/ocm/commands/ocmcmds/routingslips/common/typehandler.go +++ b/cmds/ocm/commands/ocmcmds/routingslips/common/typehandler.go @@ -5,13 +5,13 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr" - "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" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip" - utils2 "github.com/open-component-model/ocm/pkg/utils" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/labels/routingslip" + utils2 "ocm.software/ocm/api/utils" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/utils" ) type Object struct { diff --git a/cmds/ocm/commands/ocmcmds/routingslips/get/cmd.go b/cmds/ocm/commands/ocmcmds/routingslips/get/cmd.go index 8ddfb92a8..d4e0979a1 100644 --- a/cmds/ocm/commands/ocmcmds/routingslips/get/cmd.go +++ b/cmds/ocm/commands/ocmcmds/routingslips/get/cmd.go @@ -6,19 +6,19 @@ import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/failonerroroption" - ocmcommon "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/routingslips/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - common2 "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/contexts/ocm" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + common2 "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/cmds/ocm/commands/common/options/failonerroroption" + ocmcommon "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/routingslips/common" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/routingslips/get/cmd_test.go b/cmds/ocm/commands/ocmcmds/routingslips/get/cmd_test.go index 1c8fecc15..511e04ef2 100644 --- a/cmds/ocm/commands/ocmcmds/routingslips/get/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/routingslips/get/cmd_test.go @@ -7,16 +7,16 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" "github.com/mandelsoft/goutils/finalizer" - "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/ocm/labels/routingslip" - "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip/types/comment" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" + "ocm.software/ocm/api/ocm/extensions/labels/routingslip" + "ocm.software/ocm/api/ocm/extensions/labels/routingslip/types/comment" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/routingslips/get/options.go b/cmds/ocm/commands/ocmcmds/routingslips/get/options.go index 2741cdc2d..22f49f0a9 100644 --- a/cmds/ocm/commands/ocmcmds/routingslips/get/options.go +++ b/cmds/ocm/commands/ocmcmds/routingslips/get/options.go @@ -3,7 +3,7 @@ package get import ( "github.com/spf13/pflag" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" + "ocm.software/ocm/cmds/ocm/common/options" ) func From(o options.OptionSetProvider) *Option { diff --git a/cmds/ocm/commands/ocmcmds/sourceconfig/add/cmd.go b/cmds/ocm/commands/ocmcmds/sourceconfig/add/cmd.go index 6dad2388b..5ff6aefe8 100644 --- a/cmds/ocm/commands/ocmcmds/sourceconfig/add/cmd.go +++ b/cmds/ocm/commands/ocmcmds/sourceconfig/add/cmd.go @@ -3,16 +3,16 @@ package add import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" - rscadd "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs/srcs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/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/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/utils/template" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/utils/template" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + rscadd "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs/srcs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( @@ -40,7 +40,7 @@ func (o *Command) ForName(name string) *cobra.Command { Args: cobra.MinimumNArgs(1), Short: "add a source specification to a source config file", Example: ` -$ ocm add source-config sources.yaml --name sources --type filesystem --access '{ "type": "gitHub", "repoUrl": "github.com/open-component-model/ocm", "commit": "xyz" }' +$ ocm add source-config sources.yaml --name sources --type filesystem --access '{ "type": "gitHub", "repoUrl": "ocm.software/ocm", "commit": "xyz" }' `, } } diff --git a/cmds/ocm/commands/ocmcmds/sourceconfig/add/cmd_test.go b/cmds/ocm/commands/ocmcmds/sourceconfig/add/cmd_test.go index 18b3d3088..89189d449 100644 --- a/cmds/ocm/commands/ocmcmds/sourceconfig/add/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/sourceconfig/add/cmd_test.go @@ -3,7 +3,7 @@ package add_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" "github.com/mandelsoft/goutils/testutils" ) diff --git a/cmds/ocm/commands/ocmcmds/sourceconfig/cmd.go b/cmds/ocm/commands/ocmcmds/sourceconfig/cmd.go index c89bbf405..dc5b490df 100644 --- a/cmds/ocm/commands/ocmcmds/sourceconfig/cmd.go +++ b/cmds/ocm/commands/ocmcmds/sourceconfig/cmd.go @@ -3,10 +3,10 @@ package sourceconfig import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/sourceconfig/add" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/sourceconfig/add" + "ocm.software/ocm/cmds/ocm/common/utils" ) var Names = names.SourceConfig diff --git a/cmds/ocm/commands/ocmcmds/sources/add/cmd.go b/cmds/ocm/commands/ocmcmds/sources/add/cmd.go index db1874e13..6514c0062 100644 --- a/cmds/ocm/commands/ocmcmds/sources/add/cmd.go +++ b/cmds/ocm/commands/ocmcmds/sources/add/cmd.go @@ -3,16 +3,16 @@ package add import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs/srcs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/inputs" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/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/contexts/ocm" - "github.com/open-component-model/ocm/pkg/utils/template" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/utils/template" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs/srcs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/sources/add/cmd_test.go b/cmds/ocm/commands/ocmcmds/sources/add/cmd_test.go index 334eb82ae..d0a8d8ccf 100644 --- a/cmds/ocm/commands/ocmcmds/sources/add/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/sources/add/cmd_test.go @@ -8,15 +8,15 @@ import ( . "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/blobaccess" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "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/contexts/ocm/repositories/comparch" - "github.com/open-component-model/ocm/pkg/mime" + . "ocm.software/ocm/cmds/ocm/testhelper" + + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/extensions/repositories/comparch" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/mime" + common "ocm.software/ocm/api/utils/misc" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/sources/cmd.go b/cmds/ocm/commands/ocmcmds/sources/cmd.go index 19378385e..84535d614 100644 --- a/cmds/ocm/commands/ocmcmds/sources/cmd.go +++ b/cmds/ocm/commands/ocmcmds/sources/cmd.go @@ -3,11 +3,11 @@ package sources import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/sources/add" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/sources/get" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/sources/add" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/sources/get" + "ocm.software/ocm/cmds/ocm/common/utils" ) var Names = names.Sources diff --git a/cmds/ocm/commands/ocmcmds/sources/common/typehandler.go b/cmds/ocm/commands/ocmcmds/sources/common/typehandler.go index 60f75806f..1442c6dbb 100644 --- a/cmds/ocm/commands/ocmcmds/sources/common/typehandler.go +++ b/cmds/ocm/commands/ocmcmds/sources/common/typehandler.go @@ -1,12 +1,12 @@ package common import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr" - "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" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/utils" ) func Elem(e interface{}) *compdesc.Source { diff --git a/cmds/ocm/commands/ocmcmds/sources/get/cmd.go b/cmds/ocm/commands/ocmcmds/sources/get/cmd.go index b0ce07a93..fa527f73a 100644 --- a/cmds/ocm/commands/ocmcmds/sources/get/cmd.go +++ b/cmds/ocm/commands/ocmcmds/sources/get/cmd.go @@ -4,21 +4,21 @@ import ( "github.com/mandelsoft/goutils/sliceutils" "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/common/options/closureoption" - ocmcommon "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/sources/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" - "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/ocm" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/cmds/ocm/commands/common/options/closureoption" + ocmcommon "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/elemhdlr" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/versionconstraintsoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/sources/common" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/processing" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/sources/get/cmd_test.go b/cmds/ocm/commands/ocmcmds/sources/get/cmd_test.go index a092ab1fc..131846460 100644 --- a/cmds/ocm/commands/ocmcmds/sources/get/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/sources/get/cmd_test.go @@ -6,10 +6,10 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/ocm" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/utils/accessio" ) const ( diff --git a/cmds/ocm/commands/ocmcmds/versions/cmd.go b/cmds/ocm/commands/ocmcmds/versions/cmd.go index b40031b0d..357851bdf 100644 --- a/cmds/ocm/commands/ocmcmds/versions/cmd.go +++ b/cmds/ocm/commands/ocmcmds/versions/cmd.go @@ -3,10 +3,10 @@ package versions import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/versions/show" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/versions/show" + "ocm.software/ocm/cmds/ocm/common/utils" ) var Names = names.Versions diff --git a/cmds/ocm/commands/ocmcmds/versions/show/cmd.go b/cmds/ocm/commands/ocmcmds/versions/show/cmd.go index 0045375c0..c0322d732 100644 --- a/cmds/ocm/commands/ocmcmds/versions/show/cmd.go +++ b/cmds/ocm/commands/ocmcmds/versions/show/cmd.go @@ -6,15 +6,15 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - ocmcommon "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/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/contexts/ocm" - "github.com/open-component-model/ocm/pkg/out" - "github.com/open-component-model/ocm/pkg/semverutils" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/api/utils/semverutils" + ocmcommon "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) var ( diff --git a/cmds/ocm/commands/ocmcmds/versions/show/cmd_test.go b/cmds/ocm/commands/ocmcmds/versions/show/cmd_test.go index 3218e5b6f..de16920f8 100644 --- a/cmds/ocm/commands/ocmcmds/versions/show/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/versions/show/cmd_test.go @@ -6,9 +6,9 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" - "github.com/open-component-model/ocm/pkg/common/accessio" + "ocm.software/ocm/api/utils/accessio" ) const ( diff --git a/cmds/ocm/commands/plugin/cmd.go b/cmds/ocm/commands/plugin/cmd.go index c226f221c..ceef4c19a 100644 --- a/cmds/ocm/commands/plugin/cmd.go +++ b/cmds/ocm/commands/plugin/cmd.go @@ -7,9 +7,9 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "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/ocm/plugin" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm/plugin" + "ocm.software/ocm/cmds/ocm/common/utils" ) type Command struct { diff --git a/cmds/ocm/commands/toicmds/cmd.go b/cmds/ocm/commands/toicmds/cmd.go index 33a3ee21c..9b3ec0e88 100644 --- a/cmds/ocm/commands/toicmds/cmd.go +++ b/cmds/ocm/commands/toicmds/cmd.go @@ -3,14 +3,14 @@ package toicmds import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds/config" - _package "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds/package" - "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds/verbs/bootstrap" - "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds/verbs/describe" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - topicocmrefs "github.com/open-component-model/ocm/cmds/ocm/topics/ocm/refs" - topicbootstrap "github.com/open-component-model/ocm/cmds/ocm/topics/toi/bootstrapping" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/toicmds/config" + _package "ocm.software/ocm/cmds/ocm/commands/toicmds/package" + "ocm.software/ocm/cmds/ocm/commands/toicmds/verbs/bootstrap" + "ocm.software/ocm/cmds/ocm/commands/toicmds/verbs/describe" + "ocm.software/ocm/cmds/ocm/common/utils" + topicocmrefs "ocm.software/ocm/cmds/ocm/topics/ocm/refs" + topicbootstrap "ocm.software/ocm/cmds/ocm/topics/toi/bootstrapping" ) // NewCommand creates a new command. diff --git a/cmds/ocm/commands/toicmds/config/bootstrap/cmd.go b/cmds/ocm/commands/toicmds/config/bootstrap/cmd.go index bd1648533..db629eee5 100644 --- a/cmds/ocm/commands/toicmds/config/bootstrap/cmd.go +++ b/cmds/ocm/commands/toicmds/config/bootstrap/cmd.go @@ -10,29 +10,29 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - 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" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" - "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds/package/bootstrap" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - topicbootstrap "github.com/open-component-model/ocm/cmds/ocm/topics/toi/bootstrapping" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/ociuploadattr" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - utils2 "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" - "github.com/open-component-model/ocm/pkg/out" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/toi" - "github.com/open-component-model/ocm/pkg/toi/install" - utils3 "github.com/open-component-model/ocm/pkg/utils" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/oci/extensions/repositories/ocireg" + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/attrs/ociuploadattr" + "ocm.software/ocm/api/ocm/extensions/download" + utils2 "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/ocm/tools/toi" + "ocm.software/ocm/api/ocm/tools/toi/install" + utils3 "ocm.software/ocm/api/utils" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/api/utils/runtime" + ocmcommon "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/toicmds/names" + "ocm.software/ocm/cmds/ocm/commands/toicmds/package/bootstrap" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/utils" + topicbootstrap "ocm.software/ocm/cmds/ocm/topics/toi/bootstrapping" ) const ( diff --git a/cmds/ocm/commands/toicmds/config/bootstrap/cmd_test.go b/cmds/ocm/commands/toicmds/config/bootstrap/cmd_test.go index 678ae4c39..c64e87df6 100644 --- a/cmds/ocm/commands/toicmds/config/bootstrap/cmd_test.go +++ b/cmds/ocm/commands/toicmds/config/bootstrap/cmd_test.go @@ -6,15 +6,15 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds/config/bootstrap" - "github.com/open-component-model/ocm/pkg/common/accessio" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/toi" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/tools/toi" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/cmds/ocm/commands/toicmds/config/bootstrap" ) const ( diff --git a/cmds/ocm/commands/toicmds/config/cmd.go b/cmds/ocm/commands/toicmds/config/cmd.go index cb3d17403..11294ba0b 100644 --- a/cmds/ocm/commands/toicmds/config/cmd.go +++ b/cmds/ocm/commands/toicmds/config/cmd.go @@ -3,10 +3,10 @@ package config import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds/config/bootstrap" - "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds/names" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/toicmds/config/bootstrap" + "ocm.software/ocm/cmds/ocm/commands/toicmds/names" + "ocm.software/ocm/cmds/ocm/common/utils" ) var Names = names.Configuration diff --git a/cmds/ocm/commands/toicmds/names/names.go b/cmds/ocm/commands/toicmds/names/names.go index 4995b9975..b45274986 100644 --- a/cmds/ocm/commands/toicmds/names/names.go +++ b/cmds/ocm/commands/toicmds/names/names.go @@ -1,7 +1,7 @@ package names import ( - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/names" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/names" ) var ( diff --git a/cmds/ocm/commands/toicmds/package/bootstrap/cmd.go b/cmds/ocm/commands/toicmds/package/bootstrap/cmd.go index 601b16175..e77d0d67c 100644 --- a/cmds/ocm/commands/toicmds/package/bootstrap/cmd.go +++ b/cmds/ocm/commands/toicmds/package/bootstrap/cmd.go @@ -8,30 +8,30 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - 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" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" - "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - topicbootstrap "github.com/open-component-model/ocm/cmds/ocm/topics/toi/bootstrapping" - "github.com/open-component-model/ocm/pkg/blobaccess" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/listformat" - "github.com/open-component-model/ocm/pkg/out" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/toi" - defaultd "github.com/open-component-model/ocm/pkg/toi/drivers/default" - "github.com/open-component-model/ocm/pkg/toi/drivers/docker" - "github.com/open-component-model/ocm/pkg/toi/drivers/filesystem" - "github.com/open-component-model/ocm/pkg/toi/install" - utils2 "github.com/open-component-model/ocm/pkg/utils" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/oci/extensions/repositories/ocireg" + "ocm.software/ocm/api/ocm" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/tools/toi" + defaultd "ocm.software/ocm/api/ocm/tools/toi/drivers/default" + "ocm.software/ocm/api/ocm/tools/toi/drivers/docker" + "ocm.software/ocm/api/ocm/tools/toi/drivers/filesystem" + "ocm.software/ocm/api/ocm/tools/toi/install" + utils2 "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/listformat" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/api/utils/runtime" + ocmcommon "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/toicmds/names" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/utils" + topicbootstrap "ocm.software/ocm/cmds/ocm/topics/toi/bootstrapping" ) const ( diff --git a/cmds/ocm/commands/toicmds/package/cmd.go b/cmds/ocm/commands/toicmds/package/cmd.go index 4e3864065..d60808384 100644 --- a/cmds/ocm/commands/toicmds/package/cmd.go +++ b/cmds/ocm/commands/toicmds/package/cmd.go @@ -3,10 +3,10 @@ package _package import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds/package/bootstrap" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/toicmds/names" + "ocm.software/ocm/cmds/ocm/commands/toicmds/package/bootstrap" + "ocm.software/ocm/cmds/ocm/common/utils" ) var Names = names.Package diff --git a/cmds/ocm/commands/toicmds/package/describe/cmd.go b/cmds/ocm/commands/toicmds/package/describe/cmd.go index cccf87782..057d48752 100644 --- a/cmds/ocm/commands/toicmds/package/describe/cmd.go +++ b/cmds/ocm/commands/toicmds/package/describe/cmd.go @@ -7,27 +7,27 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/spf13/cobra" - 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" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" - "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds/names" - "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds/package/bootstrap" - "github.com/open-component-model/ocm/cmds/ocm/commands/verbs" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - topicbootstrap "github.com/open-component-model/ocm/cmds/ocm/topics/toi/bootstrapping" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/ociuploadattr" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - utils2 "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" - "github.com/open-component-model/ocm/pkg/out" - "github.com/open-component-model/ocm/pkg/toi" - "github.com/open-component-model/ocm/pkg/toi/install" - utils3 "github.com/open-component-model/ocm/pkg/utils" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/oci/extensions/repositories/ocireg" + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/attrs/ociuploadattr" + utils2 "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/ocm/tools/toi" + "ocm.software/ocm/api/ocm/tools/toi/install" + utils3 "ocm.software/ocm/api/utils" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/out" + ocmcommon "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/lookupoption" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/repooption" + "ocm.software/ocm/cmds/ocm/commands/toicmds/names" + "ocm.software/ocm/cmds/ocm/commands/toicmds/package/bootstrap" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/output" + "ocm.software/ocm/cmds/ocm/common/utils" + topicbootstrap "ocm.software/ocm/cmds/ocm/topics/toi/bootstrapping" ) const ( diff --git a/cmds/ocm/commands/toicmds/verbs/bootstrap/cmd.go b/cmds/ocm/commands/toicmds/verbs/bootstrap/cmd.go index 392e56a58..857b07160 100644 --- a/cmds/ocm/commands/toicmds/verbs/bootstrap/cmd.go +++ b/cmds/ocm/commands/toicmds/verbs/bootstrap/cmd.go @@ -3,10 +3,10 @@ package bootstrap import ( "github.com/spf13/cobra" - components "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds/package/bootstrap" - "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" + clictx "ocm.software/ocm/api/cli" + components "ocm.software/ocm/cmds/ocm/commands/toicmds/package/bootstrap" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) // NewCommand creates a new command. diff --git a/cmds/ocm/commands/toicmds/verbs/describe/cmd.go b/cmds/ocm/commands/toicmds/verbs/describe/cmd.go index baa8d8be9..e4720aace 100644 --- a/cmds/ocm/commands/toicmds/verbs/describe/cmd.go +++ b/cmds/ocm/commands/toicmds/verbs/describe/cmd.go @@ -3,10 +3,10 @@ package describe import ( "github.com/spf13/cobra" - _package "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds/package/describe" - "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" + clictx "ocm.software/ocm/api/cli" + _package "ocm.software/ocm/cmds/ocm/commands/toicmds/package/describe" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) // NewCommand creates a new command. diff --git a/cmds/ocm/commands/verbs/add/cmd.go b/cmds/ocm/commands/verbs/add/cmd.go index edb914aff..cbf3ec1ca 100644 --- a/cmds/ocm/commands/verbs/add/cmd.go +++ b/cmds/ocm/commands/verbs/add/cmd.go @@ -3,16 +3,16 @@ package add import ( "github.com/spf13/cobra" - components "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/components/add" - references "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/references/add" - resourceconfig "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/resourceconfig/add" - resources "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/resources/add" - routingslips "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/routingslips/add" - sourceconfig "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/sourceconfig/add" - sources "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/sources/add" - "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" + clictx "ocm.software/ocm/api/cli" + components "ocm.software/ocm/cmds/ocm/commands/ocmcmds/components/add" + references "ocm.software/ocm/cmds/ocm/commands/ocmcmds/references/add" + resourceconfig "ocm.software/ocm/cmds/ocm/commands/ocmcmds/resourceconfig/add" + resources "ocm.software/ocm/cmds/ocm/commands/ocmcmds/resources/add" + routingslips "ocm.software/ocm/cmds/ocm/commands/ocmcmds/routingslips/add" + sourceconfig "ocm.software/ocm/cmds/ocm/commands/ocmcmds/sourceconfig/add" + sources "ocm.software/ocm/cmds/ocm/commands/ocmcmds/sources/add" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) // NewCommand creates a new command. diff --git a/cmds/ocm/commands/verbs/bootstrap/cmd.go b/cmds/ocm/commands/verbs/bootstrap/cmd.go index cc03afb76..324d11301 100644 --- a/cmds/ocm/commands/verbs/bootstrap/cmd.go +++ b/cmds/ocm/commands/verbs/bootstrap/cmd.go @@ -3,11 +3,11 @@ package bootstrap import ( "github.com/spf13/cobra" - config "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds/config/bootstrap" - _package "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds/package/bootstrap" - "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" + clictx "ocm.software/ocm/api/cli" + config "ocm.software/ocm/cmds/ocm/commands/toicmds/config/bootstrap" + _package "ocm.software/ocm/cmds/ocm/commands/toicmds/package/bootstrap" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) // NewCommand creates a new command. diff --git a/cmds/ocm/commands/verbs/check/cmd.go b/cmds/ocm/commands/verbs/check/cmd.go index ca7066080..44e5978a8 100644 --- a/cmds/ocm/commands/verbs/check/cmd.go +++ b/cmds/ocm/commands/verbs/check/cmd.go @@ -3,10 +3,10 @@ package check import ( "github.com/spf13/cobra" - components "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/components/check" - "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" + clictx "ocm.software/ocm/api/cli" + components "ocm.software/ocm/cmds/ocm/commands/ocmcmds/components/check" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) // NewCommand creates a new command. diff --git a/cmds/ocm/commands/verbs/clean/cmd.go b/cmds/ocm/commands/verbs/clean/cmd.go index ffcb81568..72338b0cf 100644 --- a/cmds/ocm/commands/verbs/clean/cmd.go +++ b/cmds/ocm/commands/verbs/clean/cmd.go @@ -3,10 +3,10 @@ package clean import ( "github.com/spf13/cobra" - cache "github.com/open-component-model/ocm/cmds/ocm/commands/cachecmds/clean" - "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" + clictx "ocm.software/ocm/api/cli" + cache "ocm.software/ocm/cmds/ocm/commands/cachecmds/clean" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) // NewCommand creates a new command. diff --git a/cmds/ocm/commands/verbs/controller/cmd.go b/cmds/ocm/commands/verbs/controller/cmd.go index a79e2741f..c8b798554 100644 --- a/cmds/ocm/commands/verbs/controller/cmd.go +++ b/cmds/ocm/commands/verbs/controller/cmd.go @@ -3,11 +3,11 @@ package controller import ( "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" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/controllercmds/install" + "ocm.software/ocm/cmds/ocm/commands/controllercmds/names" + "ocm.software/ocm/cmds/ocm/commands/controllercmds/uninstall" + "ocm.software/ocm/cmds/ocm/common/utils" ) // NewCommand creates a new command. diff --git a/cmds/ocm/commands/verbs/create/cmd.go b/cmds/ocm/commands/verbs/create/cmd.go index 0cd77a58d..b48be9930 100644 --- a/cmds/ocm/commands/verbs/create/cmd.go +++ b/cmds/ocm/commands/verbs/create/cmd.go @@ -3,12 +3,12 @@ package create import ( "github.com/spf13/cobra" - rsakeypair "github.com/open-component-model/ocm/cmds/ocm/commands/misccmds/rsakeypair" - ctf "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/ctf/create" - comparch "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/componentarchive/create" - "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" + clictx "ocm.software/ocm/api/cli" + rsakeypair "ocm.software/ocm/cmds/ocm/commands/misccmds/rsakeypair" + ctf "ocm.software/ocm/cmds/ocm/commands/ocicmds/ctf/create" + comparch "ocm.software/ocm/cmds/ocm/commands/ocmcmds/componentarchive/create" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) // NewCommand creates a new command. diff --git a/cmds/ocm/commands/verbs/describe/cmd.go b/cmds/ocm/commands/verbs/describe/cmd.go index e3d27ad9f..b181ad5f5 100644 --- a/cmds/ocm/commands/verbs/describe/cmd.go +++ b/cmds/ocm/commands/verbs/describe/cmd.go @@ -3,13 +3,13 @@ package describe import ( "github.com/spf13/cobra" - cache "github.com/open-component-model/ocm/cmds/ocm/commands/cachecmds/describe" - resources "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/artifacts/describe" - plugins "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/plugins/describe" - _package "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds/package/describe" - "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" + clictx "ocm.software/ocm/api/cli" + cache "ocm.software/ocm/cmds/ocm/commands/cachecmds/describe" + resources "ocm.software/ocm/cmds/ocm/commands/ocicmds/artifacts/describe" + plugins "ocm.software/ocm/cmds/ocm/commands/ocmcmds/plugins/describe" + _package "ocm.software/ocm/cmds/ocm/commands/toicmds/package/describe" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) // NewCommand creates a new command. diff --git a/cmds/ocm/commands/verbs/download/cmd.go b/cmds/ocm/commands/verbs/download/cmd.go index e8803b576..615b35581 100644 --- a/cmds/ocm/commands/verbs/download/cmd.go +++ b/cmds/ocm/commands/verbs/download/cmd.go @@ -3,13 +3,13 @@ package download import ( "github.com/spf13/cobra" - artifacts "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/artifacts/download" - cli "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/cli/download" - components "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/components/download" - resources "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/resources/download" - "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" + clictx "ocm.software/ocm/api/cli" + artifacts "ocm.software/ocm/cmds/ocm/commands/ocicmds/artifacts/download" + cli "ocm.software/ocm/cmds/ocm/commands/ocmcmds/cli/download" + components "ocm.software/ocm/cmds/ocm/commands/ocmcmds/components/download" + resources "ocm.software/ocm/cmds/ocm/commands/ocmcmds/resources/download" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) // NewCommand creates a new command. diff --git a/cmds/ocm/commands/verbs/execute/cmd.go b/cmds/ocm/commands/verbs/execute/cmd.go index 059aac275..2f6e3c752 100644 --- a/cmds/ocm/commands/verbs/execute/cmd.go +++ b/cmds/ocm/commands/verbs/execute/cmd.go @@ -3,10 +3,10 @@ package execute import ( "github.com/spf13/cobra" - action "github.com/open-component-model/ocm/cmds/ocm/commands/misccmds/action/execute" - "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" + clictx "ocm.software/ocm/api/cli" + action "ocm.software/ocm/cmds/ocm/commands/misccmds/action/execute" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) // NewCommand creates a new command. diff --git a/cmds/ocm/commands/verbs/get/cmd.go b/cmds/ocm/commands/verbs/get/cmd.go index 085dcf266..abe0f39d3 100644 --- a/cmds/ocm/commands/verbs/get/cmd.go +++ b/cmds/ocm/commands/verbs/get/cmd.go @@ -3,19 +3,19 @@ package get import ( "github.com/spf13/cobra" - config "github.com/open-component-model/ocm/cmds/ocm/commands/misccmds/config/get" - credentials "github.com/open-component-model/ocm/cmds/ocm/commands/misccmds/credentials/get" - artifacts "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/artifacts/get" - components "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/components/get" - plugins "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/plugins/get" - pubsub "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/pubsub/get" - references "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/references/get" - resources "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/resources/get" - routingslips "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/routingslips/get" - sources "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/sources/get" - "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" + clictx "ocm.software/ocm/api/cli" + config "ocm.software/ocm/cmds/ocm/commands/misccmds/config/get" + credentials "ocm.software/ocm/cmds/ocm/commands/misccmds/credentials/get" + artifacts "ocm.software/ocm/cmds/ocm/commands/ocicmds/artifacts/get" + components "ocm.software/ocm/cmds/ocm/commands/ocmcmds/components/get" + plugins "ocm.software/ocm/cmds/ocm/commands/ocmcmds/plugins/get" + pubsub "ocm.software/ocm/cmds/ocm/commands/ocmcmds/pubsub/get" + references "ocm.software/ocm/cmds/ocm/commands/ocmcmds/references/get" + resources "ocm.software/ocm/cmds/ocm/commands/ocmcmds/resources/get" + routingslips "ocm.software/ocm/cmds/ocm/commands/ocmcmds/routingslips/get" + sources "ocm.software/ocm/cmds/ocm/commands/ocmcmds/sources/get" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) // NewCommand creates a new command. diff --git a/cmds/ocm/commands/verbs/hash/cmd.go b/cmds/ocm/commands/verbs/hash/cmd.go index ce3e1a14b..d3e53ece5 100644 --- a/cmds/ocm/commands/verbs/hash/cmd.go +++ b/cmds/ocm/commands/verbs/hash/cmd.go @@ -3,10 +3,10 @@ package hash import ( "github.com/spf13/cobra" - components "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/components/hash" - "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" + clictx "ocm.software/ocm/api/cli" + components "ocm.software/ocm/cmds/ocm/commands/ocmcmds/components/hash" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) // NewCommand creates a new command. diff --git a/cmds/ocm/commands/verbs/install/cmd.go b/cmds/ocm/commands/verbs/install/cmd.go index 8a33b8231..ea1ca8661 100644 --- a/cmds/ocm/commands/verbs/install/cmd.go +++ b/cmds/ocm/commands/verbs/install/cmd.go @@ -3,10 +3,10 @@ package install import ( "github.com/spf13/cobra" - plugins "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/plugins/install" - "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" + clictx "ocm.software/ocm/api/cli" + plugins "ocm.software/ocm/cmds/ocm/commands/ocmcmds/plugins/install" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) // NewCommand creates a new command. diff --git a/cmds/ocm/commands/verbs/list/cmd.go b/cmds/ocm/commands/verbs/list/cmd.go index 465ec1a71..430c642de 100644 --- a/cmds/ocm/commands/verbs/list/cmd.go +++ b/cmds/ocm/commands/verbs/list/cmd.go @@ -3,10 +3,10 @@ package list import ( "github.com/spf13/cobra" - components "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/components/list" - "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" + clictx "ocm.software/ocm/api/cli" + components "ocm.software/ocm/cmds/ocm/commands/ocmcmds/components/list" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) // NewCommand creates a new command. diff --git a/cmds/ocm/commands/verbs/set/cmd.go b/cmds/ocm/commands/verbs/set/cmd.go index 9349e3571..7a472a76c 100644 --- a/cmds/ocm/commands/verbs/set/cmd.go +++ b/cmds/ocm/commands/verbs/set/cmd.go @@ -3,10 +3,10 @@ package set import ( "github.com/spf13/cobra" - pubsub "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/pubsub/set" - "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" + clictx "ocm.software/ocm/api/cli" + pubsub "ocm.software/ocm/cmds/ocm/commands/ocmcmds/pubsub/set" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) // NewCommand creates a new set command. diff --git a/cmds/ocm/commands/verbs/show/cmd.go b/cmds/ocm/commands/verbs/show/cmd.go index 68269c549..61c7a7f85 100644 --- a/cmds/ocm/commands/verbs/show/cmd.go +++ b/cmds/ocm/commands/verbs/show/cmd.go @@ -3,11 +3,11 @@ package show import ( "github.com/spf13/cobra" - tags "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/tags/show" - versions "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/versions/show" - "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" + clictx "ocm.software/ocm/api/cli" + tags "ocm.software/ocm/cmds/ocm/commands/ocicmds/tags/show" + versions "ocm.software/ocm/cmds/ocm/commands/ocmcmds/versions/show" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) // NewCommand creates a new command. diff --git a/cmds/ocm/commands/verbs/sign/cmd.go b/cmds/ocm/commands/verbs/sign/cmd.go index 0dcfb1c2b..73d1e9fa6 100644 --- a/cmds/ocm/commands/verbs/sign/cmd.go +++ b/cmds/ocm/commands/verbs/sign/cmd.go @@ -3,11 +3,11 @@ package sign import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/misccmds/hash/sign" - components "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/components/sign" - "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" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/misccmds/hash/sign" + components "ocm.software/ocm/cmds/ocm/commands/ocmcmds/components/sign" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) // NewCommand creates a new command. diff --git a/cmds/ocm/commands/verbs/transfer/cmd.go b/cmds/ocm/commands/verbs/transfer/cmd.go index e65a56548..9124c789e 100644 --- a/cmds/ocm/commands/verbs/transfer/cmd.go +++ b/cmds/ocm/commands/verbs/transfer/cmd.go @@ -3,13 +3,13 @@ package transfer import ( "github.com/spf13/cobra" - artifacts "github.com/open-component-model/ocm/cmds/ocm/commands/ocicmds/artifacts/transfer" - comparch "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/componentarchive/transfer" - components "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/components/transfer" - ctf "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/ctf/transfer" - "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" + clictx "ocm.software/ocm/api/cli" + artifacts "ocm.software/ocm/cmds/ocm/commands/ocicmds/artifacts/transfer" + comparch "ocm.software/ocm/cmds/ocm/commands/ocmcmds/componentarchive/transfer" + components "ocm.software/ocm/cmds/ocm/commands/ocmcmds/components/transfer" + ctf "ocm.software/ocm/cmds/ocm/commands/ocmcmds/ctf/transfer" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) // NewCommand creates a new command. diff --git a/cmds/ocm/commands/verbs/verb.go b/cmds/ocm/commands/verbs/verb.go index 50f5310c7..ace364713 100644 --- a/cmds/ocm/commands/verbs/verb.go +++ b/cmds/ocm/commands/verbs/verb.go @@ -3,8 +3,8 @@ package verbs import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/common/utils" ) // NewCommand creates a new command. diff --git a/cmds/ocm/commands/verbs/verify/cmd.go b/cmds/ocm/commands/verbs/verify/cmd.go index 3e314da43..603bc5a77 100644 --- a/cmds/ocm/commands/verbs/verify/cmd.go +++ b/cmds/ocm/commands/verbs/verify/cmd.go @@ -3,10 +3,10 @@ package verify import ( "github.com/spf13/cobra" - components "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/components/verify" - "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" + clictx "ocm.software/ocm/api/cli" + components "ocm.software/ocm/cmds/ocm/commands/ocmcmds/components/verify" + "ocm.software/ocm/cmds/ocm/commands/verbs" + "ocm.software/ocm/cmds/ocm/common/utils" ) // NewCommand creates a new command. diff --git a/cmds/ocm/pkg/data/convert.go b/cmds/ocm/common/data/convert.go similarity index 100% rename from cmds/ocm/pkg/data/convert.go rename to cmds/ocm/common/data/convert.go diff --git a/cmds/ocm/pkg/data/dll.go b/cmds/ocm/common/data/dll.go similarity index 100% rename from cmds/ocm/pkg/data/dll.go rename to cmds/ocm/common/data/dll.go diff --git a/cmds/ocm/pkg/data/dll_test.go b/cmds/ocm/common/data/dll_test.go similarity index 100% rename from cmds/ocm/pkg/data/dll_test.go rename to cmds/ocm/common/data/dll_test.go diff --git a/cmds/ocm/pkg/data/indexed.go b/cmds/ocm/common/data/indexed.go similarity index 100% rename from cmds/ocm/pkg/data/indexed.go rename to cmds/ocm/common/data/indexed.go diff --git a/cmds/ocm/pkg/data/indexed_test.go b/cmds/ocm/common/data/indexed_test.go similarity index 100% rename from cmds/ocm/pkg/data/indexed_test.go rename to cmds/ocm/common/data/indexed_test.go diff --git a/cmds/ocm/pkg/data/iterator.go b/cmds/ocm/common/data/iterator.go similarity index 100% rename from cmds/ocm/pkg/data/iterator.go rename to cmds/ocm/common/data/iterator.go diff --git a/cmds/ocm/pkg/data/list.go b/cmds/ocm/common/data/list.go similarity index 100% rename from cmds/ocm/pkg/data/list.go rename to cmds/ocm/common/data/list.go diff --git a/cmds/ocm/pkg/data/list_test.go b/cmds/ocm/common/data/list_test.go similarity index 100% rename from cmds/ocm/pkg/data/list_test.go rename to cmds/ocm/common/data/list_test.go diff --git a/cmds/ocm/pkg/data/sort.go b/cmds/ocm/common/data/sort.go similarity index 100% rename from cmds/ocm/pkg/data/sort.go rename to cmds/ocm/common/data/sort.go diff --git a/cmds/ocm/pkg/data/suite_test.go b/cmds/ocm/common/data/suite_test.go similarity index 100% rename from cmds/ocm/pkg/data/suite_test.go rename to cmds/ocm/common/data/suite_test.go diff --git a/cmds/ocm/pkg/options/interfaces.go b/cmds/ocm/common/options/interfaces.go similarity index 97% rename from cmds/ocm/pkg/options/interfaces.go rename to cmds/ocm/common/options/interfaces.go index 368918510..e48cbad36 100644 --- a/cmds/ocm/pkg/options/interfaces.go +++ b/cmds/ocm/common/options/interfaces.go @@ -6,8 +6,8 @@ import ( "github.com/mandelsoft/goutils/generics" "github.com/spf13/pflag" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/out" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/utils/out" ) type OptionsProcessor func(Options) error diff --git a/cmds/ocm/common/options/options_test.go b/cmds/ocm/common/options/options_test.go new file mode 100644 index 000000000..df4f785a5 --- /dev/null +++ b/cmds/ocm/common/options/options_test.go @@ -0,0 +1,56 @@ +package options_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/spf13/pflag" + + "ocm.software/ocm/cmds/ocm/common/options" +) + +type TestOption struct { + Flag bool +} + +func (t *TestOption) AddFlags(fs *pflag.FlagSet) { + fs.BoolVarP(&t.Flag, "flag", "f", false, "test flag") +} + +var _ options.Options = (*TestOption)(nil) + +var _ = Describe("options", func() { + It("skips unknown option", func() { + set := options.OptionSet{} + + var opt *TestOption + Expect(set.Get(&opt)).To(BeFalse()) + }) + + It("assigns options pointer from set", func() { + inst := &TestOption{} + set := options.OptionSet{inst} + set.Options(inst).(*TestOption).Flag = true + + var opt *TestOption + Expect(set.Get(&opt)).To(BeTrue()) + Expect(opt.Flag).To(BeTrue()) + Expect(opt).To(BeIdenticalTo(inst)) + + Expect(set.Get(&set)).To(BeFalse()) + }) + + It("assigns options value from set", func() { + inst := &TestOption{} + set := options.OptionSet{inst} + + set.Options(inst).(*TestOption).Flag = true + + var opt TestOption + Expect(set.Get(&opt)).To(BeTrue()) + Expect(opt.Flag).To(BeTrue()) + + opt.Flag = false + Expect(inst.Flag).To(BeTrue()) + }) +}) diff --git a/cmds/ocm/pkg/options/suite_test.go b/cmds/ocm/common/options/suite_test.go similarity index 100% rename from cmds/ocm/pkg/options/suite_test.go rename to cmds/ocm/common/options/suite_test.go diff --git a/cmds/ocm/pkg/output/attroutput.go b/cmds/ocm/common/output/attroutput.go similarity index 89% rename from cmds/ocm/pkg/output/attroutput.go rename to cmds/ocm/common/output/attroutput.go index 356ea84a5..9f30d9e50 100644 --- a/cmds/ocm/pkg/output/attroutput.go +++ b/cmds/ocm/common/output/attroutput.go @@ -1,8 +1,8 @@ package output import ( - . "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" - . "github.com/open-component-model/ocm/pkg/out" + . "ocm.software/ocm/api/utils/out" + . "ocm.software/ocm/cmds/ocm/common/processing" "github.com/mandelsoft/goutils/errors" ) diff --git a/cmds/ocm/pkg/output/attrset.go b/cmds/ocm/common/output/attrset.go similarity index 93% rename from cmds/ocm/pkg/output/attrset.go rename to cmds/ocm/common/output/attrset.go index f38d31691..cc789281d 100644 --- a/cmds/ocm/pkg/output/attrset.go +++ b/cmds/ocm/common/output/attrset.go @@ -3,7 +3,7 @@ package output import ( "fmt" - "github.com/open-component-model/ocm/pkg/out" + "ocm.software/ocm/api/utils/out" ) type AttributeSet struct { diff --git a/cmds/ocm/common/output/chain.go b/cmds/ocm/common/output/chain.go new file mode 100644 index 000000000..04c2d33db --- /dev/null +++ b/cmds/ocm/common/output/chain.go @@ -0,0 +1,17 @@ +package output + +import ( + "ocm.software/ocm/cmds/ocm/common/processing" +) + +type ChainFunction func(opts *Options) processing.ProcessChain + +func ComposeChain(funcs ...ChainFunction) ChainFunction { + return func(opts *Options) processing.ProcessChain { + var chain processing.ProcessChain + for _, f := range funcs { + chain = processing.Append(chain, f(opts)) + } + return chain + } +} diff --git a/cmds/ocm/pkg/output/complexoutput.go b/cmds/ocm/common/output/complexoutput.go similarity index 90% rename from cmds/ocm/pkg/output/complexoutput.go rename to cmds/ocm/common/output/complexoutput.go index b44f9f8e1..ac1424013 100644 --- a/cmds/ocm/pkg/output/complexoutput.go +++ b/cmds/ocm/common/output/complexoutput.go @@ -3,13 +3,13 @@ package output import ( "fmt" - . "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" - . "github.com/open-component-model/ocm/pkg/out" + . "ocm.software/ocm/api/utils/out" + . "ocm.software/ocm/cmds/ocm/common/processing" "sigs.k8s.io/yaml" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" ) type ComplexProcessingOutput struct { diff --git a/cmds/ocm/pkg/output/elementoutput.go b/cmds/ocm/common/output/elementoutput.go similarity index 91% rename from cmds/ocm/pkg/output/elementoutput.go rename to cmds/ocm/common/output/elementoutput.go index 9f1a25de0..a505b0661 100644 --- a/cmds/ocm/pkg/output/elementoutput.go +++ b/cmds/ocm/common/output/elementoutput.go @@ -4,12 +4,12 @@ import ( "fmt" "io" - . "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" - . "github.com/open-component-model/ocm/pkg/out" + . "ocm.software/ocm/api/utils/out" + . "ocm.software/ocm/cmds/ocm/common/processing" "github.com/mandelsoft/logging" - "github.com/open-component-model/ocm/cmds/ocm/pkg/data" + "ocm.software/ocm/cmds/ocm/common/data" ) type DestinationOutput struct { diff --git a/cmds/ocm/pkg/output/funcoutput.go b/cmds/ocm/common/output/funcoutput.go similarity index 86% rename from cmds/ocm/pkg/output/funcoutput.go rename to cmds/ocm/common/output/funcoutput.go index a952f1435..5ad97c00d 100644 --- a/cmds/ocm/pkg/output/funcoutput.go +++ b/cmds/ocm/common/output/funcoutput.go @@ -1,8 +1,8 @@ package output import ( - . "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" - . "github.com/open-component-model/ocm/pkg/out" + . "ocm.software/ocm/api/utils/out" + . "ocm.software/ocm/cmds/ocm/common/processing" ) type OutputFunction func(Context, interface{}) diff --git a/cmds/ocm/common/output/options.go b/cmds/ocm/common/output/options.go new file mode 100644 index 000000000..030989235 --- /dev/null +++ b/cmds/ocm/common/output/options.go @@ -0,0 +1,198 @@ +package output + +import ( + "fmt" + "strings" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/sliceutils" + "github.com/mandelsoft/logging" + "github.com/spf13/pflag" + + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/listformat" + "ocm.software/ocm/cmds/ocm/common/options" + "ocm.software/ocm/cmds/ocm/common/processing" +) + +func From(o options.OptionSetProvider) *Options { + var opts *Options + if me, ok := o.(*Options); ok { + return me + } + o.AsOptionSet().Get(&opts) + return opts +} + +func Selected(mode string) func(o options.OptionSetProvider) bool { + return func(o options.OptionSetProvider) bool { + return From(o).OutputMode == mode + } +} + +// StatusCheckFunction manipulates the processing status error according to +// the state of the given entry. +type StatusCheckFunction func(opts *Options, e interface{}, old error) error + +type Options struct { + options.OptionSet + + allColumns bool + + Outputs Outputs + OutputMode string + Output Output + Sort []string + StatusCheck StatusCheckFunction + OptimizedColumns int + FixedColums int + Context clictx.Context // this context could be ocm context. + Logging logging.Context +} + +func OutputOptions(outputs Outputs, opts ...options.Options) *Options { + return &Options{ + Outputs: outputs, + OptionSet: opts, + } +} + +func (o *Options) OptimizeColumns(n int) *Options { + o.OptimizedColumns = n + return o +} + +// WithStatusCheck provides the possibility to check every entry to +// influence the final out status. +func (o *Options) WithStatusCheck(f StatusCheckFunction) *Options { + o.StatusCheck = f + return o +} + +func (o *Options) AdaptChain(errvar *error, chain processing.ProcessChain) processing.ProcessChain { + if o.StatusCheck != nil { + chain = processing.Map(func(e interface{}) interface{} { + *errvar = o.StatusCheck(o, e, *errvar) + return e + }).Append(chain) + } + return chain +} + +func (o *Options) LogContext() logging.Context { + if o.Logging != nil { + return o.Logging + } + return logging.DefaultContext() +} + +func (o *Options) Options(proto options.Options) interface{} { + return o.OptionSet.Options(proto) +} + +func (o *Options) Get(proto interface{}) bool { + return o.OptionSet.Get(proto) +} + +func (o *Options) UseColumnOptimization() bool { + return o.OptimizedColumns > 0 && !o.allColumns +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { + s := "" + if len(o.Outputs) > 1 { + list := utils.StringMapKeys(o.Outputs) + sep := "" + for _, o := range list { + if o != "" { + s = fmt.Sprintf("%s%s%s", s, sep, o) + sep = ", " + } + } + fs.StringVarP(&o.OutputMode, "output", "o", "", fmt.Sprintf("output mode (%s)", s)) + } + + // TODO: not the best solution to instantiate all possible outputs to figure out, whether sort fields + // are available or not + for _, out := range o.Outputs { + if _, ok := out(o).(SortFields); ok { + fs.StringArrayVarP(&o.Sort, "sort", "s", nil, "sort fields") + break + } + } + + if o.OptimizedColumns > 0 { + fs.BoolVarP(&o.allColumns, "all-columns", "", false, "show all table columns") + } + o.OptionSet.AddFlags(fs) +} + +func (o *Options) Configure(ctx clictx.Context) error { + o.Context = ctx + + // process sub options first, to assure that output options are available for output + // mode creation + err := o.OptionSet.ProcessOnOptions(options.CompleteOptionsWithCLIContext(ctx)) + if err != nil { + return err + } + + if f := o.Outputs[o.OutputMode]; f == nil { + return errors.ErrInvalid("output mode", o.OutputMode) + } else { + o.Output = f(o) + } + + var avail sliceutils.OrderedSlice[string] + + var fields []string + + if s, ok := o.Output.(SortFields); ok { + avail = s.GetSortFields() + } + for _, f := range o.Sort { + split := strings.Split(f, ",") + for _, s := range split { + s = strings.TrimSpace(s) + if s != "" { + if avail.Contains(s) { + fields = append(fields, s) + } else { + return errors.ErrInvalid("sort field", s) + } + } + } + } + o.Sort = fields + return nil +} + +func (o *Options) CompleteAll(ctx clictx.Context) error { + err := o.Configure(ctx) + if err == nil { + err = o.OptionSet.ProcessOnOptions(options.CompleteOptionsWithCLIContext(ctx)) + } + if err != nil { + return err + } + return err +} + +func (o *Options) Usage() string { + s := o.OptionSet.Usage() + + if len(o.Outputs) > 1 { + s += ` +With the option --output the output mode can be selected. +The following modes are supported: +` + listformat.FormatList(o.OutputMode, utils.StringMapKeys(o.Outputs)...) + } + return s +} + +//////////////////////////////////////////////////////////////////////////////// + +func OutputModeCondition(opts *Options, mode string) options.Condition { + return options.Flag(opts.OutputMode == mode) +} diff --git a/cmds/ocm/pkg/output/output.go b/cmds/ocm/common/output/output.go similarity index 98% rename from cmds/ocm/pkg/output/output.go rename to cmds/ocm/common/output/output.go index 831e72fd9..524741ec6 100644 --- a/cmds/ocm/pkg/output/output.go +++ b/cmds/ocm/common/output/output.go @@ -6,12 +6,12 @@ import ( "fmt" "io" - . "github.com/open-component-model/ocm/pkg/out" + . "ocm.software/ocm/api/utils/out" "github.com/mandelsoft/goutils/errors" "sigs.k8s.io/yaml" - "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" + "ocm.software/ocm/cmds/ocm/common/processing" ) type Object = interface{} diff --git a/cmds/ocm/pkg/output/select.go b/cmds/ocm/common/output/select.go similarity index 100% rename from cmds/ocm/pkg/output/select.go rename to cmds/ocm/common/output/select.go diff --git a/cmds/ocm/pkg/output/singleelemoutput.go b/cmds/ocm/common/output/singleelemoutput.go similarity index 100% rename from cmds/ocm/pkg/output/singleelemoutput.go rename to cmds/ocm/common/output/singleelemoutput.go diff --git a/cmds/ocm/pkg/output/sort_test.go b/cmds/ocm/common/output/sort_test.go similarity index 96% rename from cmds/ocm/pkg/output/sort_test.go rename to cmds/ocm/common/output/sort_test.go index fb01f8b23..a265552ee 100644 --- a/cmds/ocm/pkg/output/sort_test.go +++ b/cmds/ocm/common/output/sort_test.go @@ -4,7 +4,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/open-component-model/ocm/cmds/ocm/pkg/data" + "ocm.software/ocm/cmds/ocm/common/data" ) var _ = Describe("sort", func() { diff --git a/cmds/ocm/pkg/output/stringoutput.go b/cmds/ocm/common/output/stringoutput.go similarity index 91% rename from cmds/ocm/pkg/output/stringoutput.go rename to cmds/ocm/common/output/stringoutput.go index ee50e1a5e..df509efa2 100644 --- a/cmds/ocm/pkg/output/stringoutput.go +++ b/cmds/ocm/common/output/stringoutput.go @@ -3,9 +3,9 @@ package output import ( "strings" - . "github.com/open-component-model/ocm/pkg/out" + . "ocm.software/ocm/api/utils/out" - "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" + "ocm.software/ocm/cmds/ocm/common/processing" ) type StringOutput struct { diff --git a/cmds/ocm/pkg/output/suite_test.go b/cmds/ocm/common/output/suite_test.go similarity index 100% rename from cmds/ocm/pkg/output/suite_test.go rename to cmds/ocm/common/output/suite_test.go diff --git a/cmds/ocm/pkg/output/table.go b/cmds/ocm/common/output/table.go similarity index 97% rename from cmds/ocm/pkg/output/table.go rename to cmds/ocm/common/output/table.go index 2641b3a29..becef4389 100644 --- a/cmds/ocm/pkg/output/table.go +++ b/cmds/ocm/common/output/table.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - . "github.com/open-component-model/ocm/pkg/out" + . "ocm.software/ocm/api/utils/out" ) func FormatTable(ctx Context, gap string, data [][]string) { diff --git a/cmds/ocm/pkg/output/tableoutput.go b/cmds/ocm/common/output/tableoutput.go similarity index 93% rename from cmds/ocm/pkg/output/tableoutput.go rename to cmds/ocm/common/output/tableoutput.go index 42e0ebf87..c8cd1881b 100644 --- a/cmds/ocm/pkg/output/tableoutput.go +++ b/cmds/ocm/common/output/tableoutput.go @@ -3,14 +3,14 @@ package output import ( "strings" - . "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" + . "ocm.software/ocm/cmds/ocm/common/processing" "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/cmds/ocm/pkg/data" - "github.com/open-component-model/ocm/pkg/out" - "github.com/open-component-model/ocm/pkg/semverutils" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/out" + "ocm.software/ocm/api/utils/semverutils" + "ocm.software/ocm/cmds/ocm/common/data" ) type SortFields interface { diff --git a/cmds/ocm/pkg/output/treeoutput.go b/cmds/ocm/common/output/treeoutput.go similarity index 92% rename from cmds/ocm/pkg/output/treeoutput.go rename to cmds/ocm/common/output/treeoutput.go index 02656b8a9..86721e8c3 100644 --- a/cmds/ocm/pkg/output/treeoutput.go +++ b/cmds/ocm/common/output/treeoutput.go @@ -1,9 +1,9 @@ package output import ( - "github.com/open-component-model/ocm/cmds/ocm/pkg/data" - "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" - "github.com/open-component-model/ocm/cmds/ocm/pkg/tree" + "ocm.software/ocm/cmds/ocm/common/data" + "ocm.software/ocm/cmds/ocm/common/processing" + "ocm.software/ocm/cmds/ocm/common/tree" ) type TreeOutputOption interface { diff --git a/cmds/ocm/pkg/output/utils.go b/cmds/ocm/common/output/utils.go similarity index 100% rename from cmds/ocm/pkg/output/utils.go rename to cmds/ocm/common/output/utils.go diff --git a/cmds/ocm/pkg/processing/buffer.go b/cmds/ocm/common/processing/buffer.go similarity index 98% rename from cmds/ocm/pkg/processing/buffer.go rename to cmds/ocm/common/processing/buffer.go index 95ef0709c..76451be38 100644 --- a/cmds/ocm/pkg/processing/buffer.go +++ b/cmds/ocm/common/processing/buffer.go @@ -7,8 +7,8 @@ import ( "github.com/containerd/containerd/pkg/atomic" "github.com/mandelsoft/logging" - "github.com/open-component-model/ocm/cmds/ocm/pkg/data" - "github.com/open-component-model/ocm/pkg/utils/panics" + "ocm.software/ocm/api/utils/panics" + "ocm.software/ocm/cmds/ocm/common/data" ) type Index = IndexArray diff --git a/cmds/ocm/pkg/processing/buffer_test.go b/cmds/ocm/common/processing/buffer_test.go similarity index 98% rename from cmds/ocm/pkg/processing/buffer_test.go rename to cmds/ocm/common/processing/buffer_test.go index 9ae9d131b..490605e08 100644 --- a/cmds/ocm/pkg/processing/buffer_test.go +++ b/cmds/ocm/common/processing/buffer_test.go @@ -8,8 +8,8 @@ import ( "github.com/mandelsoft/logging" - "github.com/open-component-model/ocm/cmds/ocm/pkg/data" - ocmlog "github.com/open-component-model/ocm/pkg/logging" + ocmlog "ocm.software/ocm/api/utils/logging" + "ocm.software/ocm/cmds/ocm/common/data" ) type Result struct { diff --git a/cmds/ocm/common/processing/chain.go b/cmds/ocm/common/processing/chain.go new file mode 100644 index 000000000..d78c73ab2 --- /dev/null +++ b/cmds/ocm/common/processing/chain.go @@ -0,0 +1,196 @@ +package processing + +import ( + "github.com/mandelsoft/logging" + + "ocm.software/ocm/cmds/ocm/common/data" + "ocm.software/ocm/cmds/ocm/common/options" +) + +// ProcessChain is a data structure holding a chain definition, which is +// a chain of step creation functions used to instantiate the chain +// for a dedicated input processing. +// The instantiation is initiated by calling the Process +// method on a chain. +type ProcessChain interface { + Transform(t TransformFunction) ProcessChain + Explode(m ExplodeFunction) ProcessChain + Map(m MappingFunction) ProcessChain + Filter(f FilterFunction) ProcessChain + Sort(c CompareFunction) ProcessChain + WithPool(p ProcessorPool) ProcessChain + Unordered() ProcessChain + Parallel(n int) ProcessChain + Append(p ProcessChain) ProcessChain + + Process(data data.Iterable) ProcessingResult +} + +type stepCreator func(ProcessingResult) ProcessingResult + +type _ProcessChain struct { + parent *_ProcessChain + creator stepCreator + log logging.Context +} + +var _ ProcessChain = &_ProcessChain{} + +func Chain(log logging.Context) ProcessChain { + return (&_ProcessChain{}).new(log, nil, nil) +} + +func (this *_ProcessChain) new(log logging.Context, p *_ProcessChain, creator stepCreator) *_ProcessChain { + if p != nil { + if log == nil { + log = p.log + } + if p.creator != nil { + this.parent = p + } else if p.parent != nil { + this.parent = p.parent + } + } + if log == nil { + log = logging.DefaultContext() + } + if this.parent != nil && creator == nil { + return this.parent + } + this.creator = creator + this.log = log + return this +} + +func (this *_ProcessChain) Transform(t TransformFunction) ProcessChain { + if t == nil { + return this + } + return (&_ProcessChain{}).new(this.log, this, chainTransform(t)) +} + +func (this *_ProcessChain) Explode(e ExplodeFunction) ProcessChain { + if e == nil { + return this + } + return (&_ProcessChain{}).new(this.log, this, chainExplode(e)) +} + +func (this *_ProcessChain) Map(m MappingFunction) ProcessChain { + if m == nil { + return this + } + return (&_ProcessChain{}).new(this.log, this, chainMap(m)) +} + +func (this *_ProcessChain) Filter(f FilterFunction) ProcessChain { + if f == nil { + return this + } + return (&_ProcessChain{}).new(this.log, this, chainFilter(f)) +} + +func (this *_ProcessChain) Sort(c CompareFunction) ProcessChain { + if c == nil { + return this + } + return (&_ProcessChain{}).new(this.log, this, chainSort(c)) +} + +func (this *_ProcessChain) WithPool(p ProcessorPool) ProcessChain { + return (&_ProcessChain{}).new(this.log, this, chainWithPool(p)) +} + +func (this *_ProcessChain) Unordered() ProcessChain { + return (&_ProcessChain{}).new(this.log, this, chainUnordered) +} + +func (this *_ProcessChain) Parallel(n int) ProcessChain { + return (&_ProcessChain{}).new(this.log, this, chainParallel(n)) +} + +func (this *_ProcessChain) Append(p ProcessChain) ProcessChain { + if p == nil { + return this + } + return (&_ProcessChain{}).new(this.log, this, chainApply(p)) +} + +// Process instantiates a processing chain for a dedicated input +// It builds a dedicated execution structure +// based on the chain functioned stored along the chain definition. +func (this *_ProcessChain) Process(data data.Iterable) ProcessingResult { + p, ok := data.(ProcessingResult) + if this.parent != nil { + p = this.parent.Process(data) + } else if !ok { + p = Process(this.log, data) + } + return this.step(p) +} + +func (this *_ProcessChain) step(p ProcessingResult) ProcessingResult { + if this.creator == nil { + return p + } + return this.creator(p) +} + +func chainTransform(t TransformFunction) stepCreator { + return func(p ProcessingResult) ProcessingResult { return p.Transform(t) } +} + +func chainExplode(e ExplodeFunction) stepCreator { + return func(p ProcessingResult) ProcessingResult { return p.Explode(e) } +} + +func chainMap(m MappingFunction) stepCreator { + return func(p ProcessingResult) ProcessingResult { return p.Map(m) } +} + +func chainFilter(f FilterFunction) stepCreator { + return func(p ProcessingResult) ProcessingResult { return p.Filter(f) } +} + +func chainSort(c CompareFunction) stepCreator { + return func(p ProcessingResult) ProcessingResult { return p.Sort(c) } +} + +func chainWithPool(pool ProcessorPool) stepCreator { + return func(p ProcessingResult) ProcessingResult { return p.WithPool(pool) } +} + +func chainParallel(n int) stepCreator { + return func(p ProcessingResult) ProcessingResult { return p.Parallel(n) } +} +func chainUnordered(p ProcessingResult) ProcessingResult { return p.Unordered() } + +func chainApply(c ProcessChain) stepCreator { + return func(p ProcessingResult) ProcessingResult { + return p.Apply(c) + } +} + +//////////////////////////////////////////////////////////////////////////////// + +var initial = Chain(logging.DefaultContext()) + +func Transform(t TransformFunction) ProcessChain { return initial.Transform(t) } +func Explode(e ExplodeFunction) ProcessChain { return initial.Explode(e) } +func Map(m MappingFunction) ProcessChain { return initial.Map(m) } +func Filter(f FilterFunction) ProcessChain { return initial.Filter(f) } +func Sort(c CompareFunction) ProcessChain { return initial.Sort(c) } +func WithPool(pool ProcessorPool) ProcessChain { return initial.WithPool(pool) } +func Parallel(n int) ProcessChain { return initial.Parallel(n) } +func Unordered() ProcessChain { return initial.Unordered() } +func Append(chain, add ProcessChain, conditions ...options.Condition) ProcessChain { + if add != nil { + if options.And(conditions...).IsTrue() { + if chain == nil { + return add + } + return chain.Append(add) + } + } + return chain +} diff --git a/cmds/ocm/common/processing/logging.go b/cmds/ocm/common/processing/logging.go new file mode 100644 index 000000000..40ebf789d --- /dev/null +++ b/cmds/ocm/common/processing/logging.go @@ -0,0 +1,7 @@ +package processing + +import ( + ocmlog "ocm.software/ocm/api/utils/logging" +) + +var REALM = ocmlog.DefineSubRealm("output processing chains", "processing") diff --git a/cmds/ocm/pkg/processing/operation.go b/cmds/ocm/common/processing/operation.go similarity index 100% rename from cmds/ocm/pkg/processing/operation.go rename to cmds/ocm/common/processing/operation.go diff --git a/cmds/ocm/pkg/processing/parallel.go b/cmds/ocm/common/processing/parallel.go similarity index 98% rename from cmds/ocm/pkg/processing/parallel.go rename to cmds/ocm/common/processing/parallel.go index 8b2a2b814..5866be13a 100644 --- a/cmds/ocm/pkg/processing/parallel.go +++ b/cmds/ocm/common/processing/parallel.go @@ -5,7 +5,7 @@ import ( "github.com/mandelsoft/logging" - "github.com/open-component-model/ocm/cmds/ocm/pkg/data" + "ocm.software/ocm/cmds/ocm/common/data" ) type _ParallelProcessing struct { diff --git a/cmds/ocm/pkg/processing/pool.go b/cmds/ocm/common/processing/pool.go similarity index 100% rename from cmds/ocm/pkg/processing/pool.go rename to cmds/ocm/common/processing/pool.go diff --git a/cmds/ocm/pkg/processing/processing.go b/cmds/ocm/common/processing/processing.go similarity index 97% rename from cmds/ocm/pkg/processing/processing.go rename to cmds/ocm/common/processing/processing.go index a01e44bfb..61fb794fe 100644 --- a/cmds/ocm/pkg/processing/processing.go +++ b/cmds/ocm/common/processing/processing.go @@ -3,7 +3,7 @@ package processing import ( "github.com/mandelsoft/logging" - "github.com/open-component-model/ocm/cmds/ocm/pkg/data" + "ocm.software/ocm/cmds/ocm/common/data" ) type IncrementalProcessingSource interface { diff --git a/cmds/ocm/pkg/processing/processing_test.go b/cmds/ocm/common/processing/processing_test.go similarity index 97% rename from cmds/ocm/pkg/processing/processing_test.go rename to cmds/ocm/common/processing/processing_test.go index 1b308d9e0..cdf325964 100644 --- a/cmds/ocm/pkg/processing/processing_test.go +++ b/cmds/ocm/common/processing/processing_test.go @@ -10,8 +10,8 @@ import ( "github.com/mandelsoft/goutils/testutils" "github.com/mandelsoft/logging" - "github.com/open-component-model/ocm/cmds/ocm/pkg/data" - ocmlog "github.com/open-component-model/ocm/pkg/logging" + ocmlog "ocm.software/ocm/api/utils/logging" + "ocm.software/ocm/cmds/ocm/common/data" ) var AddOne = func(logger logging.Logger) func(e interface{}) interface{} { diff --git a/cmds/ocm/pkg/processing/sequential.go b/cmds/ocm/common/processing/sequential.go similarity index 98% rename from cmds/ocm/pkg/processing/sequential.go rename to cmds/ocm/common/processing/sequential.go index 8114dedfc..7140c33b9 100644 --- a/cmds/ocm/pkg/processing/sequential.go +++ b/cmds/ocm/common/processing/sequential.go @@ -5,7 +5,7 @@ import ( "github.com/mandelsoft/logging" - "github.com/open-component-model/ocm/cmds/ocm/pkg/data" + "ocm.software/ocm/cmds/ocm/common/data" ) type _SynchronousProcessing struct { diff --git a/cmds/ocm/pkg/processing/suite_test.go b/cmds/ocm/common/processing/suite_test.go similarity index 100% rename from cmds/ocm/pkg/processing/suite_test.go rename to cmds/ocm/common/processing/suite_test.go diff --git a/cmds/ocm/pkg/processing/utils.go b/cmds/ocm/common/processing/utils.go similarity index 100% rename from cmds/ocm/pkg/processing/utils.go rename to cmds/ocm/common/processing/utils.go diff --git a/cmds/ocm/common/tree/convert.go b/cmds/ocm/common/tree/convert.go new file mode 100644 index 000000000..4c457c131 --- /dev/null +++ b/cmds/ocm/common/tree/convert.go @@ -0,0 +1,54 @@ +package tree + +import ( + "ocm.software/ocm/cmds/ocm/common/data" +) + +type Objects []Object + +func ObjectSlice(s data.Iterable) Objects { + var a Objects + i := s.Iterator() + for i.HasNext() { + a = append(a, i.Next().(Object)) + } + return a +} + +var ( + _ data.IndexedAccess = Objects{} + _ data.Iterable = Objects{} +) + +func (o Objects) Len() int { + return len(o) +} + +func (o Objects) Get(i int) interface{} { + return o[i] +} + +func (o Objects) Iterator() data.Iterator { + return data.NewIndexedIterator(o) +} + +//////////////////////////////////////////////////////////////////////////////// + +type TreeObjects []*TreeObject + +var ( + _ data.IndexedAccess = TreeObjects{} + _ data.Iterable = TreeObjects{} +) + +func (o TreeObjects) Len() int { + return len(o) +} + +func (o TreeObjects) Get(i int) interface{} { + return o[i] +} + +func (o TreeObjects) Iterator() data.Iterator { + return data.NewIndexedIterator(o) +} diff --git a/cmds/ocm/pkg/tree/suite_test.go b/cmds/ocm/common/tree/suite_test.go similarity index 100% rename from cmds/ocm/pkg/tree/suite_test.go rename to cmds/ocm/common/tree/suite_test.go diff --git a/cmds/ocm/pkg/tree/tree.go b/cmds/ocm/common/tree/tree.go similarity index 97% rename from cmds/ocm/pkg/tree/tree.go rename to cmds/ocm/common/tree/tree.go index 120455994..8e4026f11 100644 --- a/cmds/ocm/pkg/tree/tree.go +++ b/cmds/ocm/common/tree/tree.go @@ -4,8 +4,8 @@ import ( "fmt" "strings" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" + common "ocm.software/ocm/api/utils/misc" ) type Object interface { diff --git a/cmds/ocm/pkg/tree/tree_test.go b/cmds/ocm/common/tree/tree_test.go similarity index 97% rename from cmds/ocm/pkg/tree/tree_test.go rename to cmds/ocm/common/tree/tree_test.go index f7977f19c..99fbfc675 100644 --- a/cmds/ocm/pkg/tree/tree_test.go +++ b/cmds/ocm/common/tree/tree_test.go @@ -6,8 +6,8 @@ import ( . "github.com/onsi/ginkgo/v2" - "github.com/open-component-model/ocm/cmds/ocm/pkg/tree" - "github.com/open-component-model/ocm/pkg/common" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/cmds/ocm/common/tree" ) type Elem struct { diff --git a/cmds/ocm/pkg/utils/command.go b/cmds/ocm/common/utils/command.go similarity index 97% rename from cmds/ocm/pkg/utils/command.go rename to cmds/ocm/common/utils/command.go index 212cd13c7..97ccc0b34 100644 --- a/cmds/ocm/pkg/utils/command.go +++ b/cmds/ocm/common/utils/command.go @@ -8,9 +8,9 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "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" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/utils/cobrautils" + "ocm.software/ocm/cmds/ocm/common/options" ) // OCMCommand is a command pattern, that can be instantiated for a dediated diff --git a/cmds/ocm/pkg/utils/fileutils.go b/cmds/ocm/common/utils/fileutils.go similarity index 94% rename from cmds/ocm/pkg/utils/fileutils.go rename to cmds/ocm/common/utils/fileutils.go index 17c85a605..0b605bf7d 100644 --- a/cmds/ocm/pkg/utils/fileutils.go +++ b/cmds/ocm/common/utils/fileutils.go @@ -5,7 +5,7 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/utils" ) // IsExecutable returns true if a given file is executable. diff --git a/cmds/ocm/pkg/utils/handling.go b/cmds/ocm/common/utils/handling.go similarity index 97% rename from cmds/ocm/pkg/utils/handling.go rename to cmds/ocm/common/utils/handling.go index 90b554d3b..1d65e8ced 100644 --- a/cmds/ocm/pkg/utils/handling.go +++ b/cmds/ocm/common/utils/handling.go @@ -8,7 +8,7 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/goutils/transformer" - "github.com/open-component-model/ocm/cmds/ocm/pkg/output" + "ocm.software/ocm/cmds/ocm/common/output" ) type ElemSpec interface { diff --git a/cmds/ocm/pkg/utils/plural.go b/cmds/ocm/common/utils/plural.go similarity index 100% rename from cmds/ocm/pkg/utils/plural.go rename to cmds/ocm/common/utils/plural.go diff --git a/cmds/ocm/pkg/utils/validate.go b/cmds/ocm/common/utils/validate.go similarity index 100% rename from cmds/ocm/pkg/utils/validate.go rename to cmds/ocm/common/utils/validate.go diff --git a/cmds/ocm/coretests/maven/cmd_test.go b/cmds/ocm/coretests/maven/cmd_test.go index 2929697d2..bc9071b54 100644 --- a/cmds/ocm/coretests/maven/cmd_test.go +++ b/cmds/ocm/coretests/maven/cmd_test.go @@ -7,14 +7,14 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - mavenacc "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/maven" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/maven" - "github.com/open-component-model/ocm/pkg/maven/maventest" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + mavenacc "ocm.software/ocm/api/ocm/extensions/accessmethods/maven" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/tech/maven" + "ocm.software/ocm/api/tech/maven/maventest" ) const ( diff --git a/cmds/ocm/main.go b/cmds/ocm/main.go index 2514bc96c..f092f7a18 100644 --- a/cmds/ocm/main.go +++ b/cmds/ocm/main.go @@ -4,8 +4,8 @@ import ( "fmt" "os" - "github.com/open-component-model/ocm/cmds/ocm/app" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/app" ) func main() { diff --git a/cmds/ocm/ocm_test.go b/cmds/ocm/ocm_test.go index 77602644e..e9454baf0 100644 --- a/cmds/ocm/ocm_test.go +++ b/cmds/ocm/ocm_test.go @@ -6,10 +6,10 @@ import ( . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "ocm.software/ocm/cmds/ocm/testhelper" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/oci/attrs/cacheattr" + "ocm.software/ocm/api/oci/extensions/attrs/cacheattr" + "ocm.software/ocm/api/utils/accessio" ) var _ = Describe("OCM command line test Environment", func() { diff --git a/cmds/ocm/pkg/options/options_test.go b/cmds/ocm/pkg/options/options_test.go deleted file mode 100644 index 92914958f..000000000 --- a/cmds/ocm/pkg/options/options_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package options_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/spf13/pflag" - - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" -) - -type TestOption struct { - Flag bool -} - -func (t *TestOption) AddFlags(fs *pflag.FlagSet) { - fs.BoolVarP(&t.Flag, "flag", "f", false, "test flag") -} - -var _ options.Options = (*TestOption)(nil) - -var _ = Describe("options", func() { - It("skips unknown option", func() { - set := options.OptionSet{} - - var opt *TestOption - Expect(set.Get(&opt)).To(BeFalse()) - }) - - It("assigns options pointer from set", func() { - inst := &TestOption{} - set := options.OptionSet{inst} - set.Options(inst).(*TestOption).Flag = true - - var opt *TestOption - Expect(set.Get(&opt)).To(BeTrue()) - Expect(opt.Flag).To(BeTrue()) - Expect(opt).To(BeIdenticalTo(inst)) - - Expect(set.Get(&set)).To(BeFalse()) - }) - - It("assigns options value from set", func() { - inst := &TestOption{} - set := options.OptionSet{inst} - - set.Options(inst).(*TestOption).Flag = true - - var opt TestOption - Expect(set.Get(&opt)).To(BeTrue()) - Expect(opt.Flag).To(BeTrue()) - - opt.Flag = false - Expect(inst.Flag).To(BeTrue()) - }) -}) diff --git a/cmds/ocm/pkg/output/chain.go b/cmds/ocm/pkg/output/chain.go deleted file mode 100644 index bcbca994a..000000000 --- a/cmds/ocm/pkg/output/chain.go +++ /dev/null @@ -1,17 +0,0 @@ -package output - -import ( - "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" -) - -type ChainFunction func(opts *Options) processing.ProcessChain - -func ComposeChain(funcs ...ChainFunction) ChainFunction { - return func(opts *Options) processing.ProcessChain { - var chain processing.ProcessChain - for _, f := range funcs { - chain = processing.Append(chain, f(opts)) - } - return chain - } -} diff --git a/cmds/ocm/pkg/output/options.go b/cmds/ocm/pkg/output/options.go deleted file mode 100644 index c68e7db67..000000000 --- a/cmds/ocm/pkg/output/options.go +++ /dev/null @@ -1,198 +0,0 @@ -package output - -import ( - "fmt" - "strings" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/sliceutils" - "github.com/mandelsoft/logging" - "github.com/spf13/pflag" - - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" - "github.com/open-component-model/ocm/cmds/ocm/pkg/processing" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/listformat" - "github.com/open-component-model/ocm/pkg/utils" -) - -func From(o options.OptionSetProvider) *Options { - var opts *Options - if me, ok := o.(*Options); ok { - return me - } - o.AsOptionSet().Get(&opts) - return opts -} - -func Selected(mode string) func(o options.OptionSetProvider) bool { - return func(o options.OptionSetProvider) bool { - return From(o).OutputMode == mode - } -} - -// StatusCheckFunction manipulates the processing status error according to -// the state of the given entry. -type StatusCheckFunction func(opts *Options, e interface{}, old error) error - -type Options struct { - options.OptionSet - - allColumns bool - - Outputs Outputs - OutputMode string - Output Output - Sort []string - StatusCheck StatusCheckFunction - OptimizedColumns int - FixedColums int - Context clictx.Context // this context could be ocm context. - Logging logging.Context -} - -func OutputOptions(outputs Outputs, opts ...options.Options) *Options { - return &Options{ - Outputs: outputs, - OptionSet: opts, - } -} - -func (o *Options) OptimizeColumns(n int) *Options { - o.OptimizedColumns = n - return o -} - -// WithStatusCheck provides the possibility to check every entry to -// influence the final out status. -func (o *Options) WithStatusCheck(f StatusCheckFunction) *Options { - o.StatusCheck = f - return o -} - -func (o *Options) AdaptChain(errvar *error, chain processing.ProcessChain) processing.ProcessChain { - if o.StatusCheck != nil { - chain = processing.Map(func(e interface{}) interface{} { - *errvar = o.StatusCheck(o, e, *errvar) - return e - }).Append(chain) - } - return chain -} - -func (o *Options) LogContext() logging.Context { - if o.Logging != nil { - return o.Logging - } - return logging.DefaultContext() -} - -func (o *Options) Options(proto options.Options) interface{} { - return o.OptionSet.Options(proto) -} - -func (o *Options) Get(proto interface{}) bool { - return o.OptionSet.Get(proto) -} - -func (o *Options) UseColumnOptimization() bool { - return o.OptimizedColumns > 0 && !o.allColumns -} - -func (o *Options) AddFlags(fs *pflag.FlagSet) { - s := "" - if len(o.Outputs) > 1 { - list := utils.StringMapKeys(o.Outputs) - sep := "" - for _, o := range list { - if o != "" { - s = fmt.Sprintf("%s%s%s", s, sep, o) - sep = ", " - } - } - fs.StringVarP(&o.OutputMode, "output", "o", "", fmt.Sprintf("output mode (%s)", s)) - } - - // TODO: not the best solution to instantiate all possible outputs to figure out, whether sort fields - // are available or not - for _, out := range o.Outputs { - if _, ok := out(o).(SortFields); ok { - fs.StringArrayVarP(&o.Sort, "sort", "s", nil, "sort fields") - break - } - } - - if o.OptimizedColumns > 0 { - fs.BoolVarP(&o.allColumns, "all-columns", "", false, "show all table columns") - } - o.OptionSet.AddFlags(fs) -} - -func (o *Options) Configure(ctx clictx.Context) error { - o.Context = ctx - - // process sub options first, to assure that output options are available for output - // mode creation - err := o.OptionSet.ProcessOnOptions(options.CompleteOptionsWithCLIContext(ctx)) - if err != nil { - return err - } - - if f := o.Outputs[o.OutputMode]; f == nil { - return errors.ErrInvalid("output mode", o.OutputMode) - } else { - o.Output = f(o) - } - - var avail sliceutils.OrderedSlice[string] - - var fields []string - - if s, ok := o.Output.(SortFields); ok { - avail = s.GetSortFields() - } - for _, f := range o.Sort { - split := strings.Split(f, ",") - for _, s := range split { - s = strings.TrimSpace(s) - if s != "" { - if avail.Contains(s) { - fields = append(fields, s) - } else { - return errors.ErrInvalid("sort field", s) - } - } - } - } - o.Sort = fields - return nil -} - -func (o *Options) CompleteAll(ctx clictx.Context) error { - err := o.Configure(ctx) - if err == nil { - err = o.OptionSet.ProcessOnOptions(options.CompleteOptionsWithCLIContext(ctx)) - } - if err != nil { - return err - } - return err -} - -func (o *Options) Usage() string { - s := o.OptionSet.Usage() - - if len(o.Outputs) > 1 { - s += ` -With the option --output the output mode can be selected. -The following modes are supported: -` + listformat.FormatList(o.OutputMode, utils.StringMapKeys(o.Outputs)...) - } - return s -} - -//////////////////////////////////////////////////////////////////////////////// - -func OutputModeCondition(opts *Options, mode string) options.Condition { - return options.Flag(opts.OutputMode == mode) -} diff --git a/cmds/ocm/pkg/processing/chain.go b/cmds/ocm/pkg/processing/chain.go deleted file mode 100644 index 5e1fa8653..000000000 --- a/cmds/ocm/pkg/processing/chain.go +++ /dev/null @@ -1,196 +0,0 @@ -package processing - -import ( - "github.com/mandelsoft/logging" - - "github.com/open-component-model/ocm/cmds/ocm/pkg/data" - "github.com/open-component-model/ocm/cmds/ocm/pkg/options" -) - -// ProcessChain is a data structure holding a chain definition, which is -// a chain of step creation functions used to instantiate the chain -// for a dedicated input processing. -// The instantiation is initiated by calling the Process -// method on a chain. -type ProcessChain interface { - Transform(t TransformFunction) ProcessChain - Explode(m ExplodeFunction) ProcessChain - Map(m MappingFunction) ProcessChain - Filter(f FilterFunction) ProcessChain - Sort(c CompareFunction) ProcessChain - WithPool(p ProcessorPool) ProcessChain - Unordered() ProcessChain - Parallel(n int) ProcessChain - Append(p ProcessChain) ProcessChain - - Process(data data.Iterable) ProcessingResult -} - -type stepCreator func(ProcessingResult) ProcessingResult - -type _ProcessChain struct { - parent *_ProcessChain - creator stepCreator - log logging.Context -} - -var _ ProcessChain = &_ProcessChain{} - -func Chain(log logging.Context) ProcessChain { - return (&_ProcessChain{}).new(log, nil, nil) -} - -func (this *_ProcessChain) new(log logging.Context, p *_ProcessChain, creator stepCreator) *_ProcessChain { - if p != nil { - if log == nil { - log = p.log - } - if p.creator != nil { - this.parent = p - } else if p.parent != nil { - this.parent = p.parent - } - } - if log == nil { - log = logging.DefaultContext() - } - if this.parent != nil && creator == nil { - return this.parent - } - this.creator = creator - this.log = log - return this -} - -func (this *_ProcessChain) Transform(t TransformFunction) ProcessChain { - if t == nil { - return this - } - return (&_ProcessChain{}).new(this.log, this, chainTransform(t)) -} - -func (this *_ProcessChain) Explode(e ExplodeFunction) ProcessChain { - if e == nil { - return this - } - return (&_ProcessChain{}).new(this.log, this, chainExplode(e)) -} - -func (this *_ProcessChain) Map(m MappingFunction) ProcessChain { - if m == nil { - return this - } - return (&_ProcessChain{}).new(this.log, this, chainMap(m)) -} - -func (this *_ProcessChain) Filter(f FilterFunction) ProcessChain { - if f == nil { - return this - } - return (&_ProcessChain{}).new(this.log, this, chainFilter(f)) -} - -func (this *_ProcessChain) Sort(c CompareFunction) ProcessChain { - if c == nil { - return this - } - return (&_ProcessChain{}).new(this.log, this, chainSort(c)) -} - -func (this *_ProcessChain) WithPool(p ProcessorPool) ProcessChain { - return (&_ProcessChain{}).new(this.log, this, chainWithPool(p)) -} - -func (this *_ProcessChain) Unordered() ProcessChain { - return (&_ProcessChain{}).new(this.log, this, chainUnordered) -} - -func (this *_ProcessChain) Parallel(n int) ProcessChain { - return (&_ProcessChain{}).new(this.log, this, chainParallel(n)) -} - -func (this *_ProcessChain) Append(p ProcessChain) ProcessChain { - if p == nil { - return this - } - return (&_ProcessChain{}).new(this.log, this, chainApply(p)) -} - -// Process instantiates a processing chain for a dedicated input -// It builds a dedicated execution structure -// based on the chain functioned stored along the chain definition. -func (this *_ProcessChain) Process(data data.Iterable) ProcessingResult { - p, ok := data.(ProcessingResult) - if this.parent != nil { - p = this.parent.Process(data) - } else if !ok { - p = Process(this.log, data) - } - return this.step(p) -} - -func (this *_ProcessChain) step(p ProcessingResult) ProcessingResult { - if this.creator == nil { - return p - } - return this.creator(p) -} - -func chainTransform(t TransformFunction) stepCreator { - return func(p ProcessingResult) ProcessingResult { return p.Transform(t) } -} - -func chainExplode(e ExplodeFunction) stepCreator { - return func(p ProcessingResult) ProcessingResult { return p.Explode(e) } -} - -func chainMap(m MappingFunction) stepCreator { - return func(p ProcessingResult) ProcessingResult { return p.Map(m) } -} - -func chainFilter(f FilterFunction) stepCreator { - return func(p ProcessingResult) ProcessingResult { return p.Filter(f) } -} - -func chainSort(c CompareFunction) stepCreator { - return func(p ProcessingResult) ProcessingResult { return p.Sort(c) } -} - -func chainWithPool(pool ProcessorPool) stepCreator { - return func(p ProcessingResult) ProcessingResult { return p.WithPool(pool) } -} - -func chainParallel(n int) stepCreator { - return func(p ProcessingResult) ProcessingResult { return p.Parallel(n) } -} -func chainUnordered(p ProcessingResult) ProcessingResult { return p.Unordered() } - -func chainApply(c ProcessChain) stepCreator { - return func(p ProcessingResult) ProcessingResult { - return p.Apply(c) - } -} - -//////////////////////////////////////////////////////////////////////////////// - -var initial = Chain(logging.DefaultContext()) - -func Transform(t TransformFunction) ProcessChain { return initial.Transform(t) } -func Explode(e ExplodeFunction) ProcessChain { return initial.Explode(e) } -func Map(m MappingFunction) ProcessChain { return initial.Map(m) } -func Filter(f FilterFunction) ProcessChain { return initial.Filter(f) } -func Sort(c CompareFunction) ProcessChain { return initial.Sort(c) } -func WithPool(pool ProcessorPool) ProcessChain { return initial.WithPool(pool) } -func Parallel(n int) ProcessChain { return initial.Parallel(n) } -func Unordered() ProcessChain { return initial.Unordered() } -func Append(chain, add ProcessChain, conditions ...options.Condition) ProcessChain { - if add != nil { - if options.And(conditions...).IsTrue() { - if chain == nil { - return add - } - return chain.Append(add) - } - } - return chain -} diff --git a/cmds/ocm/pkg/processing/logging.go b/cmds/ocm/pkg/processing/logging.go deleted file mode 100644 index efdc63ee2..000000000 --- a/cmds/ocm/pkg/processing/logging.go +++ /dev/null @@ -1,7 +0,0 @@ -package processing - -import ( - ocmlog "github.com/open-component-model/ocm/pkg/logging" -) - -var REALM = ocmlog.DefineSubRealm("output processing chains", "processing") diff --git a/cmds/ocm/pkg/tree/convert.go b/cmds/ocm/pkg/tree/convert.go deleted file mode 100644 index 6c8e9a3dd..000000000 --- a/cmds/ocm/pkg/tree/convert.go +++ /dev/null @@ -1,54 +0,0 @@ -package tree - -import ( - "github.com/open-component-model/ocm/cmds/ocm/pkg/data" -) - -type Objects []Object - -func ObjectSlice(s data.Iterable) Objects { - var a Objects - i := s.Iterator() - for i.HasNext() { - a = append(a, i.Next().(Object)) - } - return a -} - -var ( - _ data.IndexedAccess = Objects{} - _ data.Iterable = Objects{} -) - -func (o Objects) Len() int { - return len(o) -} - -func (o Objects) Get(i int) interface{} { - return o[i] -} - -func (o Objects) Iterator() data.Iterator { - return data.NewIndexedIterator(o) -} - -//////////////////////////////////////////////////////////////////////////////// - -type TreeObjects []*TreeObject - -var ( - _ data.IndexedAccess = TreeObjects{} - _ data.Iterable = TreeObjects{} -) - -func (o TreeObjects) Len() int { - return len(o) -} - -func (o TreeObjects) Get(i int) interface{} { - return o[i] -} - -func (o TreeObjects) Iterator() data.Iterator { - return data.NewIndexedIterator(o) -} diff --git a/cmds/ocm/testhelper/env.go b/cmds/ocm/testhelper/env.go index 1becc71cc..d30964bdb 100644 --- a/cmds/ocm/testhelper/env.go +++ b/cmds/ocm/testhelper/env.go @@ -6,16 +6,16 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/app" - "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/config" - "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" - "github.com/open-component-model/ocm/pkg/env" - "github.com/open-component-model/ocm/pkg/env/builder" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/helper/builder" + "ocm.software/ocm/api/helper/env" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/cmds/ocm/app" ) type CLI struct { diff --git a/cmds/ocm/testhelper/forward.go b/cmds/ocm/testhelper/forward.go index 4ab63e22b..b869eede2 100644 --- a/cmds/ocm/testhelper/forward.go +++ b/cmds/ocm/testhelper/forward.go @@ -1,7 +1,7 @@ package testhelper import ( - "github.com/open-component-model/ocm/pkg/env" + "ocm.software/ocm/api/helper/env" ) var ( diff --git a/cmds/ocm/topics/common/attributes/topic.go b/cmds/ocm/topics/common/attributes/topic.go index 02593673b..6fc57bf28 100644 --- a/cmds/ocm/topics/common/attributes/topic.go +++ b/cmds/ocm/topics/common/attributes/topic.go @@ -6,8 +6,8 @@ import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/datacontext" ) func New(ctx clictx.Context) *cobra.Command { diff --git a/cmds/ocm/topics/common/config/topic.go b/cmds/ocm/topics/common/config/topic.go index c246b7062..de40ea6d7 100644 --- a/cmds/ocm/topics/common/config/topic.go +++ b/cmds/ocm/topics/common/config/topic.go @@ -3,7 +3,7 @@ package topicconfig import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" ) func New(ctx clictx.Context) *cobra.Command { diff --git a/cmds/ocm/topics/common/credentials/topic.go b/cmds/ocm/topics/common/credentials/topic.go index 1b9136e87..26cb06fd1 100644 --- a/cmds/ocm/topics/common/credentials/topic.go +++ b/cmds/ocm/topics/common/credentials/topic.go @@ -3,9 +3,9 @@ package attributes import ( "github.com/spf13/cobra" - "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" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/utils/listformat" ) func New(ctx clictx.Context) *cobra.Command { diff --git a/cmds/ocm/topics/common/logging/topic.go b/cmds/ocm/topics/common/logging/topic.go index 6ef01a8cd..a56af507e 100644 --- a/cmds/ocm/topics/common/logging/topic.go +++ b/cmds/ocm/topics/common/logging/topic.go @@ -7,10 +7,10 @@ import ( "github.com/mandelsoft/logging" "github.com/spf13/cobra" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - logcfg "github.com/open-component-model/ocm/pkg/contexts/datacontext/config/logging" - utils2 "github.com/open-component-model/ocm/pkg/listformat" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/datacontext" + logcfg "ocm.software/ocm/api/datacontext/config/logging" + utils2 "ocm.software/ocm/api/utils/listformat" ) func New(ctx clictx.Context) *cobra.Command { diff --git a/cmds/ocm/topics/oci/refs/topic.go b/cmds/ocm/topics/oci/refs/topic.go index 10452fc76..50709ed18 100644 --- a/cmds/ocm/topics/oci/refs/topic.go +++ b/cmds/ocm/topics/oci/refs/topic.go @@ -5,8 +5,8 @@ import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/oci/extensions/repositories/ctf" ) func New(ctx clictx.Context) *cobra.Command { diff --git a/cmds/ocm/topics/ocm/accessmethods/topic.go b/cmds/ocm/topics/ocm/accessmethods/topic.go index 7d3d95867..f3fa0ad87 100644 --- a/cmds/ocm/topics/ocm/accessmethods/topic.go +++ b/cmds/ocm/topics/ocm/accessmethods/topic.go @@ -3,8 +3,8 @@ package topicocmaccessmethods import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/contexts/ocm" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm" ) func New(ctx clictx.Context) *cobra.Command { diff --git a/cmds/ocm/topics/ocm/downloadhandlers/topic.go b/cmds/ocm/topics/ocm/downloadhandlers/topic.go index 9ea165bb3..cd0652dfa 100644 --- a/cmds/ocm/topics/ocm/downloadhandlers/topic.go +++ b/cmds/ocm/topics/ocm/downloadhandlers/topic.go @@ -3,8 +3,8 @@ package topicocmaccessmethods import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/downloaderoption" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/downloaderoption" ) func New(ctx clictx.Context) *cobra.Command { diff --git a/cmds/ocm/topics/ocm/labels/topic.go b/cmds/ocm/topics/ocm/labels/topic.go index 1cc967238..fda7563e7 100644 --- a/cmds/ocm/topics/ocm/labels/topic.go +++ b/cmds/ocm/topics/ocm/labels/topic.go @@ -3,8 +3,8 @@ package topicocmlabels import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm/valuemergehandler" ) func New(ctx clictx.Context) *cobra.Command { diff --git a/cmds/ocm/topics/ocm/pubsub/topic.go b/cmds/ocm/topics/ocm/pubsub/topic.go index cd7fc6db3..06ddb2470 100644 --- a/cmds/ocm/topics/ocm/pubsub/topic.go +++ b/cmds/ocm/topics/ocm/pubsub/topic.go @@ -3,8 +3,8 @@ package topicocmpubsub import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm/extensions/pubsub" ) func New(ctx clictx.Context) *cobra.Command { diff --git a/cmds/ocm/topics/ocm/refs/topic.go b/cmds/ocm/topics/ocm/refs/topic.go index ea76985b1..8dbe2e429 100644 --- a/cmds/ocm/topics/ocm/refs/topic.go +++ b/cmds/ocm/topics/ocm/refs/topic.go @@ -3,8 +3,8 @@ package topicocmrefs import ( "github.com/spf13/cobra" - topicocirefs "github.com/open-component-model/ocm/cmds/ocm/topics/oci/refs" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + topicocirefs "ocm.software/ocm/cmds/ocm/topics/oci/refs" ) func New(ctx clictx.Context) *cobra.Command { diff --git a/cmds/ocm/topics/ocm/uploadhandlers/topic.go b/cmds/ocm/topics/ocm/uploadhandlers/topic.go index a706957b4..dedf6ce4e 100644 --- a/cmds/ocm/topics/ocm/uploadhandlers/topic.go +++ b/cmds/ocm/topics/ocm/uploadhandlers/topic.go @@ -3,8 +3,8 @@ package topicocmaccessmethods import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/ocm/commands/ocmcmds/common/options/uploaderoption" - "github.com/open-component-model/ocm/pkg/contexts/clictx" + clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/options/uploaderoption" ) func New(ctx clictx.Context) *cobra.Command { diff --git a/cmds/ocm/topics/toi/bootstrapping/topic.go b/cmds/ocm/topics/toi/bootstrapping/topic.go index ae402b7c5..3bb896da6 100644 --- a/cmds/ocm/topics/toi/bootstrapping/topic.go +++ b/cmds/ocm/topics/toi/bootstrapping/topic.go @@ -3,10 +3,10 @@ package bootstapping import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/toi" + clictx "ocm.software/ocm/api/cli" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/tools/toi" + "ocm.software/ocm/api/utils/mime" ) func New(ctx clictx.Context, name string) *cobra.Command { diff --git a/cmds/subcmdplugin/cmds/cmd_test.go b/cmds/subcmdplugin/cmds/cmd_test.go index 54dac71e6..240349caa 100644 --- a/cmds/subcmdplugin/cmds/cmd_test.go +++ b/cmds/subcmdplugin/cmds/cmd_test.go @@ -8,10 +8,10 @@ import ( . "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/plugin/testutils" + . "ocm.software/ocm/api/ocm/plugin/testutils" + . "ocm.software/ocm/cmds/ocm/testhelper" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugincacheattr" + "ocm.software/ocm/api/ocm/extensions/attrs/plugincacheattr" ) var _ = Describe("subcmdplugin", func() { diff --git a/cmds/subcmdplugin/cmds/demo/cmd.go b/cmds/subcmdplugin/cmds/demo/cmd.go index aa96ac8ba..78a142f76 100644 --- a/cmds/subcmdplugin/cmds/demo/cmd.go +++ b/cmds/subcmdplugin/cmds/demo/cmd.go @@ -6,7 +6,7 @@ import ( "strings" // bind OCM configuration. - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/config" + _ "ocm.software/ocm/api/ocm/plugin/ppi/config" "github.com/spf13/cobra" ) diff --git a/cmds/subcmdplugin/cmds/group/cmd.go b/cmds/subcmdplugin/cmds/group/cmd.go index 7f53d1ea0..cdffc32e3 100644 --- a/cmds/subcmdplugin/cmds/group/cmd.go +++ b/cmds/subcmdplugin/cmds/group/cmd.go @@ -3,7 +3,7 @@ package group import ( "github.com/spf13/cobra" - "github.com/open-component-model/ocm/cmds/subcmdplugin/cmds/demo" + "ocm.software/ocm/cmds/subcmdplugin/cmds/demo" ) const Name = "group" diff --git a/cmds/subcmdplugin/main.go b/cmds/subcmdplugin/main.go index 07e0d504a..842f17752 100644 --- a/cmds/subcmdplugin/main.go +++ b/cmds/subcmdplugin/main.go @@ -4,13 +4,13 @@ import ( "os" // enable mandelsoft plugin logging configuration. - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/logging" + _ "ocm.software/ocm/api/ocm/plugin/ppi/logging" - "github.com/open-component-model/ocm/cmds/subcmdplugin/cmds/group" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/clicmd" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds" - "github.com/open-component-model/ocm/pkg/version" + "ocm.software/ocm/api/ocm/plugin/ppi" + "ocm.software/ocm/api/ocm/plugin/ppi/clicmd" + "ocm.software/ocm/api/ocm/plugin/ppi/cmds" + "ocm.software/ocm/api/version" + "ocm.software/ocm/cmds/subcmdplugin/cmds/group" ) func main() { diff --git a/components/demoplugin/Makefile b/components/demoplugin/Makefile index 12532b0b9..b10d4e36d 100644 --- a/components/demoplugin/Makefile +++ b/components/demoplugin/Makefile @@ -6,7 +6,7 @@ OCMREPO ?= ghcr.io/$(GITHUBORG)/ocm PLATFORMS = linux/amd64 linux/arm64 REPO_ROOT := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))/../.. -VERSION = $(shell go run ../../pkg/version/generate/release_generate.go print-rc-version $(CANDIDATE)) +VERSION = $(shell go run ../../api/version/generate/release_generate.go print-rc-version $(CANDIDATE)) COMMIT = $(shell git rev-parse HEAD) EFFECTIVE_VERSION = $(VERSION)+$(COMMIT) GIT_TREE_STATE := $(shell [ -z "$$(git status --porcelain 2>/dev/null)" ] && echo clean || echo dirty) @@ -21,10 +21,10 @@ GEN = $(REPO_ROOT)/gen/$(NAME) NOW := $(shell date -u +%FT%T%z) BUILD_FLAGS := "-s -w \ - -X github.com/open-component-model/ocm/pkg/version.gitVersion=$(EFFECTIVE_VERSION) \ - -X github.com/open-component-model/ocm/pkg/version.gitTreeState=$(GIT_TREE_STATE) \ - -X github.com/open-component-model/ocm/pkg/version.gitCommit=$(COMMIT) \ - -X github.com/open-component-model/ocm/pkg/version.buildDate=$(NOW)" + -X ocm.software/ocm/api/version.gitVersion=$(EFFECTIVE_VERSION) \ + -X ocm.software/ocm/api/version.gitTreeState=$(GIT_TREE_STATE) \ + -X ocm.software/ocm/api/version.gitCommit=$(COMMIT) \ + -X ocm.software/ocm/api/version.buildDate=$(NOW)" .PHONY: build diff --git a/components/ecrplugin/Makefile b/components/ecrplugin/Makefile index cb4dc4cb3..3f037f8f2 100644 --- a/components/ecrplugin/Makefile +++ b/components/ecrplugin/Makefile @@ -7,7 +7,7 @@ PLATFORMS = linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 REPO_ROOT := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))/../.. -VERSION = $(shell go run ../../pkg/version/generate/release_generate.go print-rc-version $(CANDIDATE)) +VERSION = $(shell go run ../../api/version/generate/release_generate.go print-rc-version $(CANDIDATE)) COMMIT = $(shell git rev-parse HEAD) EFFECTIVE_VERSION = $(VERSION)+$(COMMIT) GIT_TREE_STATE := $(shell [ -z "$$(git status --porcelain 2>/dev/null)" ] && echo clean || echo dirty) @@ -22,10 +22,10 @@ GEN = $(REPO_ROOT)/gen/$(NAME) NOW := $(shell date -u +%FT%T%z) BUILD_FLAGS := "-s -w \ - -X github.com/open-component-model/ocm/pkg/version.gitVersion=$(EFFECTIVE_VERSION) \ - -X github.com/open-component-model/ocm/pkg/version.gitTreeState=$(GIT_TREE_STATE) \ - -X github.com/open-component-model/ocm/pkg/version.gitCommit=$(COMMIT) \ - -X github.com/open-component-model/ocm/pkg/version.buildDate=$(NOW)" + -X ocm.software/ocm/api/version.gitVersion=$(EFFECTIVE_VERSION) \ + -X ocm.software/ocm/api/version.gitTreeState=$(GIT_TREE_STATE) \ + -X ocm.software/ocm/api/version.gitCommit=$(COMMIT) \ + -X ocm.software/ocm/api/version.buildDate=$(NOW)" .PHONY: build diff --git a/components/helmdemo/Makefile b/components/helmdemo/Makefile index 7c246ea78..626833154 100644 --- a/components/helmdemo/Makefile +++ b/components/helmdemo/Makefile @@ -7,7 +7,7 @@ OCMREPO ?= ghcr.io/$(GITHUBORG)/ocm HELMINSTCOMP = $(PROVIDER)/toi/installers/helminstaller REPO_ROOT := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))/../.. -VERSION = $(shell go run ../../pkg/version/generate/release_generate.go print-rc-version $(CANDIDATE)) +VERSION = $(shell go run ../../api/version/generate/release_generate.go print-rc-version $(CANDIDATE)) COMMIT = $(shell git rev-parse HEAD) EFFECTIVE_VERSION = $(VERSION)-$(COMMIT) HELMINSTVERSION ?= $(VERSION) diff --git a/components/helminstaller/Dockerfile b/components/helminstaller/Dockerfile index df0112adb..7356447b3 100644 --- a/components/helminstaller/Dockerfile +++ b/components/helminstaller/Dockerfile @@ -2,19 +2,19 @@ FROM --platform=$BUILDPLATFORM golang:1.22 AS builder ARG COMMIT EFFECTIVE_VERSION GIT_TREE_STATE ARG TARGETOS TARGETARCH -WORKDIR /go/src/github.com/open-component-model/ocm/ +WORKDIR /go/src/ocm.software/ocm/ COPY go.* *.go VERSION ./ -COPY pkg pkg +COPY api api COPY cmds cmds COPY hack/generate-docs hack/generate-docs -#COPY go/pkg pkg +#COPY go/api api RUN --mount=type=cache,target=/root/.cache/go-build go get -d ./... RUN --mount=type=cache,target=/root/.cache/go-build CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \ go build -o /main -ldflags "-s -w \ - -X github.com/open-component-model/ocm/pkg/version.gitVersion=$EFFECTIVE_VERSION \ - -X github.com/open-component-model/ocm/pkg/version.gitTreeState=$GIT_TREE_STATE \ - -X github.com/open-component-model/ocm/pkg/version.gitCommit=$COMMIT \ - -X github.com/open-component-model/ocm/pkg/version.buildDate=$(date -u +%FT%T%z)" \ + -X ocm.software/ocm/api/version.gitVersion=$EFFECTIVE_VERSION \ + -X ocm.software/ocm/api/version.gitTreeState=$GIT_TREE_STATE \ + -X ocm.software/ocm/api/version.gitCommit=$COMMIT \ + -X ocm.software/ocm/api/version.buildDate=$(date -u +%FT%T%z)" \ ./cmds/helminstaller ################################################################################### diff --git a/components/helminstaller/Makefile b/components/helminstaller/Makefile index a58c11f33..ccc438c2f 100644 --- a/components/helminstaller/Makefile +++ b/components/helminstaller/Makefile @@ -8,7 +8,7 @@ MULTI ?= true PLATFORMS ?= linux/amd64 linux/arm64 REPO_ROOT := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))/../.. -VERSION := $(shell go run ../../pkg/version/generate/release_generate.go print-rc-version $(CANDIDATE)) +VERSION := $(shell go run ../../api/version/generate/release_generate.go print-rc-version $(CANDIDATE)) COMMIT := $(shell git rev-parse --verify HEAD) EFFECTIVE_VERSION := $(VERSION)-$(COMMIT) GIT_TREE_STATE := $(shell [ -z "$$(git status --porcelain 2>/dev/null)" ] && echo clean || echo dirty) diff --git a/components/ocmcli/Makefile b/components/ocmcli/Makefile index 30db9447f..66979a20b 100644 --- a/components/ocmcli/Makefile +++ b/components/ocmcli/Makefile @@ -11,7 +11,7 @@ PLATFORMS = $(IMAGE_PLATFORMS) darwin/arm64 darwin/amd64 windows/amd64 REPO_ROOT := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))../.. GIT_TREE_STATE = $(shell [ -z "$$(git status --porcelain 2>/dev/null)" ] && echo clean || echo dirty) -VERSION = $(shell go run ../../pkg/version/generate/release_generate.go print-rc-version $(CANDIDATE)) +VERSION = $(shell go run ../../api/version/generate/release_generate.go print-rc-version $(CANDIDATE)) COMMIT = $(shell git rev-parse HEAD) EFFECTIVE_VERSION = $(VERSION)+$(COMMIT) PLATFORM_OS := $(shell go env GOOS) @@ -33,10 +33,10 @@ GEN = $(REPO_ROOT)/gen/$(shell basename $(realpath .)) NOW := $(shell date -u +%FT%T%z) BUILD_FLAGS := "-s -w \ - -X github.com/open-component-model/ocm/pkg/version.gitVersion=$(EFFECTIVE_VERSION) \ - -X github.com/open-component-model/ocm/pkg/version.gitTreeState=$(GIT_TREE_STATE) \ - -X github.com/open-component-model/ocm/pkg/version.gitCommit=$(COMMIT) \ - -X github.com/open-component-model/ocm/pkg/version.buildDate=$(NOW)" + -X ocm.software/ocm/api/version.gitVersion=$(EFFECTIVE_VERSION) \ + -X ocm.software/ocm/api/version.gitTreeState=$(GIT_TREE_STATE) \ + -X ocm.software/ocm/api/version.gitCommit=$(COMMIT) \ + -X ocm.software/ocm/api/version.buildDate=$(NOW)" ALPINE_LATEST_VER=$(shell curl -s https://registry.hub.docker.com/v2/repositories/library/alpine/tags | jq '.results[1].name' | xargs) diff --git a/components/subchartsdemo/Makefile b/components/subchartsdemo/Makefile index fcea0b120..67a3af7da 100644 --- a/components/subchartsdemo/Makefile +++ b/components/subchartsdemo/Makefile @@ -12,7 +12,7 @@ PODINFO_VERSION = 6.3.5 PODINFO_CHART_VERSION = 6.3.5 REPO_ROOT := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))/../.. -VERSION = $(shell go run $(REPO_ROOT)/pkg/version/generate/release_generate.go print-rc-version $(CANDIDATE)) +VERSION = $(shell go run $(REPO_ROOT)/api/version/generate/release_generate.go print-rc-version $(CANDIDATE)) COMMIT = $(shell git rev-parse HEAD) EFFECTIVE_VERSION = $(VERSION)-$(COMMIT) HELMINSTVERSION ?= $(VERSION) diff --git a/docs/command-plugins.md b/docs/command-plugins.md index 941de2fb1..1ba2bb3dc 100644 --- a/docs/command-plugins.md +++ b/docs/command-plugins.md @@ -112,7 +112,7 @@ for an automated configuration: ``` import ( // enable mandelsoft plugin logging configuration. - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/config" + _ "ocm.software/ocm/pkg/contexts/ocm/plugin/ppi/config" ) ``` @@ -142,7 +142,7 @@ be implemented directly with an anonymous import of ``` import ( // enable mandelsoft plugin logging configuration. - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/logging" + _ "ocm.software/ocm/pkg/contexts/ocm/plugin/ppi/logging" ) ``` The plugin code is then configured with the logging configuration of the OCM CLI and the mandelsoft logging frame work diff --git a/docs/reference/ocm_add_componentversions.md b/docs/reference/ocm_add_componentversions.md index 5cdca2c38..18174e6ae 100644 --- a/docs/reference/ocm_add_componentversions.md +++ b/docs/reference/ocm_add_componentversions.md @@ -185,27 +185,27 @@ The uploader name may be a path expression with the following possibilities: Alternatively, a single string value can be given representing an OCI repository reference. - - ocm/npmPackage: uploading npm artifacts + - ocm/mavenPackage: uploading maven artifacts - The ocm/npmPackage uploader is able to upload npm artifacts - as artifact archive according to the npm package spec. + The ocm/mavenPackage 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 npm repository. + 'url': the URL of the maven repository. - plugin: [downloaders provided by plugins] sub namespace of the form <plugin name>/<handler> - - ocm/mavenPackage: uploading maven artifacts + - ocm/npmPackage: uploading npm artifacts - The ocm/mavenPackage uploader is able to upload maven artifacts (whole GAV only!) - as artifact archive according to the maven artifact spec. + The ocm/npmPackage uploader is able to upload npm artifacts + as artifact archive according to the npm package 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 maven repository. + 'url': the URL of the npm repository. diff --git a/docs/reference/ocm_add_source-configuration.md b/docs/reference/ocm_add_source-configuration.md index 72242aec4..9626b9a72 100644 --- a/docs/reference/ocm_add_source-configuration.md +++ b/docs/reference/ocm_add_source-configuration.md @@ -976,7 +976,7 @@ There are several templaters that can be selected by the --templaterocm/npmPackage: uploading npm artifacts + - ocm/mavenPackage: uploading maven artifacts - The ocm/npmPackage uploader is able to upload npm artifacts - as artifact archive according to the npm package spec. + The ocm/mavenPackage 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 npm repository. + 'url': the URL of the maven repository. - plugin: [downloaders provided by plugins] sub namespace of the form <plugin name>/<handler> - - ocm/mavenPackage: uploading maven artifacts + - ocm/npmPackage: uploading npm artifacts - The ocm/mavenPackage uploader is able to upload maven artifacts (whole GAV only!) - as artifact archive according to the maven artifact spec. + The ocm/npmPackage uploader is able to upload npm artifacts + as artifact archive according to the npm package 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 maven repository. + 'url': the URL of the npm repository. diff --git a/docs/reference/ocm_transfer_commontransportarchive.md b/docs/reference/ocm_transfer_commontransportarchive.md index a4aea932d..9fcdaa3bb 100644 --- a/docs/reference/ocm_transfer_commontransportarchive.md +++ b/docs/reference/ocm_transfer_commontransportarchive.md @@ -135,27 +135,27 @@ The uploader name may be a path expression with the following possibilities: Alternatively, a single string value can be given representing an OCI repository reference. - - ocm/npmPackage: uploading npm artifacts + - ocm/mavenPackage: uploading maven artifacts - The ocm/npmPackage uploader is able to upload npm artifacts - as artifact archive according to the npm package spec. + The ocm/mavenPackage 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 npm repository. + 'url': the URL of the maven repository. - plugin: [downloaders provided by plugins] sub namespace of the form <plugin name>/<handler> - - ocm/mavenPackage: uploading maven artifacts + - ocm/npmPackage: uploading npm artifacts - The ocm/mavenPackage uploader is able to upload maven artifacts (whole GAV only!) - as artifact archive according to the maven artifact spec. + The ocm/npmPackage uploader is able to upload npm artifacts + as artifact archive according to the npm package 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 maven repository. + 'url': the URL of the npm repository. diff --git a/docs/reference/ocm_transfer_componentversions.md b/docs/reference/ocm_transfer_componentversions.md index a50e22919..4c7541e25 100644 --- a/docs/reference/ocm_transfer_componentversions.md +++ b/docs/reference/ocm_transfer_componentversions.md @@ -192,27 +192,27 @@ The uploader name may be a path expression with the following possibilities: Alternatively, a single string value can be given representing an OCI repository reference. - - ocm/npmPackage: uploading npm artifacts + - ocm/mavenPackage: uploading maven artifacts - The ocm/npmPackage uploader is able to upload npm artifacts - as artifact archive according to the npm package spec. + The ocm/mavenPackage 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 npm repository. + 'url': the URL of the maven repository. - plugin: [downloaders provided by plugins] sub namespace of the form <plugin name>/<handler> - - ocm/mavenPackage: uploading maven artifacts + - ocm/npmPackage: uploading npm artifacts - The ocm/mavenPackage uploader is able to upload maven artifacts (whole GAV only!) - as artifact archive according to the maven artifact spec. + The ocm/npmPackage uploader is able to upload npm artifacts + as artifact archive according to the npm package 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 maven repository. + 'url': the URL of the npm repository. diff --git a/docs/releasenotes/v0.12.0.md b/docs/releasenotes/v0.12.0.md index dbdd6ddad..d4f0f0b4e 100644 --- a/docs/releasenotes/v0.12.0.md +++ b/docs/releasenotes/v0.12.0.md @@ -13,7 +13,7 @@ Release v0.12.0 - enhance the auto update of the flake vendor hash (#826) - Bump the go group with 7 updates (#825) - Update README.md (#822) -- fix https://github.com/open-component-model/ocm-project/issues/196 (#819) +- fix https://ocm.software/ocm-project/issues/196 (#819) - Bump the go group with 8 updates (#816) - Fix make cmds (#810) - Adjust action (#813) diff --git a/examples/lib/README.md b/examples/lib/README.md index a1589d7cd..ae4dcd7a5 100644 --- a/examples/lib/README.md +++ b/examples/lib/README.md @@ -26,7 +26,7 @@ For working with [OCM repositories](../../docs/ocm/model.md#repositories) an app context, which can be used to retrieve OCM repositories, can be accessed with: ```go -import "github.com/open-component-model/ocm/pkg/contexts/ocm" +import "ocm.software/ocm/api/ocm" func MyFirstOCMApplication() { @@ -49,9 +49,9 @@ provide access to hosted components and component versions. To access a repository, a [repository specification](../../docs/formats/repositories/README.md) is required. Every repository type extension supported by this library -uses its own package under `github.com/open-component-model/ocm/pkg/contexts/ocm/repositories`. +uses its own package under `ocm.software/ocm/api/ocm/extensions/repositories`. To access an OCM repository based on an OCI registry the package -`github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ocireg` +`ocm.software/ocm/api/ocm/extensions/repositories/ocireg` contains the appropriate language binding for the OCI registry mappings. Those packages typically have the method `NewRepositorySpec` to create diff --git a/examples/lib/comparison-scenario/00-consumer.go b/examples/lib/comparison-scenario/00-consumer.go index db4a1d547..a4c667827 100644 --- a/examples/lib/comparison-scenario/00-consumer.go +++ b/examples/lib/comparison-scenario/00-consumer.go @@ -7,17 +7,17 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/vfs/pkg/memoryfs" - "github.com/open-component-model/ocm/examples/lib/helper" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm/signing" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" - ocmutils "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" - "github.com/open-component-model/ocm/pkg/utils/tarutils" + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/download" + "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" + ocmutils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/ocm/tools/signing" + "ocm.software/ocm/api/ocm/tools/transfer" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/tarutils" + "ocm.software/ocm/examples/lib/helper" ) func TransportTo(target ocm.Repository, src string) error { diff --git a/examples/lib/comparison-scenario/00-provider.go b/examples/lib/comparison-scenario/00-provider.go index 519338830..31d1babbb 100644 --- a/examples/lib/comparison-scenario/00-provider.go +++ b/examples/lib/comparison-scenario/00-provider.go @@ -5,11 +5,11 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/examples/lib/helper" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm/signing" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" + utils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/ocm/tools/signing" + "ocm.software/ocm/examples/lib/helper" ) func ReadConfiguration(ctx ocm.Context, cfg *helper.Config) error { diff --git a/examples/lib/comparison-scenario/01-create.go b/examples/lib/comparison-scenario/01-create.go index 469359b43..2f4f62672 100644 --- a/examples/lib/comparison-scenario/01-create.go +++ b/examples/lib/comparison-scenario/01-create.go @@ -5,16 +5,16 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/examples/lib/helper" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/elements" - "github.com/open-component-model/ocm/pkg/contexts/ocm/elements/artifactaccess/helmaccess" - "github.com/open-component-model/ocm/pkg/contexts/ocm/elements/artifactaccess/ociartifactaccess" - "github.com/open-component-model/ocm/pkg/contexts/ocm/elements/artifactblob/fileblob" - "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/mime" + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/elements" + "ocm.software/ocm/api/ocm/elements/artifactaccess/helmaccess" + "ocm.software/ocm/api/ocm/elements/artifactaccess/ociartifactaccess" + "ocm.software/ocm/api/ocm/elements/artifactblob/fileblob" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/repositories/composition" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/examples/lib/helper" ) const ( diff --git a/examples/lib/comparison-scenario/02-sign.go b/examples/lib/comparison-scenario/02-sign.go index ab85c547b..9d8075b9a 100644 --- a/examples/lib/comparison-scenario/02-sign.go +++ b/examples/lib/comparison-scenario/02-sign.go @@ -5,11 +5,11 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/examples/lib/helper" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/signing" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" + "ocm.software/ocm/api/ocm/tools/signing" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/examples/lib/helper" ) const SIGNATURE_NAME = "acme.org" diff --git a/examples/lib/comparison-scenario/03-write.go b/examples/lib/comparison-scenario/03-write.go index b727d5052..0d2ddc3dd 100644 --- a/examples/lib/comparison-scenario/03-write.go +++ b/examples/lib/comparison-scenario/03-write.go @@ -7,13 +7,13 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/examples/lib/helper" - ociidentity "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" + ociidentity "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" + "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/examples/lib/helper" ) const KEYFILE = "/tmp/comparison.pub" diff --git a/examples/lib/comparison-scenario/04-transport.go b/examples/lib/comparison-scenario/04-transport.go index 9060566d0..033fc75e9 100644 --- a/examples/lib/comparison-scenario/04-transport.go +++ b/examples/lib/comparison-scenario/04-transport.go @@ -5,12 +5,12 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/examples/lib/helper" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" + "ocm.software/ocm/api/ocm/tools/transfer" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/examples/lib/helper" ) func Transport(cfg *helper.Config) error { diff --git a/examples/lib/comparison-scenario/05-verify.go b/examples/lib/comparison-scenario/05-verify.go index 58563dbed..a2ebd9d92 100644 --- a/examples/lib/comparison-scenario/05-verify.go +++ b/examples/lib/comparison-scenario/05-verify.go @@ -5,9 +5,9 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/examples/lib/helper" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/signing" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/tools/signing" + "ocm.software/ocm/examples/lib/helper" ) func Verify(cfg *helper.Config) error { diff --git a/examples/lib/comparison-scenario/06-download.go b/examples/lib/comparison-scenario/06-download.go index 9deea201d..61661e681 100644 --- a/examples/lib/comparison-scenario/06-download.go +++ b/examples/lib/comparison-scenario/06-download.go @@ -7,12 +7,12 @@ import ( "github.com/mandelsoft/vfs/pkg/memoryfs" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/examples/lib/helper" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "github.com/open-component-model/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/utils/tarutils" + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/download" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/tarutils" + "ocm.software/ocm/examples/lib/helper" ) func Download(cfg *helper.Config) error { diff --git a/examples/lib/comparison-scenario/07-getref.go b/examples/lib/comparison-scenario/07-getref.go index c87c41b1c..ce07a923c 100644 --- a/examples/lib/comparison-scenario/07-getref.go +++ b/examples/lib/comparison-scenario/07-getref.go @@ -6,10 +6,10 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/examples/lib/helper" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + utils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/examples/lib/helper" ) func GetRef(cfg *helper.Config) error { diff --git a/examples/lib/comparison-scenario/08-deployscript.go b/examples/lib/comparison-scenario/08-deployscript.go index 4d24dec70..4a9dddc52 100644 --- a/examples/lib/comparison-scenario/08-deployscript.go +++ b/examples/lib/comparison-scenario/08-deployscript.go @@ -5,10 +5,10 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/examples/lib/helper" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + utils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/examples/lib/helper" ) func GetDeployScript(cfg *helper.Config) error { diff --git a/examples/lib/comparison-scenario/09-localize.go b/examples/lib/comparison-scenario/09-localize.go index 44e084f2a..77337a4f6 100644 --- a/examples/lib/comparison-scenario/09-localize.go +++ b/examples/lib/comparison-scenario/09-localize.go @@ -10,18 +10,18 @@ import ( "helm.sh/helm/v3/pkg/chart" "sigs.k8s.io/yaml" - "github.com/open-component-model/ocm/examples/lib/helper" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - ocmutils "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils/localize" - "github.com/open-component-model/ocm/pkg/helm/loader" - "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" - "github.com/open-component-model/ocm/pkg/utils/template" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/download" + ocmutils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/ocm/ocmutils/localize" + "ocm.software/ocm/api/tech/helm/loader" + "ocm.software/ocm/api/utils" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/api/utils/tarutils" + "ocm.software/ocm/api/utils/template" + "ocm.software/ocm/examples/lib/helper" ) type DeployDescriptor struct { diff --git a/examples/lib/comparison-scenario/main.go b/examples/lib/comparison-scenario/main.go index 7ffe43da7..f25507ec2 100644 --- a/examples/lib/comparison-scenario/main.go +++ b/examples/lib/comparison-scenario/main.go @@ -4,8 +4,8 @@ import ( "fmt" "os" - "github.com/open-component-model/ocm/examples/lib/helper" - "github.com/open-component-model/ocm/pkg/utils/template" + "ocm.software/ocm/api/utils/template" + "ocm.software/ocm/examples/lib/helper" ) const ( diff --git a/examples/lib/comparison-scenario/utils.go b/examples/lib/comparison-scenario/utils.go index d57ac8be8..c2ecf7629 100644 --- a/examples/lib/comparison-scenario/utils.go +++ b/examples/lib/comparison-scenario/utils.go @@ -10,10 +10,10 @@ import ( "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/cli" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" + "ocm.software/ocm/api/tech/signing/handlers/rsa" ) func PrintPublicKey(ctx ocm.Context, name string) { diff --git a/examples/lib/config1/example.go b/examples/lib/config1/example.go index 7269d9a87..ea27903f0 100644 --- a/examples/lib/config1/example.go +++ b/examples/lib/config1/example.go @@ -5,12 +5,12 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/examples/lib/helper" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - ociid "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - ccfg "github.com/open-component-model/ocm/pkg/contexts/credentials/config" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/directcreds" - "github.com/open-component-model/ocm/pkg/contexts/ocm" + "ocm.software/ocm/api/credentials" + ociid "ocm.software/ocm/api/credentials/builtin/oci/identity" + ccfg "ocm.software/ocm/api/credentials/config" + "ocm.software/ocm/api/credentials/extensions/repositories/directcreds" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/examples/lib/helper" ) func UsingConfigs() error { diff --git a/examples/lib/config2/example.go b/examples/lib/config2/example.go index 6995c3fdf..20ba1f8fb 100644 --- a/examples/lib/config2/example.go +++ b/examples/lib/config2/example.go @@ -6,10 +6,10 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - ociid "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/runtime" + "ocm.software/ocm/api/credentials" + ociid "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/utils/runtime" ) const CFGFILE = "examples/lib/config2/config.yaml" diff --git a/examples/lib/config3/example.go b/examples/lib/config3/example.go index c5fd4c240..f9e2b4f49 100644 --- a/examples/lib/config3/example.go +++ b/examples/lib/config3/example.go @@ -6,9 +6,9 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/contexts/config" - "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/runtime" + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/utils/runtime" ) const CFGFILE = "examples/lib/config3/config.yaml" diff --git a/examples/lib/contexts.md b/examples/lib/contexts.md index 6a61614ec..7a22afbe9 100644 --- a/examples/lib/contexts.md +++ b/examples/lib/contexts.md @@ -99,7 +99,7 @@ can be orchestrated by context builders. ## Package organization All functional areas supported by contexts can be found as sub packages of -`github.com/open-component-model/ocm/pkg/contexts`. +`ocm.software/ocm/pkg/contexts`. A context package directly contains the typical user API for the functional area. The most important interface is the interface `Context`. It acts as main @@ -122,7 +122,7 @@ might be attached to any kind of contexts. They are inherited along a context usage relation (for example ocm -> oci -> credentials -> config). If a context type supports multiple extension types there is typically -a dedicated sub package for this type (for example `github.com/open-component-model/pkg/contexts/ocm/accessmethods`), which again +a dedicated sub package for this type (for example `github.com/open-component-model/api/ocm/extensions/accessmethods`), which again contains the various implementation types in sub packages. ### The OCM context organization diff --git a/examples/lib/cred1/example.go b/examples/lib/cred1/example.go index 07325f698..aa178e0ba 100644 --- a/examples/lib/cred1/example.go +++ b/examples/lib/cred1/example.go @@ -5,14 +5,14 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/examples/lib/helper" - "github.com/open-component-model/ocm/pkg/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "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/contexts/ocm/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/mime" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/examples/lib/helper" ) func SimpleWriteWithCredentials() error { diff --git a/examples/lib/cred2/example.go b/examples/lib/cred2/example.go index 1ca1e13fc..3d01062dc 100644 --- a/examples/lib/cred2/example.go +++ b/examples/lib/cred2/example.go @@ -5,17 +5,17 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/examples/lib/helper" - "github.com/open-component-model/ocm/pkg/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "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/contexts/ocm/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/mime" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/examples/lib/helper" ) func SimpleWriteWithCredentials() (ferr error) { diff --git a/examples/lib/cred3/example.go b/examples/lib/cred3/example.go index 6e1d40709..202b4fdc2 100644 --- a/examples/lib/cred3/example.go +++ b/examples/lib/cred3/example.go @@ -5,15 +5,15 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/examples/lib/helper" - "github.com/open-component-model/ocm/pkg/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/dockerconfig" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "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/contexts/ocm/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/mime" + "ocm.software/ocm/api/credentials/extensions/repositories/dockerconfig" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/mime" + "ocm.software/ocm/examples/lib/helper" ) func SimpleWriteWithCredentials() error { diff --git a/examples/lib/creds.md b/examples/lib/creds.md index f526a3c13..27ec5336b 100644 --- a/examples/lib/creds.md +++ b/examples/lib/creds.md @@ -24,7 +24,7 @@ the credentials directly for the lookup call: defer repo.Close() ``` -Credentials are given by an object of type [`credentials.Credentials`](../../pkg/contexts/credentials/interface.go). +Credentials are given by an object of type [`credentials.Credentials`](../../api/credentials/interface.go). This is basically a set of string attributes. For OCM repositories based on OCI registries two attributes are used: - `credentials.ATTR_USERNAME` the username @@ -120,7 +120,7 @@ consumer ids always feature the attribute `type`, describing the kind of context. For example, to describe the request for credentials for -an [OCI registry](../../pkg/contexts/credentials/builtin/oci/identity/identity.go) and repository, +an [OCI registry](../../api/credentials/builtin/oci/identity/identity.go) and repository, the type value is `oci.CONSUMER_TYPE`. Additionally, the following attributes are used to fully describe the usage context. diff --git a/examples/lib/ctf.md b/examples/lib/ctf.md index 746600af5..a22f68567 100644 --- a/examples/lib/ctf.md +++ b/examples/lib/ctf.md @@ -15,7 +15,7 @@ which is the default. In this example we just use a memory based filesystem. ```go - import "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" + import "ocm.software/ocm/api/ocm/extensions/repositories/ctf" octx := ocm.DefaultContext() diff --git a/examples/lib/ctf/example.go b/examples/lib/ctf/example.go index 1a26b5f42..859c006aa 100644 --- a/examples/lib/ctf/example.go +++ b/examples/lib/ctf/example.go @@ -7,15 +7,15 @@ import ( "github.com/mandelsoft/goutils/finalizer" "github.com/mandelsoft/vfs/pkg/memoryfs" - "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/ocm" - "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/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/mime" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/mime" ) const ( diff --git a/examples/lib/example.go b/examples/lib/example.go index 510581ef2..d0e4c3ddc 100644 --- a/examples/lib/example.go +++ b/examples/lib/example.go @@ -3,11 +3,11 @@ package main import ( "fmt" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "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/contexts/ocm/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/utils" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" + "ocm.software/ocm/api/utils" ) const ( diff --git a/examples/lib/helper/helper.go b/examples/lib/helper/helper.go index 4085301df..0190c345f 100644 --- a/examples/lib/helper/helper.go +++ b/examples/lib/helper/helper.go @@ -6,9 +6,9 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/runtime" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/utils/runtime" ) type Config struct { diff --git a/examples/lib/tour/01-getting-started/README.md b/examples/lib/tour/01-getting-started/README.md index 8754370f0..72ca76f19 100644 --- a/examples/lib/tour/01-getting-started/README.md +++ b/examples/lib/tour/01-getting-started/README.md @@ -32,7 +32,7 @@ a context object. Our example uses the default context provided by the library, which covers the complete type registration contained in the executable. -It can be accessed by a function of the `pkg/contexts/ocm` package. +It can be accessed by a function of the `api/ocm` package. ```go ctx := ocm.DefaultContext() @@ -53,7 +53,7 @@ the repository and can be used to store the serialized form as part of other resources, for example Kubernetes resources or configuration settings. The available repository implementations can be found -under `.../pkg/contexts/ocm/repositories`. +under `.../api/ocm/extensions/repositories`. ```go spec := ocireg.NewRepositorySpec("ghcr.io/open-component-model/ocm") @@ -164,32 +164,32 @@ differ, because the code always describes the latest version): ``` resources of the latest version: - version: 0.12.0 + version: 0.13.0 provider: ocm.software 1: name: ocmcli extra identity: "architecture"="amd64","os"="linux" resource type: executable - access: Local blob sha256:4a3e2f2069f428e237ca7cd21d9f525ac527cde92f4ae0f797697727d5e059f9[] + access: Local blob sha256:c02de4aa8801ee0300edd31749efc7ef1cb63f082fed67861fddc6ca0ace4545[] 2: name: ocmcli extra identity: "architecture"="arm64","os"="linux" resource type: executable - access: Local blob sha256:b99a50553fdf100ec449dbfd3e822de8d5051e83070d37a3046703003ccba1db[] + access: Local blob sha256:7ac5a31e21214b533a6764c41f94703d9704a639a8c0119e6ece3070ba0e8988[] 3: name: ocmcli extra identity: "architecture"="arm64","os"="darwin" resource type: executable - access: Local blob sha256:01f913684ca0b01ac0c93e5cee98e6d3a29e908b88761a0c869b39ae8971b538[] + access: Local blob sha256:63d4604711c55532882c8625a6214186e654558abb1b28861604ad856916c921[] 4: name: ocmcli extra identity: "architecture"="amd64","os"="darwin" resource type: executable - access: Local blob sha256:ad5391320eb2662f84dbae676fb0963eb3925b0f76d966a71a89795bbac529a8[] + access: Local blob sha256:09f676530a5c4782bc9d62df7c352ab0797970aa4851e1809b39b586a18eaf57[] 5: name: ocmcli extra identity: "architecture"="amd64","os"="windows" resource type: executable - access: Local blob sha256:71ed95abd178112489f32cddeea377d2703ad6213f54a3486741f48483cd4391[] + access: Local blob sha256:ed5a761031862f09b0851613802b9856e7a57410e52aa7a71d24a8118d0d6867[] 6: name: ocmcli-image extra identity: resource type: ociImage - access: OCI artifact ghcr.io/open-component-model/ocm/ocm.software/ocmcli/ocmcli-image:0.12.0 + access: OCI artifact ghcr.io/open-component-model/ocm/ocm.software/ocmcli/ocmcli-image:0.13.0 ``` Resources have some metadata, like their identity and a resource type. diff --git a/examples/lib/tour/01-getting-started/example.go b/examples/lib/tour/01-getting-started/example.go index 23188abbb..25b5b3423 100644 --- a/examples/lib/tour/01-getting-started/example.go +++ b/examples/lib/tour/01-getting-started/example.go @@ -9,14 +9,14 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "github.com/open-component-model/ocm/pkg/contexts/ocm/extraid" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" - "github.com/open-component-model/ocm/pkg/semverutils" + "ocm.software/ocm/api/ocm" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/extensions/download" + "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" + "ocm.software/ocm/api/ocm/extraid" + utils "ocm.software/ocm/api/ocm/ocmutils" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/semverutils" ) func GettingStarted() error { @@ -46,7 +46,7 @@ func GettingStarted() error { // form as part of other resources, for example // Kubernetes resources. // The available repository implementations can be found - // under .../pkg/contexts/ocm/repositories. + // under .../api/ocm/extensions/repositories. // --- begin repository spec --- spec := ocireg.NewRepositorySpec("ghcr.io/open-component-model/ocm") // --- end repository spec --- 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 a122669da..829e3ad90 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 @@ -7,18 +7,18 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/goutils/finalizer" - "github.com/open-component-model/ocm/pkg/blobaccess" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - "github.com/open-component-model/ocm/pkg/contexts/ocm/elements" - "github.com/open-component-model/ocm/pkg/contexts/ocm/elements/artifactblob/dockermultiblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/elements/artifactblob/textblob" - ctfocm "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/semverutils" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/elements" + "ocm.software/ocm/api/ocm/elements/artifactblob/dockermultiblob" + "ocm.software/ocm/api/ocm/elements/artifactblob/textblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + ctfocm "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/mime" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/semverutils" ) // setupVersion configures a component version. @@ -70,7 +70,7 @@ func setupVersion(cv ocm.ComponentVersionAccess) error { // Here, we just use an image provided by the // OCM ecosystem. // Supported access types can be found under - // .../pkg/contexts/ocm/accessmethods. + // .../api/ocm/extensions/accessmethods. // --- begin setup image access --- acc := ociartifact.New("ghcr.io/open-component-model/ocm/ocm.software/toi/installers/helminstaller/helminstaller:0.4.0") // --- end setup image access --- diff --git a/examples/lib/tour/02-composing-a-component-version/02-composition-version.go b/examples/lib/tour/02-composing-a-component-version/02-composition-version.go index dc664d227..e2d59cd98 100644 --- a/examples/lib/tour/02-composing-a-component-version/02-composition-version.go +++ b/examples/lib/tour/02-composing-a-component-version/02-composition-version.go @@ -5,8 +5,8 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/composition" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/repositories/composition" ) func ComposingAComponentVersionB() error { 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 6cfd68ef0..573f1db9d 100644 --- a/examples/lib/tour/02-composing-a-component-version/README.md +++ b/examples/lib/tour/02-composing-a-component-version/README.md @@ -115,7 +115,7 @@ for this kind of repository, we can just refer to it. Here, we just use an image provided by the OCM ecosystem. Supported access types can be found under -.../pkg/contexts/ocm/accessmethods. +.../api/ocm/extensions/accessmethods. ```go acc := ociartifact.New("ghcr.io/open-component-model/ocm/ocm.software/toi/installers/helminstaller/helminstaller:0.4.0") diff --git a/examples/lib/tour/03-working-with-credentials/01-using-credentials.go b/examples/lib/tour/03-working-with-credentials/01-using-credentials.go index 8bf49d325..0bc75c89f 100644 --- a/examples/lib/tour/03-working-with-credentials/01-using-credentials.go +++ b/examples/lib/tour/03-working-with-credentials/01-using-credentials.go @@ -3,10 +3,10 @@ package main import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/examples/lib/helper" - ociidentity "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ocireg" + ociidentity "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" + "ocm.software/ocm/examples/lib/helper" ) func UsingCredentialsA(cfg *helper.Config) error { diff --git a/examples/lib/tour/03-working-with-credentials/02-basic-credential-management.go b/examples/lib/tour/03-working-with-credentials/02-basic-credential-management.go index 3d9a8ddf6..7d96b4497 100644 --- a/examples/lib/tour/03-working-with-credentials/02-basic-credential-management.go +++ b/examples/lib/tour/03-working-with-credentials/02-basic-credential-management.go @@ -7,12 +7,12 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/examples/lib/helper" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - ociidentity "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ocireg" + "ocm.software/ocm/api/credentials" + ociidentity "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" + "ocm.software/ocm/examples/lib/helper" ) func UsingCredentialsB(cfg *helper.Config, create bool) error { diff --git a/examples/lib/tour/03-working-with-credentials/03-credential-repositories.go b/examples/lib/tour/03-working-with-credentials/03-credential-repositories.go index a00b38a78..47e15dd83 100644 --- a/examples/lib/tour/03-working-with-credentials/03-credential-repositories.go +++ b/examples/lib/tour/03-working-with-credentials/03-credential-repositories.go @@ -5,12 +5,12 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/examples/lib/helper" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - ociidentity "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/dockerconfig" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/ocm" + "ocm.software/ocm/api/credentials" + ociidentity "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/credentials/extensions/repositories/dockerconfig" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/examples/lib/helper" ) func UsingCredentialsRepositories(cfg *helper.Config) error { diff --git a/examples/lib/tour/03-working-with-credentials/common.go b/examples/lib/tour/03-working-with-credentials/common.go index c04b397ea..fa7184809 100644 --- a/examples/lib/tour/03-working-with-credentials/common.go +++ b/examples/lib/tour/03-working-with-credentials/common.go @@ -7,18 +7,18 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/goutils/finalizer" - "github.com/open-component-model/ocm/pkg/blobaccess" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - "github.com/open-component-model/ocm/pkg/contexts/ocm/elements" - "github.com/open-component-model/ocm/pkg/contexts/ocm/elements/artifactblob/dockermultiblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/elements/artifactblob/textblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/semverutils" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/elements" + "ocm.software/ocm/api/ocm/elements/artifactblob/dockermultiblob" + "ocm.software/ocm/api/ocm/elements/artifactblob/textblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/mime" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/semverutils" ) // setupVersion configures a component version. @@ -62,7 +62,7 @@ func setupVersion(cv ocm.ComponentVersionAccess) error { // Here, we just use an image provided by the // OCM ecosystem. // Supported access types can be found under - // .../pkg/contexts/ocm/accessmethods. + // .../api/ocm/extensions/accessmethods. acc := ociartifact.New("ghcr.io/open-component-model/ocm/ocm.software/toi/installers/helminstaller/helminstaller:0.4.0") // Once we have both, the metadata and the content specification, diff --git a/examples/lib/tour/03-working-with-credentials/main.go b/examples/lib/tour/03-working-with-credentials/main.go index e3fe4a35c..01ee318c0 100644 --- a/examples/lib/tour/03-working-with-credentials/main.go +++ b/examples/lib/tour/03-working-with-credentials/main.go @@ -5,7 +5,7 @@ import ( "os" "strings" - "github.com/open-component-model/ocm/examples/lib/helper" + "ocm.software/ocm/examples/lib/helper" ) // CFG is the path to the file containing the credentials diff --git a/examples/lib/tour/04-working-with-config/01-basic-config-management.go b/examples/lib/tour/04-working-with-config/01-basic-config-management.go index 1045e20a8..f61df0936 100644 --- a/examples/lib/tour/04-working-with-config/01-basic-config-management.go +++ b/examples/lib/tour/04-working-with-config/01-basic-config-management.go @@ -7,12 +7,12 @@ import ( "github.com/go-test/deep" "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/examples/lib/helper" - "github.com/open-component-model/ocm/pkg/contexts/config" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - credcfg "github.com/open-component-model/ocm/pkg/contexts/credentials/config" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/directcreds" - "github.com/open-component-model/ocm/pkg/contexts/oci" + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/credentials" + credcfg "ocm.software/ocm/api/credentials/config" + "ocm.software/ocm/api/credentials/extensions/repositories/directcreds" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/examples/lib/helper" ) func BasicConfigurationHandling(cfg *helper.Config) error { diff --git a/examples/lib/tour/04-working-with-config/02-handle-arbitrary-config.go b/examples/lib/tour/04-working-with-config/02-handle-arbitrary-config.go index a93e914d7..0a17a1183 100644 --- a/examples/lib/tour/04-working-with-config/02-handle-arbitrary-config.go +++ b/examples/lib/tour/04-working-with-config/02-handle-arbitrary-config.go @@ -6,13 +6,13 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/examples/lib/helper" - "github.com/open-component-model/ocm/pkg/contexts/config" - configcfg "github.com/open-component-model/ocm/pkg/contexts/config/config" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - credcfg "github.com/open-component-model/ocm/pkg/contexts/credentials/config" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/directcreds" - "github.com/open-component-model/ocm/pkg/contexts/oci" + "ocm.software/ocm/api/config" + configcfg "ocm.software/ocm/api/config/extensions/config" + "ocm.software/ocm/api/credentials" + credcfg "ocm.software/ocm/api/credentials/config" + "ocm.software/ocm/api/credentials/extensions/repositories/directcreds" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/examples/lib/helper" ) func credConfig(cfg *helper.Config) (config.Config, error) { diff --git a/examples/lib/tour/04-working-with-config/03-using-ocm-config.go b/examples/lib/tour/04-working-with-config/03-using-ocm-config.go index cf8f5aa75..d88cb45c2 100644 --- a/examples/lib/tour/04-working-with-config/03-using-ocm-config.go +++ b/examples/lib/tour/04-working-with-config/03-using-ocm-config.go @@ -6,14 +6,14 @@ import ( "github.com/mandelsoft/goutils/errors" "sigs.k8s.io/yaml" - "github.com/open-component-model/ocm/examples/lib/helper" - configcfg "github.com/open-component-model/ocm/pkg/contexts/config/config" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - credcfg "github.com/open-component-model/ocm/pkg/contexts/credentials/config" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/dockerconfig" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" + configcfg "ocm.software/ocm/api/config/extensions/config" + "ocm.software/ocm/api/credentials" + credcfg "ocm.software/ocm/api/credentials/config" + "ocm.software/ocm/api/credentials/extensions/repositories/dockerconfig" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/ocm" + utils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/examples/lib/helper" ) func HandleOCMConfig(cfg *helper.Config) error { diff --git a/examples/lib/tour/04-working-with-config/04-write-config-type.go b/examples/lib/tour/04-working-with-config/04-write-config-type.go index 403fbad1c..9d9ad2cb1 100644 --- a/examples/lib/tour/04-working-with-config/04-write-config-type.go +++ b/examples/lib/tour/04-working-with-config/04-write-config-type.go @@ -7,14 +7,14 @@ import ( "github.com/mandelsoft/goutils/errors" "sigs.k8s.io/yaml" - "github.com/open-component-model/ocm/examples/lib/helper" - configcfg "github.com/open-component-model/ocm/pkg/contexts/config/config" - "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - ociidentity "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/runtime" + "ocm.software/ocm/api/config/cpi" + configcfg "ocm.software/ocm/api/config/extensions/config" + "ocm.software/ocm/api/credentials" + ociidentity "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/examples/lib/helper" ) // TYPE is the name of our new configuration object type. diff --git a/examples/lib/tour/04-working-with-config/05-write-config-consumer.go b/examples/lib/tour/04-working-with-config/05-write-config-consumer.go index bd1c578ab..c5a4166df 100644 --- a/examples/lib/tour/04-working-with-config/05-write-config-consumer.go +++ b/examples/lib/tour/04-working-with-config/05-write-config-consumer.go @@ -6,11 +6,11 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/examples/lib/helper" - "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - ociidentity "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/oci" + "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/credentials" + ociidentity "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/examples/lib/helper" ) // we already have our new acme.org config object type, diff --git a/examples/lib/tour/04-working-with-config/README.md b/examples/lib/tour/04-working-with-config/README.md index 3eef9b8a5..fa816ee17 100644 --- a/examples/lib/tour/04-working-with-config/README.md +++ b/examples/lib/tour/04-working-with-config/README.md @@ -115,7 +115,7 @@ settings for the given object and calls the appropriate methods on this object (after a type cast). Here is the code snippet from the apply method of the credential -config object ([.../pkg/contexts/credentials/config/type.go](../../../../pkg/contexts/credentials/config/type.go)): +config object ([.../api/credentials/config/type.go](../../../../../api/credentials/config/type.go)): ```go diff --git a/examples/lib/tour/04-working-with-config/common.go b/examples/lib/tour/04-working-with-config/common.go index 2c08f1d35..26f41f97e 100644 --- a/examples/lib/tour/04-working-with-config/common.go +++ b/examples/lib/tour/04-working-with-config/common.go @@ -1,7 +1,7 @@ package main import ( - "github.com/open-component-model/ocm/pkg/contexts/credentials" + "ocm.software/ocm/api/credentials" ) func obfuscate(creds credentials.Credentials) string { diff --git a/examples/lib/tour/04-working-with-config/main.go b/examples/lib/tour/04-working-with-config/main.go index 1866d3304..cf75297e1 100644 --- a/examples/lib/tour/04-working-with-config/main.go +++ b/examples/lib/tour/04-working-with-config/main.go @@ -5,7 +5,7 @@ import ( "os" "strings" - "github.com/open-component-model/ocm/examples/lib/helper" + "ocm.software/ocm/examples/lib/helper" ) // CFG is the path to the file containing the credentials diff --git a/examples/lib/tour/05-transporting-component-versions/common.go b/examples/lib/tour/05-transporting-component-versions/common.go index fb356ef73..6e3356759 100644 --- a/examples/lib/tour/05-transporting-component-versions/common.go +++ b/examples/lib/tour/05-transporting-component-versions/common.go @@ -7,17 +7,17 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/goutils/finalizer" - "github.com/open-component-model/ocm/pkg/blobaccess" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - "github.com/open-component-model/ocm/pkg/contexts/ocm/elements" - "github.com/open-component-model/ocm/pkg/contexts/ocm/elements/artifactblob/dockermultiblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/elements/artifactblob/textblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/semverutils" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/elements" + "ocm.software/ocm/api/ocm/elements/artifactblob/dockermultiblob" + "ocm.software/ocm/api/ocm/elements/artifactblob/textblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/mime" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/semverutils" ) // setupVersion configures a component version. @@ -61,7 +61,7 @@ func setupVersion(cv ocm.ComponentVersionAccess) error { // Here, we just use an image provided by the // OCM ecosystem. // Supported access types can be found under - // .../pkg/contexts/ocm/accessmethods. + // .../api/ocm/extensions/accessmethods. acc := ociartifact.New("ghcr.io/open-component-model/ocm/ocm.software/toi/installers/helminstaller/helminstaller:0.4.0") // Once we have both, the metadata and the content specification, diff --git a/examples/lib/tour/05-transporting-component-versions/example.go b/examples/lib/tour/05-transporting-component-versions/example.go index 1b1406342..4818a1add 100644 --- a/examples/lib/tour/05-transporting-component-versions/example.go +++ b/examples/lib/tour/05-transporting-component-versions/example.go @@ -5,14 +5,14 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/examples/lib/helper" - ociidentity "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" + ociidentity "ocm.software/ocm/api/credentials/builtin/oci/identity" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" + utils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/ocm/tools/transfer" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/examples/lib/helper" ) // --- begin read config --- diff --git a/examples/lib/tour/05-transporting-component-versions/main.go b/examples/lib/tour/05-transporting-component-versions/main.go index 6e6ad9b26..45661d413 100644 --- a/examples/lib/tour/05-transporting-component-versions/main.go +++ b/examples/lib/tour/05-transporting-component-versions/main.go @@ -5,7 +5,7 @@ import ( "os" "strings" - "github.com/open-component-model/ocm/examples/lib/helper" + "ocm.software/ocm/examples/lib/helper" ) // CFG is the path to the file containing the credentials diff --git a/examples/lib/tour/06-signing-component-versions/01-basic-signing.go b/examples/lib/tour/06-signing-component-versions/01-basic-signing.go index b40dfe7f7..021995ddc 100644 --- a/examples/lib/tour/06-signing-component-versions/01-basic-signing.go +++ b/examples/lib/tour/06-signing-component-versions/01-basic-signing.go @@ -5,11 +5,11 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/examples/lib/helper" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/composition" - "github.com/open-component-model/ocm/pkg/contexts/ocm/signing" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/repositories/composition" + "ocm.software/ocm/api/ocm/tools/signing" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/examples/lib/helper" ) func SigningComponentVersions(cfg *helper.Config) error { diff --git a/examples/lib/tour/06-signing-component-versions/02-using-context-settings.go b/examples/lib/tour/06-signing-component-versions/02-using-context-settings.go index faa2897cd..8340bac8a 100644 --- a/examples/lib/tour/06-signing-component-versions/02-using-context-settings.go +++ b/examples/lib/tour/06-signing-component-versions/02-using-context-settings.go @@ -5,15 +5,15 @@ import ( "github.com/mandelsoft/goutils/errors" - "github.com/open-component-model/ocm/examples/lib/helper" - configcfg "github.com/open-component-model/ocm/pkg/contexts/config/config" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/composition" - "github.com/open-component-model/ocm/pkg/contexts/ocm/signing" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" - "github.com/open-component-model/ocm/pkg/signing/signutils" + configcfg "ocm.software/ocm/api/config/extensions/config" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" + "ocm.software/ocm/api/ocm/extensions/repositories/composition" + "ocm.software/ocm/api/ocm/tools/signing" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/api/tech/signing/signutils" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/examples/lib/helper" ) func prepareComponentInRepo(ctx ocm.Context, cfg *helper.Config) error { diff --git a/examples/lib/tour/06-signing-component-versions/common.go b/examples/lib/tour/06-signing-component-versions/common.go index 15694c8c9..22e684896 100644 --- a/examples/lib/tour/06-signing-component-versions/common.go +++ b/examples/lib/tour/06-signing-component-versions/common.go @@ -9,21 +9,21 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/goutils/finalizer" - "github.com/open-component-model/ocm/examples/lib/helper" - "github.com/open-component-model/ocm/pkg/blobaccess" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "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/contexts/ocm/elements" - "github.com/open-component-model/ocm/pkg/contexts/ocm/elements/artifactblob/dockermultiblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/elements/artifactblob/textblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/semverutils" - "github.com/open-component-model/ocm/pkg/signing/signutils" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/elements" + "ocm.software/ocm/api/ocm/elements/artifactblob/dockermultiblob" + "ocm.software/ocm/api/ocm/elements/artifactblob/textblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + utils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/tech/signing/signutils" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/mime" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/semverutils" + "ocm.software/ocm/examples/lib/helper" ) // setupVersion configures a component version. @@ -67,7 +67,7 @@ func setupVersion(cv ocm.ComponentVersionAccess) error { // Here, we just use an image provided by the // OCM ecosystem. // Supported access types can be found under - // .../pkg/contexts/ocm/accessmethods. + // .../api/ocm/extensions/accessmethods. acc := ociartifact.New("ghcr.io/open-component-model/ocm/ocm.software/toi/installers/helminstaller/helminstaller:0.4.0") // Once we have both, the metadata and the content specification, diff --git a/examples/lib/tour/06-signing-component-versions/main.go b/examples/lib/tour/06-signing-component-versions/main.go index 2a31e09f1..d1ee2e83e 100644 --- a/examples/lib/tour/06-signing-component-versions/main.go +++ b/examples/lib/tour/06-signing-component-versions/main.go @@ -5,9 +5,9 @@ import ( "os" "strings" - "github.com/open-component-model/ocm/examples/lib/helper" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" - "github.com/open-component-model/ocm/pkg/signing/signutils" + "ocm.software/ocm/api/tech/signing/handlers/rsa" + "ocm.software/ocm/api/tech/signing/signutils" + "ocm.software/ocm/examples/lib/helper" ) // CFG is the path to the file containing the credentials diff --git a/examples/lib/tour/docsrc/01-getting-started/README.md b/examples/lib/tour/docsrc/01-getting-started/README.md index ea0cc9648..d12704dd0 100644 --- a/examples/lib/tour/docsrc/01-getting-started/README.md +++ b/examples/lib/tour/docsrc/01-getting-started/README.md @@ -31,7 +31,7 @@ a context object. Our example uses the default context provided by the library, which covers the complete type registration contained in the executable. -It can be accessed by a function of the `pkg/contexts/ocm` package. +It can be accessed by a function of the `api/ocm` package. ```go {{include}{../../01-getting-started/example.go}{default context}} @@ -52,7 +52,7 @@ the repository and can be used to store the serialized form as part of other resources, for example Kubernetes resources or configuration settings. The available repository implementations can be found -under `.../pkg/contexts/ocm/repositories`. +under `.../api/ocm/extensions/repositories`. ```go {{include}{../../01-getting-started/example.go}{repository spec}} diff --git a/examples/lib/tour/docsrc/02-composing-a-component-version/README.md b/examples/lib/tour/docsrc/02-composing-a-component-version/README.md index ca91bca49..6e00432e2 100644 --- a/examples/lib/tour/docsrc/02-composing-a-component-version/README.md +++ b/examples/lib/tour/docsrc/02-composing-a-component-version/README.md @@ -93,7 +93,7 @@ for this kind of repository, we can just refer to it. Here, we just use an image provided by the OCM ecosystem. Supported access types can be found under -.../pkg/contexts/ocm/accessmethods. +.../api/ocm/extensions/accessmethods. ```go {{include}{../../02-composing-a-component-version/01-basic-componentversion-creation.go}{setup image access}} diff --git a/examples/lib/tour/docsrc/04-working-with-config/README.md b/examples/lib/tour/docsrc/04-working-with-config/README.md index bd0cf2a5b..e5fc80c3e 100644 --- a/examples/lib/tour/docsrc/04-working-with-config/README.md +++ b/examples/lib/tour/docsrc/04-working-with-config/README.md @@ -90,10 +90,10 @@ settings for the given object and calls the appropriate methods on this object (after a type cast). Here is the code snippet from the apply method of the credential -config object ([.../pkg/contexts/credentials/config/type.go](../../../../pkg/contexts/credentials/config/type.go)): +config object ([.../api/credentials/config/type.go](../../../../../api/credentials/config/type.go)): ```go -{{include}{../../../../../pkg/contexts/credentials/config/type.go}{apply}} +{{include}{../../../../../api/credentials/config/type.go}{apply}} ... ``` @@ -365,7 +365,7 @@ It just checks for a valid YAML document featuring a non-empty `type` field: ```go -{{include}{../../../../../pkg/runtime/utils.go}{check}} +{{include}{../../../../../api/utils/runtime/utils.go}{check}} ``` The most important method to implement is `ApplyTo(_ cpi.Context, tgt interface{}) error`, diff --git a/examples/lib/transfer1/example.go b/examples/lib/transfer1/example.go index f1904fe61..fd92781d8 100644 --- a/examples/lib/transfer1/example.go +++ b/examples/lib/transfer1/example.go @@ -10,20 +10,20 @@ import ( "github.com/mandelsoft/vfs/pkg/osfs" "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/open-component-model/ocm/examples/lib/helper" - "github.com/open-component-model/ocm/pkg/blobaccess" - "github.com/open-component-model/ocm/pkg/common" - "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/config/configutils" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "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/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" - "github.com/open-component-model/ocm/pkg/mime" + "ocm.software/ocm/api/config/configutils" + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/tools/transfer" + "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/mime" + common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/examples/lib/helper" ) const ( diff --git a/flake.nix b/flake.nix index 75488a690..bc4984948 100644 --- a/flake.nix +++ b/flake.nix @@ -40,10 +40,10 @@ ldflags = [ "-s" "-w" - "-X github.com/open-component-model/ocm/pkg/version.gitVersion=${version}" - "-X github.com/open-component-model/ocm/pkg/version.gitTreeState=${state}" - "-X github.com/open-component-model/ocm/pkg/version.gitCommit=${gitCommit}" - # "-X github.com/open-component-model/ocm/pkg/version.buildDate=1970-01-01T0:00:00+0000" + "-X ocm.software/ocm/api/version.gitVersion=${version}" + "-X ocm.software/ocm/api/version.gitTreeState=${state}" + "-X ocm.software/ocm/api/version.gitCommit=${gitCommit}" + # "-X ocm.software/ocm/api/version.buildDate=1970-01-01T0:00:00+0000" ]; CGO_ENABLED = 0; diff --git a/go.mod b/go.mod index fe7159f3c..a57696181 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/open-component-model/ocm +module ocm.software/ocm go 1.22.5 diff --git a/hack/cross-build.sh b/hack/cross-build.sh index c6ee228f2..a3a09c30c 100755 --- a/hack/cross-build.sh +++ b/hack/cross-build.sh @@ -22,10 +22,10 @@ for i in "${build_matrix[@]}"; do CGO_ENABLED=0 GOOS=$os GOARCH=$arch GO111MODULE=on \ go build -o $bin_path \ -ldflags "-s -w \ - -X github.com/open-component-model/ocm/pkg/version.gitVersion=$EFFECTIVE_VERSION \ - -X github.com/open-component-model/ocm/pkg/version.gitTreeState=$([ -z "$(git status --porcelain 2>/dev/null)" ] && echo clean || echo dirty) \ - -X github.com/open-component-model/ocm/pkg/version.gitCommit=$(git rev-parse --verify HEAD) \ - -X github.com/open-component-model/ocm/pkg/version.buildDate=$(date -u +%FT%T%z)" \ + -X ocm.software/ocm/api/version.gitVersion=$EFFECTIVE_VERSION \ + -X ocm.software/ocm/api/version.gitTreeState=$([ -z "$(git status --porcelain 2>/dev/null)" ] && echo clean || echo dirty) \ + -X ocm.software/ocm/api/version.gitCommit=$(git rev-parse --verify HEAD) \ + -X ocm.software/ocm/api/version.buildDate=$(date -u +%FT%T%z)" \ ${PROJECT_ROOT}/cmds/ocm # create zipped file diff --git a/hack/format.sh b/hack/format.sh index 1e19c9b97..5005dbba3 100755 --- a/hack/format.sh +++ b/hack/format.sh @@ -8,9 +8,9 @@ log() { echo " === ${msg}" } -pkgprefix="github.com/open-component-model/ocm" +pkgprefix="ocm.software/ocm" -log "Format with gci" # gci write --custom-order --skip-generated -s standard -s blank -s dot -s default -s "Prefix(github.com/open-component-model/ocm)" +log "Format with gci" # gci write --custom-order --skip-generated -s standard -s blank -s dot -s default -s "Prefix(ocm.software/ocm)" GCIFMT=( -s standard -s blank -s dot -s default -s="prefix(${pkgprefix})" --custom-order ) gci diff --skip-generated "${GCIFMT[@]}" "${@%/...}" ` create a migration script usable to migrate projects using this library + +# Migrating projects using this library + +The migration was done creating a migration script `migration.mig`. It can be used +to migrate packages or whole projects using this library to the new package structure and module name. +It is executed by using the same migration Bash script `migrate.sh` using the option + +`--script ` migrate a project using the OCM library. + +Optionally, package paths can be added as additional arguments to the command line. By default, the +complete current working directory is migrated. + diff --git a/migrate.mig b/migrate.mig new file mode 100644 index 000000000..464a7a66c --- /dev/null +++ b/migrate.mig @@ -0,0 +1,84 @@ + package github.com/open-component-model/ocm + adaptMig pkg/contexts/datacontext api + adaptMig pkg/contexts/clictx api + adaptMig pkg/contexts/config api + adaptMig pkg/contexts/credentials api + adaptMig pkg/contexts/oci api + adaptMig api/clictx/config api/cli + adaptMig api/clictx/internal api/cli + adaptRename api/clictx cli + adaptRenameParent api/clictx cli + adaptMig pkg/contexts/ocm api + adaptMig api/ocm/registration api/ocm/plugin + adaptMig pkg/helm api/tech + adaptMig pkg/npm api/tech + adaptMig pkg/maven api/tech + adaptMig pkg/signing api/tech + adaptMig pkg/docker api/tech + adaptMig cmds/ocm/pkg/data cmds/ocm/common + adaptMig cmds/ocm/pkg/options cmds/ocm/common + adaptMig cmds/ocm/pkg/output cmds/ocm/common + adaptMig cmds/ocm/pkg/processing cmds/ocm/common + adaptMig cmds/ocm/pkg/tree cmds/ocm/common + adaptMig cmds/ocm/pkg/utils cmds/ocm/common + adaptRenameParent cmds/ocm/pkg common + adaptMig pkg/utils api + adaptMig pkg/runtime api/utils + adaptMig pkg/spiff api/utils + adaptMig pkg/dirtree api/utils + adaptMig pkg/blobaccess api/utils + adaptMig pkg/clisupport api/utils + adaptMig pkg/cobrautils api/utils + adaptMig pkg/encrypt api/utils + adaptMig pkg/errkind api/utils + adaptMig pkg/filelock api/utils + adaptMig pkg/mime api/utils + adaptMig pkg/out api/utils + adaptMig pkg/refmgmt api/utils + adaptMig pkg/iotools api/utils + adaptMig pkg/listformat api/utils + adaptMig pkg/logging api/utils + adaptMig pkg/registrations api/utils + adaptMig pkg/runtimefinalizer api/utils + adaptMig pkg/semverutils api/utils + adaptMig pkg/testutils api/utils + adaptMig pkg/common/accessio api/utils + adaptMig pkg/common/accessobj api/utils + adaptMig pkg/common/compression api/utils + adaptMig pkg/common api/utils + adaptRename api/utils/common misc + adaptRenameParent api/utils/common misc + adaptMig pkg/env/builder api/helper + adaptMig pkg/env api/helper + adaptMig api/config/config api/config/extensions + adaptMig api/credentials/repositories api/credentials/extensions + adaptMig api/oci/repositories api/oci/extensions + adaptMig api/oci/actions api/oci/extensions + adaptMig api/oci/attrs api/oci/extensions + adaptMig api/oci/transfer api/oci/tools + adaptMig api/ocm/repositories api/ocm/extensions + adaptMig api/ocm/accessmethods api/ocm/extensions + adaptMig api/ocm/blobhandler api/ocm/extensions + adaptMig api/ocm/attrs api/ocm/extensions + adaptMig api/ocm/download api/ocm/extensions + adaptMig api/ocm/actionhandler api/ocm/extensions + adaptMig api/ocm/digester api/ocm/extensions + adaptMig api/ocm/labels api/ocm/extensions + adaptMig api/ocm/resourcetypes api/ocm/extensions + adaptMig api/ocm/pubsub api/ocm/extensions + adaptMig pkg/toi api/ocm/tools + adaptMig api/ocm/signing api/ocm/tools + adaptMig api/ocm/transfer api/ocm/tools + adaptMig pkg/version api + adaptRename api/ocm/context types + adaptRenameParent api/ocm/context types + adaptMig api/ocm/utils/check api/ocm/ocmutils + adaptMig api/ocm/utils/defaultconfigregistry api/ocm/ocmutils + adaptMig api/ocm/utils/localize api/ocm/ocmutils + adaptMig api/ocm/utils/registry api/ocm/ocmutils + adaptRename api/ocm/utils ocmutils + adaptRenameParent api/ocm/utils ocmutils + adaptRename api/ocm/extensions/resourcetypes artifacttypes + adaptRenameParent api/ocm/extensions/resourcetypes artifacttypes + MODULE="ocm.software/ocm" + changeModuleName diff --git a/migrate.sh b/migrate.sh new file mode 100755 index 000000000..114fe7d04 --- /dev/null +++ b/migrate.sh @@ -0,0 +1,434 @@ +#!/bin/bash -e + +PACKAGE="github.com/open-component-model/ocm" + +build=(components .github Dockerfile flake.nix Makefile .goreleaser.yaml dist/config.yaml README.md .reuse/dep5) + +adapts=( api pkg cmds hack examples ) + + +addMigrator() +{ + if [ -n "$MIGRATOR" ]; then + echo "$NESTED" "$@" >>"${MIGRATOR}" + fi + if [ -n "$SCRIPT" ]; then + echo "$@" + fi +} + +package() +{ + addMigrator package "$1" + + PACKAGE="$1" + prefix="$PACKAGE/" +} + +remove() +{ + for i in "$@"; do + if [ -f "$i" -o -d "$i" ]; then + echo "removing $i" + rm -rf "$i" + else + echo "already removed $i" + fi + done +} + +subst() +{ + echo -n "s:$1\([^a-zA-Z0-9]\):$2\1:g" +} + +substDot() +{ + echo -n "s:\./$1\([^a-zA-Z0-9]\):\./$2\1:g" +} + + +adaptMig() +{ + local dst="$2/$(basename "$1")" + + addMigrator adaptMig "$1" "$2" + + for s in "${adapts[@]}" "${@:3}"; do + if [ -e "$s" ]; then + echo "${NESTED} adapting absolute $s" + find "$s" -type f -exec sed -i "$(subst "$prefix$1" "$prefix$dst")" {} \; + fi + done +} + +mig() +{ + local P + if [ "$1" == "-p" ]; then + P="$1" + shift + fi + local dst="$2/$(basename "$1")" + if [ -d "$dst" ]; then + echo "${NESTED}already migrated $1 to $dst" + return + fi + if [ ! -d "$1" ]; then + echo "${NESTED}already migrated $1 to $dst" + return + fi + echo "${NESTED}migrate $1 to $dst" + + mkdir -p "$2" + mv "$1" "$2" + + adaptMig "$1" "$2" "${@:3}" + + # adapting relative (file) paths + if [ -z "$P" ]; then + for s in cmds examples "${@:3}"; do + if [ -e "$s" ]; then + echo "${NESTED} adapting relative $s" + find "$s" -type f -exec sed -i "$(subst "$1" "$dst")" {} \; + fi + done + else + for s in cmds examples "${@:3}"; do + if [ -e "$s" ]; then + echo "${NESTED} adapting relative $s" + find "$s" -type f -exec sed -i "$(substDot "$1" "$dst")" {} \; + fi + done + fi +} + +substDirect() +{ + # -r requires unescaped brackets + echo -n "s:(import\s+)\"$1\":\1\"$2\":g" +} + +substLabeled() +{ + # -r requires unescaped brackets + echo -n "s:([a-zA-Z0-9_]+\s+)\"$1\":\1\"$2\":g" +} + +substUnLabeled() +{ + local old="$(basename "$1")" + # -r requires unescaped brackets + echo -n "s:\"$1\":$old \"$2\":g" +} + +move() +{ + if [ -d "$2" ]; then + echo "already moved $1 to $2" + return + fi + if [ ! -d "$1" ]; then + echo "already moved $1 to $2" + return + fi + + if [ "$(basename "$1")" == "$(basename "$2")" ]; then + mig "$1" "$(dirname "$2")" + return + fi + + if [ "$(dirname "$1")" == "$(dirname "$2")" ]; then + rename "$1" "$(basename "$2")" + return + fi + + mig "$1" "$(dirname "$2")" + rename "$(dirname "$2")/$(basename "$1")" "$(basename "$2")" +} + +adaptRename() +{ + local dst="$(dirname "$1")/$2" + + addMigrator adaptRename "$@" + + for s in "${adapts[@]}"; do + if [ -e "$s" ]; then + echo " adapting $s" + find "$s" -type f -exec sed -r -i -e "$(substDirect "$prefix$1" "$prefix$dst")" -e "$(substLabeled "$prefix$1" "$prefix$dst")" -e "$(substUnLabeled "$prefix$1" "$prefix$dst")" {} \; + fi + done +} + +adaptRenameParent() +{ + local dst="$(dirname "$1")/$2" + + addMigrator adaptRenameParent "$@" + + for s in "${adapts[@]}" "${@:3}"; do + if [ -e "$s" ]; then + echo " adapting $s" + find "$s" -type f -exec sed -i "$(subst "$1" "$dst")" {} \; + fi + done +} + +rename() { + local dst="$(dirname "$1")/$2" + + if [ -d "$dst" ]; then + echo already renamed "$1" to "$dst" + return + fi + if [ ! -d "$1" ]; then + echo already renamed "$1" to "$dst" + return + fi + + echo "rename $1 to $dst" + + mkdir -p "$dst" + for d in "$1"/*; do + if [ -d "$d" ]; then + if [ "$d" != testdata ]; then + NESTED=" " mig "$d" "$dst" + fi + fi + done + + local files=( "$1"/* ) + if [ ${#files[@]} -ne 1 -o "${files[0]}" != "$1/*" ]; then + mv "$1"/* "$dst" + + adaptRename "$@" + fi + + adaptRenameParent "$@" + + rmdir "$1" +} + +fixRelativePathsFor() +{ + p="$1" + dst= + lvl=1 + + while true; do + p="$p/*" + (( lvl++ )) + + files=( $p ) + + if [ ${#files[@]} -eq 1 -a "${files[0]}" == "$p" ]; then + break + fi + + echo "level $lvl: $p" + dst="$dst../" + + d="$(sed 's/\./\\\./g' <<<"$dst")" + + for f in "${files[@]}"; do + if [ -f "$f" -a ! -h "$f" ]; then + for t in "${@:2}"; do + sed -r -i -e "s:([^./])(\.\./)+$t:\1$d$t:g" "$f" + done + fi + done + done +} + + +changeModuleName() +{ + if [ -n "$MODULE" ]; then + echo "*** changing module name" + + addMigrator MODULE=\""$MODULE"\" + addMigrator changeModuleName + + for s in "${adapts[@]}" "$@" go.mod .golangci.yaml; do + find "$s" -type f -exec sed -i -e "s:$PACKAGE:$MODULE:g" -e "s:\(LABEL.*\)$MODULE:\1$PACKAGE:g" -e "s&\([uU]rl:.*\)$MODULE&\1$PACKAGE&g" {} \; + done + + #echo "*** adapting nix description" + #sed -i -e "s=github:open-component-model/ocm=github:open-component-model/ocm-core=g" README.md + fi +} + +########################################################## +# main +########################################################## + +if [ "$1" == "--reset" ]; then + echo "*** reset" + git checkout HEAD . + rm -rf api + rm -rf cmds/ocm/common + exit +fi + +mkdir -p api + +if [ "$1" == "--test" ]; then + mig pkg/contexts/ocm api "${build[@]}" + sed -i -e "s/pkg/api/g" Makefile + sed -i -e "s/pkg/api/g" components/*/Dockerfile + exit +fi + +while [ $# -gt 0 ]; do + case "$1" in + --migrator) + MIGRATOR="$2" + shift 2;; + --script) + SCRIPT="$2" + shift 2;; + --paths) + FIXPATHS=X + shift;; + --module) + MODULE=ocm.software/ocm + shift;; + --deprecated) + DEPRECATED=X + shift;; + -*) + echo "invalid option $1" >&2 + exit 1;; + *) + if [ -z "$SCRIPT" ]; then + echo "invalid extra arguments $@" >&2 + exit 1 + fi + break;; + esac +done + +package "$PACKAGE" + +if [ -n "$SCRIPT" ]; then + MIGRATOR= + adapts=( "${@}" ) + if [ ${#adapts[@]} -eq 0 ]; then + adapts=( . ) + fi + + if [ ! -f "$SCRIPT" ]; then + echo "invalid script $SCRIPT" >&2 + exit 1 + fi + echo migrating "${adapts[@]}" with script "$SCRIPT" + source "$SCRIPT" + exit 0 +fi + +if [ -n "$MIGRATOR" ]; then + echo "restructure packages and create migration script..." +else + echo "restructure packages..." +fi + +echo "*** removing deprecated packages..." +remove pkg/{contexts/options,errors,finalizer,exception,generics,optionutils,regex,tokens} pkg/utils/{pkgutils,testutils} testdata +remove pkg/helm/identity pkg/contexts/oci/identity + +if [ -n "$DEPRECATED" ]; then + exit 0 +fi + +echo "*** relocating packages..." +# contexts +for s in datacontext clictx config credentials oci; do + mig pkg/contexts/$s api +done +rename api/clictx cli + +mig pkg/contexts/ocm api "${build[@]}" +mig api/ocm/registration api/ocm/plugin + +# technology support +for s in helm npm maven signing; do + mig pkg/$s api/tech +done +mig pkg/docker api/tech .golangci.yaml + +rename cmds/ocm/pkg common + +# utils +mig pkg/utils api +mig -p pkg/runtime api/utils +for s in pkg/{spiff,dirtree,blobaccess,clisupport,cobrautils,encrypt,errkind,filelock,mime,out,refmgmt,iotools,listformat,logging,registrations,runtimefinalizer,semverutils,testutils} pkg/common/{accessio,accessobj,compression}; do + mig $s api/utils +done + +move pkg/common api/utils/misc + +for s in pkg/{env/builder,env}; do + mig $s api/helper +done + + +# now migrate sub packaged from migrated packages + +# config extensions +for s in api/config/config; do + mig $s api/config/extensions +done + +# credentials extensions +for s in api/credentials/repositories; do + mig $s api/credentials/extensions +done + +# oci extensions +for s in api/oci/{repositories,actions,attrs}; do + mig $s api/oci/extensions +done + +# oci tools support +for s in api/oci/transfer; do + mig $s api/oci/tools +done + +# ocm extensions +for s in api/ocm/{repositories,accessmethods,blobhandler,attrs,download,actionhandler,digester,labels,resourcetypes,pubsub}; do + mig $s api/ocm/extensions "${build[@]}" +done + +# ocm tools support +for s in pkg/toi api/ocm/{signing,transfer}; do + mig $s api/ocm/tools +done + +mig pkg/version api "${build[@]}" + +echo "*** renaming packages..." +rename api/ocm/context types +rename api/ocm/utils ocmutils +rename api/ocm/extensions/resourcetypes artifacttypes + +echo "*** adapting go source root folders" +sed -i -e "s/pkg/api/g" Makefile +sed -i -e "s/pkg/api/g" components/*/Dockerfile + +if [ -n "$FIXPATHS" ]; then + echo "*** adapting relative paths" + for i in api cmds examples docs; do + fixRelativePathsFor "$i" api cmds examples docs resources + done +fi + +if [ -n "$MODULE" ]; then + changeModuleName components docs "${build[@]}" go.mod .golangci.yaml +fi + +echo "*** formatting" +go fmt ./{api,cmds}/... >/dev/null + +echo "*** generating" +make generate diff --git a/pkg/blobaccess/blobaccess/access.go b/pkg/blobaccess/blobaccess/access.go deleted file mode 100644 index 4d64c9b47..000000000 --- a/pkg/blobaccess/blobaccess/access.go +++ /dev/null @@ -1,73 +0,0 @@ -package blobaccess - -import ( - "bytes" - "io" - - "github.com/mandelsoft/filepath/pkg/filepath" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" - "github.com/open-component-model/ocm/pkg/iotools" - mimetypes "github.com/open-component-model/ocm/pkg/mime" -) - -type bytesAccess struct { - _nopCloser - data []byte - origin string -} - -func DataAccessForData(data []byte, origin ...string) bpi.DataSource { - path := "" - if len(origin) > 0 { - path = filepath.Join(origin...) - } - return &bytesAccess{data: data, origin: path} -} - -func DataAccessForString(data string, origin ...string) bpi.DataSource { - return DataAccessForData([]byte(data), origin...) -} - -func (a *bytesAccess) Get() ([]byte, error) { - return a.data, nil -} - -func (a *bytesAccess) Reader() (io.ReadCloser, error) { - return iotools.ReadCloser(bytes.NewReader(a.data)), nil -} - -func (a *bytesAccess) Origin() string { - return a.origin -} - -//////////////////////////////////////////////////////////////////////////////// - -// ForString wraps a string into a BlobAccess, which does not need a close. -func ForString(mime string, data string) bpi.BlobAccess { - if mime == "" { - mime = mimetypes.MIME_TEXT - } - return ForData(mime, []byte(data)) -} - -func ProviderForString(mime, data string) bpi.BlobAccessProvider { - return bpi.BlobAccessProviderFunction(func() (bpi.BlobAccess, error) { - return ForString(mime, data), nil - }) -} - -// ForData wraps data into a BlobAccess, which does not need a close. -func ForData(mime string, data []byte) bpi.BlobAccess { - if mime == "" { - mime = mimetypes.MIME_OCTET - } - return bpi.ForStaticDataAccessAndMeta(mime, DataAccessForData(data), digest.FromBytes(data), int64(len(data))) -} - -func ProviderForData(mime string, data []byte) bpi.BlobAccessProvider { - return bpi.BlobAccessProviderFunction(func() (bpi.BlobAccess, error) { - return ForData(mime, data), nil - }) -} diff --git a/pkg/blobaccess/blobaccess/deprecated.go b/pkg/blobaccess/blobaccess/deprecated.go deleted file mode 100644 index f6d4aea82..000000000 --- a/pkg/blobaccess/blobaccess/deprecated.go +++ /dev/null @@ -1,21 +0,0 @@ -package blobaccess - -import ( - "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" - "github.com/open-component-model/ocm/pkg/blobaccess/file" -) - -// ForTemporaryFile wraps a temporary file into a BlobAccess, which does not need a close. -// Deprecated: ForTemporaryFile. -func ForTemporaryFileWithMeta(mime string, digest digest.Digest, size int64, temp vfs.File, fss ...vfs.FileSystem) bpi.BlobAccess { - return file.BlobAccessForTemporaryFile(mime, temp, file.WithFileSystem(fss...), file.WithDigest(digest), file.WithSize(size)) -} - -// ForTemporaryFile wraps a temporary file into a BlobAccess, which does not need a close. -// Deprecated: ForTemporaryFilePath. -func ForTemporaryFilePathWithMeta(mime string, digest digest.Digest, size int64, temp string, fss ...vfs.FileSystem) BlobAccess { - return file.BlobAccessForTemporaryFilePath(mime, temp, file.WithFileSystem(fss...), file.WithDigest(digest), file.WithSize(size)) -} diff --git a/pkg/blobaccess/blobaccess/interface.go b/pkg/blobaccess/blobaccess/interface.go deleted file mode 100644 index ea6084f76..000000000 --- a/pkg/blobaccess/blobaccess/interface.go +++ /dev/null @@ -1,30 +0,0 @@ -package blobaccess - -import ( - "github.com/open-component-model/ocm/pkg/blobaccess/internal" -) - -const ( - KIND_BLOB = internal.KIND_BLOB - KIND_MEDIATYPE = internal.KIND_MEDIATYPE - - BLOB_UNKNOWN_SIZE = internal.BLOB_UNKNOWN_SIZE - BLOB_UNKNOWN_DIGEST = internal.BLOB_UNKNOWN_DIGEST -) - -type ( - DataAccess = internal.DataAccess - DataReader = internal.DataReader - DataGetter = internal.DataGetter -) - -type ( - BlobAccess = internal.BlobAccess - BlobAccessProvider = internal.BlobAccessProvider - - DataSource = internal.DataSource - DigestSource = internal.DigestSource - MimeType = internal.MimeType -) - -type FileLocation = internal.FileLocation diff --git a/pkg/blobaccess/blobaccess/utils.go b/pkg/blobaccess/blobaccess/utils.go deleted file mode 100644 index 002a80c6e..000000000 --- a/pkg/blobaccess/blobaccess/utils.go +++ /dev/null @@ -1,78 +0,0 @@ -package blobaccess - -import ( - "io" - - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" - "github.com/open-component-model/ocm/pkg/iotools" -) - -func Cast[I interface{}](acc BlobAccess) I { - return bpi.Cast[I](acc) -} - -//////////////////////////////////////////////////////////////////////////////// - -// BlobData can be applied directly to a function result -// providing a BlobAccess to get the data for the provided blob. -// If the blob access providing function provides an error -// result it is passed to the caller. -func BlobData(blob DataGetter, err ...error) ([]byte, error) { - if len(err) > 0 && err[0] != nil { - return nil, err[0] - } - return blob.Get() -} - -// BlobReader can be applied directly to a function result -// providing a BlobAccess to get a reader for the provided blob. -// If the blob access providing function provides an error -// result it is passed to the caller. -func BlobReader(blob DataReader, err ...error) (io.ReadCloser, error) { - if len(err) > 0 && err[0] != nil { - return nil, err[0] - } - return blob.Reader() -} - -// DataFromProvider extracts the data for a given BlobAccess provider. -func DataFromProvider(s BlobAccessProvider) ([]byte, error) { - blob, err := s.BlobAccess() - if err != nil { - return nil, err - } - defer blob.Close() - return blob.Get() -} - -// ReaderFromProvider gets a reader for a BlobAccess provided by -// a BlobAccesssProvider. Closing the Reader also closes the BlobAccess. -func ReaderFromProvider(s BlobAccessProvider) (io.ReadCloser, error) { - blob, err := s.BlobAccess() - if err != nil { - return nil, err - } - r, err := blob.Reader() - if err != nil { - blob.Close() - return nil, err - } - return iotools.AddReaderCloser(r, blob), nil -} - -// MimeReaderFromProvider gets a reader for a BlobAccess provided by -// a BlobAccesssProvider. Closing the Reader also closes the BlobAccess. -// Additionally, the mime type of the blob is returned. -func MimeReaderFromProvider(s BlobAccessProvider) (io.ReadCloser, string, error) { - blob, err := s.BlobAccess() - if err != nil { - return nil, "", err - } - mime := blob.MimeType() - r, err := blob.Reader() - if err != nil { - blob.Close() - return nil, "", err - } - return iotools.AddReaderCloser(r, blob), mime, nil -} diff --git a/pkg/blobaccess/bpi/interface.go b/pkg/blobaccess/bpi/interface.go deleted file mode 100644 index 75af2f949..000000000 --- a/pkg/blobaccess/bpi/interface.go +++ /dev/null @@ -1,52 +0,0 @@ -package bpi - -import ( - "github.com/mandelsoft/goutils/errors" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/blobaccess/internal" - "github.com/open-component-model/ocm/pkg/refmgmt" - "github.com/open-component-model/ocm/pkg/utils" -) - -const ( - KIND_BLOB = internal.KIND_BLOB - KIND_MEDIATYPE = internal.KIND_MEDIATYPE - - BLOB_UNKNOWN_SIZE = internal.BLOB_UNKNOWN_SIZE - BLOB_UNKNOWN_DIGEST = internal.BLOB_UNKNOWN_DIGEST -) - -var ErrClosed = refmgmt.ErrClosed - -type DataAccess = internal.DataAccess - -type ( - BlobAccess = internal.BlobAccess - BlobAccessBase = internal.BlobAccessBase - BlobAccessProvider = internal.BlobAccessProvider - - Validatable = utils.Validatable - - DataReader = internal.DataReader - DataGetter = internal.DataGetter - DataSource = internal.DataSource - DigestSource = internal.DigestSource - MimeType = internal.MimeType -) - -type FileLocation = internal.FileLocation - -type BlobAccessProviderFunction func() (BlobAccess, error) - -func (p BlobAccessProviderFunction) BlobAccess() (BlobAccess, error) { - return p() -} - -func ErrBlobNotFound(digest digest.Digest) error { - return errors.ErrNotFound(KIND_BLOB, digest.String()) -} - -func IsErrBlobNotFound(err error) bool { - return errors.IsErrNotFoundKind(err, KIND_BLOB) -} diff --git a/pkg/blobaccess/bpi/utils.go b/pkg/blobaccess/bpi/utils.go deleted file mode 100644 index 50be302ea..000000000 --- a/pkg/blobaccess/bpi/utils.go +++ /dev/null @@ -1,224 +0,0 @@ -package bpi - -import ( - "io" - "sync" - "sync/atomic" - - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/iotools" -) - -type _dataAccess = DataAccess - -type baseAccess interface { - base() BlobAccessBase -} - -func Cast[I interface{}](acc BlobAccess) I { - var _nil I - - var b BlobAccessBase = acc - - for b != nil { - if i, ok := b.(I); ok { - return i - } - if i, ok := b.(baseAccess); ok { - b = i.base() - } else { - b = nil - } - } - return _nil -} - -//////////////////////////////////////////////////////////////////////////////// - -type blobAccess struct { - _dataAccess - - lock sync.RWMutex - digest digest.Digest - size int64 - mimeType string -} - -func (b *blobAccess) MimeType() string { - return b.mimeType -} - -func (b *blobAccess) DigestKnown() bool { - b.lock.RLock() - defer b.lock.RUnlock() - return b.digest != "" -} - -func (b *blobAccess) Digest() digest.Digest { - b.lock.Lock() - defer b.lock.Unlock() - return b._Digest() -} - -func (b *blobAccess) _Digest() digest.Digest { - if b.digest == "" { - b.update() - } - return b.digest -} - -func (b *blobAccess) Size() int64 { - b.lock.Lock() - defer b.lock.Unlock() - return b._Size() -} - -func (b *blobAccess) _Size() int64 { - if b.size < 0 { - b.update() - } - return b.size -} - -func (b *blobAccess) update() error { - reader, err := b.Reader() - if err != nil { - return err - } - - defer reader.Close() - count := iotools.NewCountingReader(reader) - - digest, err := digest.Canonical.FromReader(count) - if err != nil { - return err - } - - b.size = count.Size() - b.digest = digest - - return nil -} - -type closableBlobAccess struct { - blobAccess - closed atomic.Bool -} - -func (b *closableBlobAccess) Close() error { - b.lock.Lock() - defer b.lock.Unlock() - if !b.closed.Load() { - tmp := b._dataAccess - b.closed.Store(true) - b._dataAccess = nil - return tmp.Close() - } - return ErrClosed -} - -func (b *closableBlobAccess) Get() ([]byte, error) { - b.lock.Lock() - defer b.lock.Unlock() - - if b.closed.Load() { - return nil, ErrClosed - } - return b.blobAccess.Get() -} - -func (b *closableBlobAccess) Reader() (io.ReadCloser, error) { - b.lock.Lock() - defer b.lock.Unlock() - - if b.closed.Load() { - return nil, ErrClosed - } - return b.blobAccess.Reader() -} - -func (b *closableBlobAccess) Digest() digest.Digest { - b.lock.Lock() - defer b.lock.Unlock() - - if b.closed.Load() { - return b.digest - } - return b.blobAccess._Digest() -} - -func (b *closableBlobAccess) Size() int64 { - b.lock.Lock() - defer b.lock.Unlock() - - if b.closed.Load() { - return b.size - } - return b.blobAccess._Size() -} - -// BaseAccessForDataAccess is used for a general data. -// It calculated the metadata on-the-fly for the content -// of the data access. The content may not be changing. -func BaseAccessForDataAccess(mime string, acc DataAccess) BlobAccessBase { - return &closableBlobAccess{ - blobAccess: blobAccess{mimeType: mime, _dataAccess: acc, digest: BLOB_UNKNOWN_DIGEST, size: BLOB_UNKNOWN_SIZE}, - } -} - -// BaseAccessForDataAccessAndMeta provides a BlobAccessBase for a DataAccess -// adding the additional blob access metadata (mime, digest, and size). -// Digest and size can be set to unknown using the constants (BLOB_UNKNOWN_DIGEST -// and BLOB_UNKNOWN_SIZE). -func BaseAccessForDataAccessAndMeta(mime string, acc DataAccess, dig digest.Digest, size int64) BlobAccessBase { - return &closableBlobAccess{ - blobAccess: blobAccess{mimeType: mime, _dataAccess: acc, digest: dig, size: size}, - } -} - -//////////////////////////////////////////////////////////////////////////////// - -// StaticBlobAccess is a BlobAccess which does not -// require finalization, therefore it can be used -// as BlobAccessProvider, also. -type StaticBlobAccess interface { - BlobAccess - BlobAccessProvider -} - -type staticBlobAccess struct { - blobAccess -} - -func (s *staticBlobAccess) Dup() (BlobAccess, error) { - return s, nil -} - -func (s *staticBlobAccess) BlobAccess() (BlobAccess, error) { - return s, nil -} - -func (s *staticBlobAccess) Close() error { - return nil -} - -// ForStaticDataAccess is used for a data access using no closer. -// They don't require a finalization and can be used -// as long as they exist. Therefore, no ref counting -// is required and they can be used as BlobAccessProvider, also. -func ForStaticDataAccess(mime string, acc DataAccess) StaticBlobAccess { - return &staticBlobAccess{ - blobAccess: blobAccess{mimeType: mime, _dataAccess: acc, digest: BLOB_UNKNOWN_DIGEST, size: BLOB_UNKNOWN_SIZE}, - } -} - -// ForStaticDataAccessAndMeta provides a StaticBlobAccess for a DataAccess -// adding the additional blob access metadata (mime, digest, and size). -// Digest and size can be set to unknown using the constants (BLOB_UNKNOWN_DIGEST -// and BLOB_UNKNOWN_SIZE). -func ForStaticDataAccessAndMeta(mime string, acc DataAccess, dig digest.Digest, size int64) StaticBlobAccess { - return &staticBlobAccess{ - blobAccess: blobAccess{mimeType: mime, _dataAccess: acc, digest: dig, size: size}, - } -} diff --git a/pkg/blobaccess/bpi/view.go b/pkg/blobaccess/bpi/view.go deleted file mode 100644 index abfc174c5..000000000 --- a/pkg/blobaccess/bpi/view.go +++ /dev/null @@ -1,96 +0,0 @@ -package bpi - -import ( - "fmt" - "io" - - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/refmgmt" - "github.com/open-component-model/ocm/pkg/utils" -) - -func NewBlobAccessForBase(acc BlobAccessBase, closer ...io.Closer) BlobAccess { - return refmgmt.WithView[BlobAccessBase, BlobAccess](acc, blobAccessViewCreator, closer...) -} - -func blobAccessViewCreator(blob BlobAccessBase, view *refmgmt.View[BlobAccess]) BlobAccess { - return &blobAccessView{view, blob} -} - -type blobAccessView struct { - *refmgmt.View[BlobAccess] - baseblob BlobAccessBase -} - -var ( - _ utils.Validatable = (*blobAccessView)(nil) - _ utils.Unwrappable = (*blobAccessView)(nil) -) - -func (b *blobAccessView) base() BlobAccessBase { - return b.baseblob -} - -func (b *blobAccessView) Unwrap() interface{} { - return b.baseblob -} - -func (b *blobAccessView) Close() error { - return b.View.Close() -} - -func (b *blobAccessView) Validate() error { - return utils.ValidateObject(b.baseblob) -} - -func (b *blobAccessView) Get() (result []byte, err error) { - return result, b.Execute(func() error { - result, err = b.baseblob.Get() - if err != nil { - return err - } - return nil - }) -} - -func (b *blobAccessView) Reader() (result io.ReadCloser, err error) { - return result, b.Execute(func() error { - result, err = b.baseblob.Reader() - if err != nil { - return fmt.Errorf("unable to read access: %w", err) - } - - return nil - }) -} - -func (b *blobAccessView) Digest() (result digest.Digest) { - err := b.Execute(func() error { - result = b.baseblob.Digest() - return nil - }) - if err != nil { - return BLOB_UNKNOWN_DIGEST - } - return -} - -func (b *blobAccessView) MimeType() string { - return b.baseblob.MimeType() -} - -func (b *blobAccessView) DigestKnown() bool { - return b.baseblob.DigestKnown() -} - -func (b *blobAccessView) Size() (result int64) { - err := b.Execute(func() error { - result = b.baseblob.Size() - return nil - }) - if err != nil { - return BLOB_UNKNOWN_SIZE - } - return -} diff --git a/pkg/blobaccess/deprecated.go b/pkg/blobaccess/deprecated.go deleted file mode 100644 index 30d1bf48c..000000000 --- a/pkg/blobaccess/deprecated.go +++ /dev/null @@ -1,11 +0,0 @@ -package blobaccess - -import ( - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" -) - -// DataAccessForBytes wraps a bytes slice into a DataAccess. -// Deprecated: used DataAccessForData. -func DataAccessForBytes(data []byte, origin ...string) DataSource { - return blobaccess.DataAccessForData(data, origin...) -} diff --git a/pkg/blobaccess/dirtree/access.go b/pkg/blobaccess/dirtree/access.go deleted file mode 100644 index 4da131a9f..000000000 --- a/pkg/blobaccess/dirtree/access.go +++ /dev/null @@ -1,77 +0,0 @@ -package dirtree - -import ( - "compress/gzip" - "fmt" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/optionutils" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" - "github.com/open-component-model/ocm/pkg/blobaccess/file" - "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" -) - -func DataAccess(path string, opts ...Option) (bpi.DataAccess, error) { - blobAccess, err := BlobAccess(path, opts...) - if err != nil { - return nil, err - } - return blobAccess, nil -} - -func BlobAccess(path string, opts ...Option) (_ bpi.BlobAccess, rerr error) { - eff := optionutils.EvalOptions(opts...) - fs := utils.FileSystem(eff.FileSystem) - - ok, err := vfs.IsDir(fs, path) - if err != nil { - return nil, err - } - if !ok { - return nil, fmt.Errorf("%q is no directory", path) - } - - taropts := tarutils.TarFileSystemOptions{ - IncludeFiles: eff.IncludeFiles, - ExcludeFiles: eff.ExcludeFiles, - PreserveDir: utils.AsBool(eff.PreserveDir), - FollowSymlinks: utils.AsBool(eff.FollowSymlinks), - } - - temp, err := file.NewTempFile(fs.FSTempDir(), "resourceblob*.tgz", fs) - if err != nil { - return nil, err - } - defer errors.PropagateError(&rerr, temp.Close) - - if utils.AsBool(eff.CompressWithGzip) { - if eff.MimeType == "" { - eff.MimeType = mime.MIME_TGZ - } - gw := gzip.NewWriter(temp.Writer()) - if err := tarutils.PackFsIntoTar(fs, path, gw, taropts); err != nil { - return nil, fmt.Errorf("unable to tar input artifact: %w", err) - } - if err := gw.Close(); err != nil { - return nil, fmt.Errorf("unable to close gzip writer: %w", err) - } - } else { - if eff.MimeType == "" { - eff.MimeType = mime.MIME_TAR - } - if err := tarutils.PackFsIntoTar(fs, path, temp.Writer(), taropts); err != nil { - return nil, fmt.Errorf("unable to tar input artifact: %w", err) - } - } - return temp.AsBlob(eff.MimeType), nil -} - -func Provider(path string, opts ...Option) bpi.BlobAccessProvider { - return bpi.BlobAccessProviderFunction(func() (bpi.BlobAccess, error) { - return BlobAccess(path, opts...) - }) -} diff --git a/pkg/blobaccess/dirtree/deprecated.go b/pkg/blobaccess/dirtree/deprecated.go deleted file mode 100644 index e5b32baac..000000000 --- a/pkg/blobaccess/dirtree/deprecated.go +++ /dev/null @@ -1,17 +0,0 @@ -package dirtree - -import ( - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" -) - -// BlobAccessForDirTree returns a BlobAccess for the given directory tree. -// Deprecated: use BlobAccess. -func BlobAccessForDirTree(path string, opts ...Option) (_ bpi.BlobAccess, rerr error) { - return BlobAccess(path, opts...) -} - -// BlobAccessProviderForDirTree returns a BlobAccessProvider for the given directory tree. -// Deprecated: use Provider. -func BlobAccessProviderForDirTree(path string, opts ...Option) bpi.BlobAccessProvider { - return Provider(path, opts...) -} diff --git a/pkg/blobaccess/dirtree/options.go b/pkg/blobaccess/dirtree/options.go deleted file mode 100644 index 3eba40a11..000000000 --- a/pkg/blobaccess/dirtree/options.go +++ /dev/null @@ -1,134 +0,0 @@ -package dirtree - -import ( - "github.com/mandelsoft/goutils/optionutils" - "github.com/mandelsoft/vfs/pkg/vfs" - "golang.org/x/exp/slices" - - "github.com/open-component-model/ocm/pkg/utils" -) - -type Option = optionutils.Option[*Options] - -type Options struct { - // FileSystem defines the file system that contains the specified directory. - FileSystem vfs.FileSystem - MimeType string - // CompressWithGzip defines whether the specified directory should be compressed. - CompressWithGzip *bool `json:"compress,omitempty"` - // PreserveDir defines that the specified directory should be included in the blob. - PreserveDir *bool `json:"preserveDir,omitempty"` - // IncludeFiles is a list of shell file name patterns that describe the files that should be included. - // If nothing is defined, all files are included. - IncludeFiles []string `json:"includeFiles,omitempty"` - // ExcludeFiles is a list of shell file name patterns that describe the files that should be excluded from the resulting tar. - // Excluded files always overwrite included files. - ExcludeFiles []string `json:"excludeFiles,omitempty"` - // FollowSymlinks configures to follow and resolve symlinks when a directory is tarred. - // This options will include the content of the symlink directly in the tar. - // This option should be used with care. - FollowSymlinks *bool `json:"followSymlinks,omitempty"` -} - -func (o *Options) ApplyTo(opts *Options) { - if opts == nil { - return - } - if o.FileSystem != nil { - opts.FileSystem = o.FileSystem - } - if o.MimeType != "" { - opts.MimeType = o.MimeType - } - if o.CompressWithGzip != nil { - opts.CompressWithGzip = utils.BoolP(*o.CompressWithGzip) - } - if o.PreserveDir != nil { - opts.PreserveDir = utils.BoolP(*o.PreserveDir) - } - if len(o.IncludeFiles) != 0 { - opts.IncludeFiles = slices.Clone(o.IncludeFiles) - } - if len(o.ExcludeFiles) != 0 { - opts.ExcludeFiles = slices.Clone(o.ExcludeFiles) - } - if o.FollowSymlinks != nil { - opts.FollowSymlinks = utils.BoolP(*o.FollowSymlinks) - } -} - -//////////////////////////////////////////////////////////////////////////////// - -type fileSystem struct { - fs vfs.FileSystem -} - -func (o *fileSystem) ApplyTo(opts *Options) { - opts.FileSystem = o.fs -} - -func WithFileSystem(fs vfs.FileSystem) Option { - return &fileSystem{fs: fs} -} - -//////////////////////////////////////////////////////////////////////////////// - -type mimeType string - -func (o mimeType) ApplyTo(opts *Options) { - opts.MimeType = string(o) -} - -func WithMimeType(mime string) Option { - return mimeType(mime) -} - -type compressWithGzip bool - -func (o compressWithGzip) ApplyTo(opts *Options) { - opts.CompressWithGzip = utils.BoolP(o) -} - -func WithCompressWithGzip(b ...bool) Option { - return compressWithGzip(utils.OptionalDefaultedBool(true, b...)) -} - -type preserveDir bool - -func (o preserveDir) ApplyTo(opts *Options) { - opts.PreserveDir = utils.BoolP(o) -} - -func WithPreserveDir(b ...bool) Option { - return preserveDir(utils.OptionalDefaultedBool(true, b...)) -} - -type includeFiles []string - -func (o includeFiles) ApplyTo(opts *Options) { - opts.IncludeFiles = slices.Clone(o) -} - -func WithIncludeFiles(files []string) Option { - return includeFiles(files) -} - -type excludeFiles []string - -func (o excludeFiles) ApplyTo(opts *Options) { - opts.ExcludeFiles = slices.Clone(o) -} - -func WithExcludeFiles(files []string) Option { - return excludeFiles(files) -} - -type followSymlinks bool - -func (o followSymlinks) ApplyTo(opts *Options) { - opts.FollowSymlinks = utils.BoolP(o) -} - -func WithFollowSymlinks(b ...bool) Option { - return followSymlinks(utils.OptionalDefaultedBool(true, b...)) -} diff --git a/pkg/blobaccess/dockerdaemon/access.go b/pkg/blobaccess/dockerdaemon/access.go deleted file mode 100644 index e7cb29fb9..000000000 --- a/pkg/blobaccess/dockerdaemon/access.go +++ /dev/null @@ -1,76 +0,0 @@ -package dockerdaemon - -import ( - "fmt" - - "github.com/mandelsoft/goutils/optionutils" - - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" - "github.com/open-component-model/ocm/pkg/contexts/oci/annotations" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/docker" - cpi "github.com/open-component-model/ocm/pkg/contexts/oci/types" -) - -func (o *Options) OCIContext() cpi.Context { - if o.Context == nil { - return cpi.DefaultContext() - } - return o.Context -} - -func ImageInfoFor(name string, opts ...Option) (locator string, version string, err error) { - eff := optionutils.EvalOptions(opts...) - - locator, version, err = docker.ParseGenericRef(name) - if err != nil { - return "", "", err - } - - if version == "" || version == "latest" || optionutils.AsValue(eff.OverrideVersion) { - version = eff.Version - } - if version == "" { - return "", "", fmt.Errorf("no version specified") - } - return locator, version, nil -} - -func Provider(name string, opts ...Option) bpi.BlobAccessProvider { - return bpi.BlobAccessProviderFunction(func() (bpi.BlobAccess, error) { - b, _, err := BlobAccess(name, opts...) - return b, err - }) -} - -// BlobAccess returns a BlobAccess for the image with the given name. -func BlobAccess(name string, opts ...Option) (bpi.BlobAccess, string, error) { - eff := optionutils.EvalOptions(opts...) - ctx := eff.OCIContext() - - locator, version, err := ImageInfoFor(name, eff) - if err != nil { - return nil, "", err - } - spec := docker.NewRepositorySpec() - repo, err := ctx.RepositoryForSpec(spec) - if err != nil { - return nil, "", err - } - ns, err := repo.LookupNamespace(locator) - if err != nil { - return nil, "", err - } - blob, err := artifactset.SynthesizeArtifactBlob(ns, version, - func(art cpi.ArtifactAccess) error { - if eff.Origin != nil { - art.Artifact().SetAnnotation(annotations.COMPVERS_ANNOTATION, eff.Origin.String()) - } - return nil - }, - ) - if err != nil { - return nil, "", err - } - return blob, version, nil -} diff --git a/pkg/blobaccess/dockerdaemon/deprecated.go b/pkg/blobaccess/dockerdaemon/deprecated.go deleted file mode 100644 index 9ceb05f60..000000000 --- a/pkg/blobaccess/dockerdaemon/deprecated.go +++ /dev/null @@ -1,17 +0,0 @@ -package dockerdaemon - -import ( - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" -) - -// BlobAccessProviderForImageFromDockerDaemon returns a BlobAccessProvider for the image with the given name. -// Deprecated: use Provider. -func BlobAccessProviderForImageFromDockerDaemon(name string, opts ...Option) bpi.BlobAccessProvider { - return Provider(name, opts...) -} - -// BlobAccessForImageFromDockerDaemon returns a BlobAccess for the image with the given name. -// Decrecated: use BlobAccess. -func BlobAccessForImageFromDockerDaemon(name string, opts ...Option) (bpi.BlobAccess, string, error) { - return BlobAccess(name, opts...) -} diff --git a/pkg/blobaccess/dockerdaemon/options.go b/pkg/blobaccess/dockerdaemon/options.go deleted file mode 100644 index b9e8da91a..000000000 --- a/pkg/blobaccess/dockerdaemon/options.go +++ /dev/null @@ -1,110 +0,0 @@ -package dockerdaemon - -import ( - "github.com/mandelsoft/goutils/optionutils" - - "github.com/open-component-model/ocm/pkg/common" - cpi "github.com/open-component-model/ocm/pkg/contexts/oci/types" - "github.com/open-component-model/ocm/pkg/utils" -) - -type Option = optionutils.Option[*Options] - -type Options struct { - Context cpi.Context - Name string - Version string - OverrideVersion *bool - Origin *common.NameVersion -} - -func (o *Options) ApplyTo(opts *Options) { - if opts == nil { - return - } - if o.Context != nil { - opts.Context = o.Context - } - if o.Name != "" { - opts.Name = o.Name - } - if o.Version != "" { - opts.Version = o.Version - } - if o.OverrideVersion != nil { - opts.OverrideVersion = o.OverrideVersion - } - if o.Origin != nil { - opts.Origin = o.Origin - } -} - -//////////////////////////////////////////////////////////////////////////////// - -type context struct { - cpi.Context -} - -func (o context) ApplyTo(opts *Options) { - opts.Context = o -} - -func WithContext(ctx cpi.ContextProvider) Option { - return context{ctx.OCIContext()} -} - -//////////////////////////////////////////////////////////////////////////////// - -type name string - -func (o name) ApplyTo(opts *Options) { - opts.Name = string(o) -} - -func WithName(n string) Option { - return name(n) -} - -//////////////////////////////////////////////////////////////////////////////// - -type version string - -func (o version) ApplyTo(opts *Options) { - opts.Version = string(o) -} - -func WithVersion(v string) Option { - return version(v) -} - -//////////////////////////////////////////////////////////////////////////////// - -type override struct { - flag bool - version string -} - -func (o *override) ApplyTo(opts *Options) { - opts.OverrideVersion = utils.BoolP(o.flag) - opts.Version = o.version -} - -func WithVersionOverride(v string, flag ...bool) Option { - return &override{ - version: v, - flag: utils.OptionalDefaultedBool(true, flag...), - } -} - -//////////////////////////////////////////////////////////////////////////////// - -type compvers common.NameVersion - -func (o compvers) ApplyTo(opts *Options) { - n := common.NameVersion(o) - opts.Origin = &n -} - -func WithOrigin(o common.NameVersion) Option { - return compvers(o) -} diff --git a/pkg/blobaccess/dockermulti/access.go b/pkg/blobaccess/dockermulti/access.go deleted file mode 100644 index cc8d5dd28..000000000 --- a/pkg/blobaccess/dockermulti/access.go +++ /dev/null @@ -1,156 +0,0 @@ -package dockermulti - -import ( - "fmt" - - . "github.com/mandelsoft/goutils/finalizer" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/optionutils" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/annotations" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/docker" -) - -func (o *Options) OCIContext() oci.Context { - if o.Context == nil { - return oci.DefaultContext() - } - return o.Context -} - -func (s *Options) getVariant(ctx oci.Context, finalize *Finalizer, variant string) (oci.ArtifactAccess, error) { - locator, version, err := docker.ParseGenericRef(variant) - if err != nil { - return nil, err - } - if version == "" { - return nil, fmt.Errorf("artifact version required") - } - spec := docker.NewRepositorySpec() - repo, err := ctx.RepositoryForSpec(spec) - if err != nil { - return nil, err - } - finalize.Close(repo) - ns, err := repo.LookupNamespace(locator) - if err != nil { - return nil, err - } - finalize.Close(ns) - - art, err := ns.GetArtifact(version) - if err != nil { - return nil, artifactset.GetArtifactError{Original: err, Ref: locator + ":" + version} - } - finalize.Close(art) - return art, nil -} - -func BlobAccess(opts ...Option) (bpi.BlobAccess, error) { - eff := optionutils.EvalOptions(opts...) - ctx := eff.OCIContext() - - index := artdesc.NewIndex() - i := 0 - - version := eff.Version - if eff.Origin != nil { - if version == "" { - version = eff.Origin.GetVersion() - } - index.SetAnnotation(annotations.COMPVERS_ANNOTATION, eff.Origin.String()) - } - if version == "" { - return nil, fmt.Errorf("no versio specified") - } - - feedback := func(blob bpi.BlobAccess, art cpi.ArtifactAccess) error { - desc := artdesc.DefaultBlobDescriptor(blob) - if art.IsManifest() { - cfgBlob, err := art.ManifestAccess().GetConfigBlob() - if err != nil { - return errors.Wrapf(err, "cannot get config blob") - } - cfg, err := artdesc.ParseImageConfig(cfgBlob) - if err != nil { - return errors.Wrapf(err, "cannot parse config blob") - } - if cfg.Architecture != "" { - desc.Platform = &artdesc.Platform{ - Architecture: cfg.Architecture, - OS: cfg.OS, - Variant: cfg.Variant, - } - } - } - index.AddManifest(desc) - return nil - } - - blob, err := artifactset.SynthesizeArtifactBlobFor(version, func() (fac artifactset.ArtifactFactory, main bool, err error) { - var art cpi.ArtifactAccess - var blob bpi.BlobAccess - - switch { - case i > len(eff.Variants): - // end loop - case i == len(eff.Variants): - // provide index (main) artifact - if eff.Printer != nil { - eff.Printer.Printf("image %d: INDEX\n", i) - } - fac = func(set *artifactset.ArtifactSet) (digest.Digest, string, error) { - art, err = set.NewArtifact(index) - if err != nil { - return "", "", errors.Wrapf(err, "cannot create index artifact") - } - defer art.Close() - blob, err = set.AddArtifact(art) - if err != nil { - return "", "", errors.Wrapf(err, "cannot add index artifact") - } - defer blob.Close() - return blob.Digest(), blob.MimeType(), nil - } - main = true - default: - // provide variant - if eff.Printer != nil { - eff.Printer.Printf("image %d: %s\n", i, eff.Variants[i]) - } - var finalize Finalizer - - art, err = eff.getVariant(ctx, &finalize, eff.Variants[i]) - - if err == nil { - if eff.Origin != nil { - art.Artifact().SetAnnotation(annotations.COMPVERS_ANNOTATION, eff.Origin.String()) - } - blob, err = art.Blob() - if err == nil { - finalize.Close(art) - fac = artifactset.ArtifactTransferCreator(art, &finalize, feedback) - } - } - } - i++ - return - }) - if err != nil { - return nil, err - } - return blob, nil -} - -func Provider(opts ...Option) bpi.BlobAccessProvider { - return bpi.BlobAccessProviderFunction(func() (bpi.BlobAccess, error) { - return BlobAccess(opts...) - }) -} diff --git a/pkg/blobaccess/dockermulti/deprecated.go b/pkg/blobaccess/dockermulti/deprecated.go deleted file mode 100644 index 4ef55d48b..000000000 --- a/pkg/blobaccess/dockermulti/deprecated.go +++ /dev/null @@ -1,17 +0,0 @@ -package dockermulti - -import ( - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" -) - -// BlobAccessForMultiImageFromDockerDaemon returns a BlobAccess for the image with the given name. -// Deprecated: use BlobAccess. -func BlobAccessForMultiImageFromDockerDaemon(opts ...Option) (bpi.BlobAccess, error) { - return BlobAccess(opts...) -} - -// BlobAccessProviderForMultiImageFromDockerDaemon returns a BlobAccessProvider for the image with the given name. -// Deprecated: use Provider. -func BlobAccessProviderForMultiImageFromDockerDaemon(opts ...Option) bpi.BlobAccessProvider { - return Provider(opts...) -} diff --git a/pkg/blobaccess/dockermulti/options.go b/pkg/blobaccess/dockermulti/options.go deleted file mode 100644 index 4b93db782..000000000 --- a/pkg/blobaccess/dockermulti/options.go +++ /dev/null @@ -1,105 +0,0 @@ -package dockermulti - -import ( - "github.com/mandelsoft/goutils/optionutils" - "golang.org/x/exp/slices" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/oci" -) - -type Option = optionutils.Option[*Options] - -type Options struct { - Context oci.Context - Version string - Variants []string - Origin *common.NameVersion - Printer common.Printer -} - -func (o *Options) ApplyTo(opts *Options) { - if opts == nil { - return - } - if o.Context != nil { - opts.Context = o.Context - } - if o.Version != "" { - opts.Version = o.Version - } - if o.Variants != nil { - opts.Variants = append(opts.Variants, o.Variants...) - } - if o.Origin != nil { - opts.Origin = o.Origin - } - if o.Printer != nil { - opts.Printer = o.Printer - } -} - -//////////////////////////////////////////////////////////////////////////////// - -type context struct { - oci.Context -} - -func (o context) ApplyTo(opts *Options) { - opts.Context = o -} - -func WithContext(ctx oci.ContextProvider) Option { - return context{ctx.OCIContext()} -} - -//////////////////////////////////////////////////////////////////////////////// - -type version string - -func (o version) ApplyTo(opts *Options) { - opts.Version = string(o) -} - -func WithVersion(v string) Option { - return version(v) -} - -//////////////////////////////////////////////////////////////////////////////// - -type compvers common.NameVersion - -func (o compvers) ApplyTo(opts *Options) { - n := common.NameVersion(o) - opts.Origin = &n -} - -func WithOrigin(o common.NameVersion) Option { - return compvers(o) -} - -//////////////////////////////////////////////////////////////////////////////// - -type variants []string - -func (o variants) ApplyTo(opts *Options) { - opts.Variants = append(opts.Variants, []string(o)...) -} - -func WithVariants(v ...string) Option { - return variants(slices.Clone(v)) -} - -//////////////////////////////////////////////////////////////////////////////// - -type printer struct { - common.Printer -} - -func (o printer) ApplyTo(opts *Options) { - opts.Printer = o -} - -func WithPrinter(p common.Printer) Option { - return printer{p} -} diff --git a/pkg/blobaccess/file/access.go b/pkg/blobaccess/file/access.go deleted file mode 100644 index 7a198da28..000000000 --- a/pkg/blobaccess/file/access.go +++ /dev/null @@ -1,340 +0,0 @@ -package file - -import ( - "io" - "sync" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/optionutils" - "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" - "github.com/open-component-model/ocm/pkg/iotools" - "github.com/open-component-model/ocm/pkg/utils" -) - -type ( - _nopCloser = iotools.NopCloser - _blobAccess = bpi.BlobAccess -) - -//////////////////////////////////////////////////////////////////////////////// - -type fileDataAccess struct { - _nopCloser - fs vfs.FileSystem - path string -} - -var ( - _ bpi.DataSource = (*fileDataAccess)(nil) - _ bpi.Validatable = (*fileDataAccess)(nil) -) - -func DataAccess(fs vfs.FileSystem, path string) bpi.DataAccess { - return &fileDataAccess{fs: fs, path: path} -} - -func (a *fileDataAccess) Get() ([]byte, error) { - data, err := vfs.ReadFile(a.fs, a.path) - if err != nil { - return nil, errors.Wrapf(err, "file %q", a.path) - } - return data, nil -} - -func (a *fileDataAccess) Reader() (io.ReadCloser, error) { - file, err := a.fs.Open(a.path) - if err != nil { - return nil, errors.Wrapf(err, "file %q", a.path) - } - return file, nil -} - -// Validate checks if the access is valid, meaning -// it can provide data. Here, this means -// that the file exists. -func (a *fileDataAccess) Validate() error { - ok, err := vfs.Exists(a.fs, a.path) - if err != nil { - return err - } - if !ok { - return errors.ErrNotFound("file", a.path) - } - return nil -} - -func (a *fileDataAccess) Origin() string { - return a.path -} - -//////////////////////////////////////////////////////////////////////////////// - -type fileBlobAccess struct { - fileDataAccess - mimeType string -} - -var ( - _ bpi.BlobAccess = (*fileBlobAccess)(nil) - _ bpi.FileLocation = (*fileBlobAccess)(nil) -) - -func (f *fileBlobAccess) FileSystem() vfs.FileSystem { - return f.fs -} - -func (f *fileBlobAccess) Path() string { - return f.path -} - -func (f *fileBlobAccess) Dup() (bpi.BlobAccess, error) { - return f, nil -} - -func (f *fileBlobAccess) Size() int64 { - size := bpi.BLOB_UNKNOWN_SIZE - fi, err := f.fs.Stat(f.path) - if err == nil { - size = fi.Size() - } - return size -} - -func (f *fileBlobAccess) MimeType() string { - return f.mimeType -} - -func (f *fileBlobAccess) DigestKnown() bool { - return false -} - -func (f *fileBlobAccess) Digest() digest.Digest { - r, err := f.Reader() - if err != nil { - return "" - } - defer r.Close() - d, err := digest.FromReader(r) - if err != nil { - return "" - } - return d -} - -// BlobAccess wraps a file path into a BlobAccess, which does not need a close. -func BlobAccess(mime string, path string, fss ...vfs.FileSystem) bpi.BlobAccess { - return &fileBlobAccess{ - mimeType: mime, - fileDataAccess: fileDataAccess{fs: utils.FileSystem(fss...), path: path}, - } -} - -func Provider(mime string, path string, fss ...vfs.FileSystem) bpi.BlobAccessProvider { - return bpi.BlobAccessProviderFunction(func() (bpi.BlobAccess, error) { - return BlobAccess(mime, path, fss...), nil - }) -} - -type fileBlobAccessView struct { - _blobAccess - access *fileDataAccess -} - -var ( - _ bpi.BlobAccess = (*fileBlobAccessView)(nil) - _ bpi.FileLocation = (*fileBlobAccessView)(nil) -) - -func (f *fileBlobAccessView) Dup() (bpi.BlobAccess, error) { - b, err := f._blobAccess.Dup() - if err != nil { - return nil, err - } - return &fileBlobAccessView{b, f.access}, nil -} - -func (f *fileBlobAccessView) FileSystem() vfs.FileSystem { - return f.access.fs -} - -func (f *fileBlobAccessView) Path() string { - return f.access.path -} - -func BlobAccessWithCloser(closer io.Closer, mime string, path string, fss ...vfs.FileSystem) bpi.BlobAccess { - fb := &fileBlobAccess{fileDataAccess{fs: utils.FileSystem(fss...), path: path}, mime} - return &fileBlobAccessView{ - bpi.NewBlobAccessForBase(fb, closer), - &fb.fileDataAccess, - } -} - -//////////////////////////////////////////////////////////////////////////////// - -type temporaryFileBlob struct { - _blobAccess - lock sync.Mutex - path string - file vfs.File - filesystem vfs.FileSystem -} - -var ( - _ bpi.BlobAccessBase = (*temporaryFileBlob)(nil) - _ bpi.FileLocation = (*temporaryFileBlob)(nil) -) - -// Validate checks if the access is valid, meaning -// it can provide data. Here, this means -// that the file exists. -func (b *temporaryFileBlob) Validate() error { - b.lock.Lock() - defer b.lock.Unlock() - if b.path == "" { - return bpi.ErrClosed - } - ok, err := vfs.Exists(b.filesystem, b.path) - if err != nil { - return err - } - if !ok { - return errors.ErrNotFound("file", b.path) - } - return nil -} - -func (b *temporaryFileBlob) Close() error { - b.lock.Lock() - defer b.lock.Unlock() - if b.path != "" { - list := errors.ErrListf("temporary blob") - if b.file != nil { - list.Add(b.file.Close()) - } - list.Add(b.filesystem.Remove(b.path)) - b.path = "" - b.file = nil - b._blobAccess = nil - return list.Result() - } - return nil -} - -func (b *temporaryFileBlob) FileSystem() vfs.FileSystem { - return b.filesystem -} - -func (b *temporaryFileBlob) Path() string { - return b.path -} - -func BlobAccessForTemporaryFile(mime string, temp vfs.File, opts ...Option) bpi.BlobAccess { - eff := optionutils.EvalOptions(opts...) - t := &temporaryFileBlob{ - _blobAccess: BlobAccess(mime, temp.Name(), eff.FileSystem), - filesystem: utils.FileSystem(eff.FileSystem), - path: temp.Name(), - file: temp, - } - // TODO: handle FileLocation interface in combination with partially set meta data. - if eff.Digest != "" || eff.GetSize() != bpi.BLOB_UNKNOWN_SIZE { - return bpi.NewBlobAccessForBase(bpi.BaseAccessForDataAccessAndMeta(mime, t, eff.Digest, eff.GetSize())) - } - return bpi.NewBlobAccessForBase(t) -} - -func BlobAccessForTemporaryFilePath(mime string, temp string, opts ...Option) bpi.BlobAccess { - eff := optionutils.EvalOptions(opts...) - return bpi.NewBlobAccessForBase(bpi.BaseAccessForDataAccessAndMeta(mime, &temporaryFileBlob{ - _blobAccess: BlobAccess(mime, temp, eff.FileSystem), - filesystem: utils.FileSystem(eff.FileSystem), - path: temp, - }, eff.Digest, eff.GetSize())) -} - -//////////////////////////////////////////////////////////////////////////////// - -// TempFile holds a temporary file that should be kept open. -// Close should never be called directly. -// It can be passed to another responsibility realm by calling Release- -// For example to be transformed into a TemporaryBlobAccess. -// Close will close and remove an unreleased file and does -// nothing if it has been released. -// If it has been released the new realm is responsible. -// to close and remove it. -type TempFile struct { - lock sync.Mutex - temp vfs.File - filesystem vfs.FileSystem -} - -func NewTempFile(dir string, pattern string, fss ...vfs.FileSystem) (*TempFile, error) { - fs := utils.FileSystem(fss...) - temp, err := vfs.TempFile(fs, dir, pattern) - if err != nil { - return nil, err - } - return &TempFile{ - temp: temp, - filesystem: fs, - }, nil -} - -func (t *TempFile) Name() string { - t.lock.Lock() - defer t.lock.Unlock() - return t.temp.Name() -} - -func (t *TempFile) FileSystem() vfs.FileSystem { - t.lock.Lock() - defer t.lock.Unlock() - return t.filesystem -} - -// Release passes the responsibility for closing and removing -// the temporary file to another realm. After calling this method -// the TempFile object will not handle these operations anymore, if it is closed. -func (t *TempFile) Release() vfs.File { - t.lock.Lock() - defer t.lock.Unlock() - if t.temp != nil { - t.temp.Sync() - } - tmp := t.temp - t.temp = nil - return tmp -} - -func (t *TempFile) Writer() io.Writer { - t.lock.Lock() - defer t.lock.Unlock() - return t.temp -} - -func (t *TempFile) Sync() error { - t.lock.Lock() - defer t.lock.Unlock() - return t.temp.Sync() -} - -func (t *TempFile) AsBlob(mime string) bpi.BlobAccess { - return BlobAccessForTemporaryFile(mime, t.Release(), WithFileSystem(t.filesystem)) -} - -// Close closes and removes the temporary file as long it has not -// been released before by calling Release. -func (t *TempFile) Close() error { - t.lock.Lock() - defer t.lock.Unlock() - if t.temp != nil { - name := t.temp.Name() - t.temp.Close() - t.temp = nil - return t.filesystem.Remove(name) - } - return nil -} diff --git a/pkg/blobaccess/file/options.go b/pkg/blobaccess/file/options.go deleted file mode 100644 index 5a2397654..000000000 --- a/pkg/blobaccess/file/options.go +++ /dev/null @@ -1,76 +0,0 @@ -package file - -import ( - "github.com/mandelsoft/goutils/generics" - "github.com/mandelsoft/goutils/optionutils" - "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" - "github.com/open-component-model/ocm/pkg/utils" -) - -type Option = optionutils.Option[*Options] - -type Options struct { - // FileSystem defines the file system that contains the specified directory. - FileSystem vfs.FileSystem - Digest digest.Digest - Size *int64 -} - -func (o *Options) GetSize() int64 { - if o.Size == nil { - return bpi.BLOB_UNKNOWN_SIZE - } - return *o.Size -} - -func (o *Options) ApplyTo(opts *Options) { - if opts == nil { - return - } - if o.FileSystem != nil { - opts.FileSystem = o.FileSystem - } - if o.Digest != "" { - opts.Digest = o.Digest - } - optionutils.ApplyOption(o.Size, &opts.Size) -} - -//////////////////////////////////////////////////////////////////////////////// - -type fileSystem struct { - fs vfs.FileSystem -} - -func (o *fileSystem) ApplyTo(opts *Options) { - opts.FileSystem = o.fs -} - -func WithFileSystem(fss ...vfs.FileSystem) Option { - return &fileSystem{fs: utils.FileSystem(fss...)} -} - -//////////////////////////////////////////////////////////////////////////////// - -type size int64 - -func (o size) ApplyTo(opts *Options) { - opts.Size = generics.Pointer(int64(o)) -} - -func WithSize(s int64) Option { - return size(s) -} - -type _digest digest.Digest - -func (o _digest) ApplyTo(opts *Options) { - opts.Digest = digest.Digest(o) -} - -func WithDigest(d digest.Digest) Option { - return _digest(d) -} diff --git a/pkg/blobaccess/helm/access.go b/pkg/blobaccess/helm/access.go deleted file mode 100644 index d64e78a16..000000000 --- a/pkg/blobaccess/helm/access.go +++ /dev/null @@ -1,81 +0,0 @@ -package helm - -import ( - "fmt" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/optionutils" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/helm/identity" - ocihelm "github.com/open-component-model/ocm/pkg/contexts/oci/ociutils/helm" - "github.com/open-component-model/ocm/pkg/helm" - "github.com/open-component-model/ocm/pkg/helm/loader" - "github.com/open-component-model/ocm/pkg/utils" -) - -func BlobAccess(path string, opts ...Option) (blob bpi.BlobAccess, name, version string, err error) { - eff := optionutils.EvalOptions(opts...) - ctx := eff.OCIContext() - fs := utils.FileSystem(eff.FileSystem) - printer := eff.Printer - if printer == nil { - printer = common.NewPrinter(nil) - } - - var chartLoader loader.Loader - if eff.HelmRepository == "" { - if ok, err := vfs.Exists(fs, path); !ok || err != nil { - return nil, "", "", errors.NewEf(err, "invalid file path %q", path) - } - chartLoader = loader.VFSLoader(path, fs) - } else { - cert := []byte(eff.CACert) - if eff.CACertFile != "" { - cert, err = vfs.ReadFile(fs, eff.CACertFile) - if err != nil { - return nil, "", "", errors.Wrapf(err, "cannot read root certificates from %q", eff.CACertFile) - } - } - - acc, err := helm.DownloadChart(printer, ctx, path, eff.Version, eff.HelmRepository, - helm.WithCredentials(identity.GetCredentials(ctx, eff.HelmRepository, path)), - helm.WithRootCert(cert)) - if err != nil { - return nil, "", "", errors.Wrapf(err, "cannot download chart %s:%s from %s", path, eff.Version, eff.HelmRepository) - } - chartLoader = loader.AccessLoader(acc) - } - - defer errors.PropagateError(&err, chartLoader.Close) - - chart, err := chartLoader.Chart() - if err != nil { - return nil, "", "", err - } - vers := chart.Metadata.Version - if vers == "" || optionutils.AsValue(eff.OverrideVersion) { - vers = eff.Version - } - if vers == "" { - return nil, "", "", fmt.Errorf("no version found or specified") - } - - blob, err = chartLoader.ChartArtefactSet() - if err == nil && blob == nil { - blob, err = ocihelm.SynthesizeArtifactBlob(chartLoader) - if err != nil { - return nil, "", "", errors.Wrapf(err, "cannot synthesize artifact blob") - } - } - return blob, chart.Name(), vers, err -} - -func Provider(name string, opts ...Option) bpi.BlobAccessProvider { - return bpi.BlobAccessProviderFunction(func() (bpi.BlobAccess, error) { - b, _, _, err := BlobAccess(name, opts...) - return b, err - }) -} diff --git a/pkg/blobaccess/helm/deprecated.go b/pkg/blobaccess/helm/deprecated.go deleted file mode 100644 index f731f80c5..000000000 --- a/pkg/blobaccess/helm/deprecated.go +++ /dev/null @@ -1,17 +0,0 @@ -package helm - -import ( - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" -) - -// BlobAccessForHelmChart returns a BlobAccess for the Helm chart with the given path. -// Deprecated: use BlobAccess. -func BlobAccessForHelmChart(path string, opts ...Option) (blob bpi.BlobAccess, name, version string, err error) { - return BlobAccess(path, opts...) -} - -// BlobAccessProviderForHelmChart returns a BlobAccessProvider for the Helm chart with the given name. -// Deprecated: use Provider. -func BlobAccessProviderForHelmChart(name string, opts ...Option) bpi.BlobAccessProvider { - return Provider(name, opts...) -} diff --git a/pkg/blobaccess/helm/options.go b/pkg/blobaccess/helm/options.go deleted file mode 100644 index e75247140..000000000 --- a/pkg/blobaccess/helm/options.go +++ /dev/null @@ -1,171 +0,0 @@ -package helm - -import ( - "github.com/mandelsoft/goutils/optionutils" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/utils" -) - -type Option = optionutils.Option[*Options] - -type Options struct { - Context oci.Context - FileSystem vfs.FileSystem - Version string - OverrideVersion *bool - HelmRepository string - CACert string - CACertFile string - - Printer common.Printer -} - -func (o *Options) ApplyTo(opts *Options) { - if opts == nil { - return - } - if o.Context != nil { - opts.Context = o.Context - } - if o.FileSystem != nil { - opts.FileSystem = o.FileSystem - } - if o.Version != "" { - opts.Version = o.Version - } - if o.OverrideVersion != nil { - opts.OverrideVersion = o.OverrideVersion - } - if o.HelmRepository != "" { - opts.HelmRepository = o.HelmRepository - } - if o.CACert != "" { - opts.CACert = o.CACert - } - if o.CACertFile != "" { - opts.CACertFile = o.CACertFile - } - if o.Printer != nil { - opts.Printer = o.Printer - } -} - -func (o *Options) OCIContext() oci.Context { - if o.Context == nil { - return oci.DefaultContext() - } - return o.Context -} - -//////////////////////////////////////////////////////////////////////////////// - -type context struct { - oci.Context -} - -func (o context) ApplyTo(opts *Options) { - opts.Context = o -} - -func WithContext(ctx oci.ContextProvider) Option { - return context{ctx.OCIContext()} -} - -//////////////////////////////////////////////////////////////////////////////// - -type fileSystem struct { - fs vfs.FileSystem -} - -func (o *fileSystem) ApplyTo(opts *Options) { - opts.FileSystem = o.fs -} - -func WithFileSystem(fs vfs.FileSystem) Option { - return &fileSystem{fs: fs} -} - -//////////////////////////////////////////////////////////////////////////////// - -type version string - -func (o version) ApplyTo(opts *Options) { - opts.Version = string(o) -} - -func WithVersion(v string) Option { - return version(v) -} - -//////////////////////////////////////////////////////////////////////////////// - -type override struct { - flag bool - version string -} - -func (o *override) ApplyTo(opts *Options) { - opts.OverrideVersion = utils.BoolP(o.flag) - opts.Version = o.version -} - -func WithVersionOverride(v string, flag ...bool) Option { - return &override{ - version: v, - flag: utils.OptionalDefaultedBool(true, flag...), - } -} - -//////////////////////////////////////////////////////////////////////////////// - -type helmrepo string - -func (o helmrepo) ApplyTo(opts *Options) { - opts.HelmRepository = string(o) -} - -// WithHelmRepository defines the helm repository to read from. -func WithHelmRepository(v string) Option { - return helmrepo(v) -} - -//////////////////////////////////////////////////////////////////////////////// - -type cacert string - -func (o cacert) ApplyTo(opts *Options) { - opts.CACert = string(o) -} - -func WithCACert(v string) Option { - return cacert(v) -} - -//////////////////////////////////////////////////////////////////////////////// - -type cacertfile string - -func (o cacertfile) ApplyTo(opts *Options) { - opts.CACertFile = string(o) -} - -func WithCACertFile(v string) Option { - return cacertfile(v) -} - -//////////////////////////////////////////////////////////////////////////////// - -type printer struct { - common.Printer -} - -func (o printer) ApplyTo(opts *Options) { - opts.Printer = o -} - -func WithPrinter(p common.Printer) Option { - return printer{p} -} diff --git a/pkg/blobaccess/interface.go b/pkg/blobaccess/interface.go deleted file mode 100644 index ea6084f76..000000000 --- a/pkg/blobaccess/interface.go +++ /dev/null @@ -1,30 +0,0 @@ -package blobaccess - -import ( - "github.com/open-component-model/ocm/pkg/blobaccess/internal" -) - -const ( - KIND_BLOB = internal.KIND_BLOB - KIND_MEDIATYPE = internal.KIND_MEDIATYPE - - BLOB_UNKNOWN_SIZE = internal.BLOB_UNKNOWN_SIZE - BLOB_UNKNOWN_DIGEST = internal.BLOB_UNKNOWN_DIGEST -) - -type ( - DataAccess = internal.DataAccess - DataReader = internal.DataReader - DataGetter = internal.DataGetter -) - -type ( - BlobAccess = internal.BlobAccess - BlobAccessProvider = internal.BlobAccessProvider - - DataSource = internal.DataSource - DigestSource = internal.DigestSource - MimeType = internal.MimeType -) - -type FileLocation = internal.FileLocation diff --git a/pkg/blobaccess/maven/access.go b/pkg/blobaccess/maven/access.go deleted file mode 100644 index d66e26ad9..000000000 --- a/pkg/blobaccess/maven/access.go +++ /dev/null @@ -1,37 +0,0 @@ -package maven - -import ( - "github.com/mandelsoft/goutils/optionutils" - - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" - "github.com/open-component-model/ocm/pkg/maven" -) - -func DataAccess(repo *maven.Repository, groupId, artifactId, version string, opts ...Option) (bpi.DataAccess, error) { - return BlobAccess(repo, groupId, artifactId, version, opts...) -} - -func BlobAccess(repo *maven.Repository, groupId, artifactId, version string, opts ...Option) (bpi.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 BlobAccessForCoords(repo *maven.Repository, coords *maven.Coordinates, opts ...Option) (bpi.BlobAccess, error) { - return BlobAccess(repo, coords.GroupId, coords.ArtifactId, coords.Version, optionutils.WithDefaults(opts, WithOptionalClassifier(coords.Classifier), WithOptionalExtension(coords.Extension))...) -} - -func Provider(repo *maven.Repository, groupId, artifactId, version string, opts ...Option) bpi.BlobAccessProvider { - return bpi.BlobAccessProviderFunction(func() (bpi.BlobAccess, error) { - b, err := BlobAccess(repo, groupId, artifactId, version, opts...) - return b, err - }) -} - -func ProviderCoords(repo *maven.Repository, coords *maven.Coordinates, opts ...Option) bpi.BlobAccessProvider { - return Provider(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 deleted file mode 100644 index 3b10c204b..000000000 --- a/pkg/blobaccess/maven/access_test.go +++ /dev/null @@ -1,128 +0,0 @@ -package maven_test - -import ( - "time" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/env/builder" - - 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/maven/maventest" - "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.BlobAccess(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.BlobAccessForCoords(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.BlobAccessForCoords(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.BlobAccessForCoords(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.BlobAccessForCoords(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.BlobAccessForCoords(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.BlobAccessForCoords(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/deprecated.go b/pkg/blobaccess/maven/deprecated.go deleted file mode 100644 index fc48da8f3..000000000 --- a/pkg/blobaccess/maven/deprecated.go +++ /dev/null @@ -1,36 +0,0 @@ -package maven - -import ( - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" - "github.com/open-component-model/ocm/pkg/maven" -) - -// DataAccessForMaven returns a DataAccess for the Maven artifact with the given coordinates. -// Deprecated: use DataAccess. -func DataAccessForMaven(repo *maven.Repository, groupId, artifactId, version string, opts ...Option) (bpi.DataAccess, error) { - return DataAccess(repo, groupId, artifactId, version, opts...) -} - -// BlobAccessForMaven returns a BlobAccess for the Maven artifact with the given coordinates. -// Deprecated: use BlobAccess. -func BlobAccessForMaven(repo *maven.Repository, groupId, artifactId, version string, opts ...Option) (bpi.BlobAccess, error) { - return BlobAccess(repo, groupId, artifactId, version, opts...) -} - -// BlobAccessForMavenCoords returns a BlobAccessProvider for the Maven artifact with the given coordinates. -// Deprecated: use BlobAccessForCoords. -func BlobAccessForMavenCoords(repo *maven.Repository, coords *maven.Coordinates, opts ...Option) (bpi.BlobAccess, error) { - return BlobAccessForCoords(repo, coords, opts...) -} - -// BlobAccessProviderForMaven returns a BlobAccessProvider for the Maven artifact with the given coordinates. -// Deprecated: use Provider. -func BlobAccessProviderForMaven(repo *maven.Repository, groupId, artifactId, version string, opts ...Option) bpi.BlobAccessProvider { - return Provider(repo, groupId, artifactId, version, opts...) -} - -// BlobAccessProviderForMavenCoords returns a BlobAccessProvider for the Maven artifact with the given coordinates. -// Deprecated: use ProviderCoords. -func BlobAccessProviderForMavenCoords(repo *maven.Repository, coords *maven.Coordinates, opts ...Option) bpi.BlobAccessProvider { - return ProviderCoords(repo, coords, opts...) -} diff --git a/pkg/blobaccess/maven/options.go b/pkg/blobaccess/maven/options.go deleted file mode 100644 index 679ad81a2..000000000 --- a/pkg/blobaccess/maven/options.go +++ /dev/null @@ -1,202 +0,0 @@ -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 - - maven.FileCoordinates -} - -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 &optionutils.NoOption[*Options]{} -} - -//////////////////////////////////////////////////////////////////////////////// - -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 &optionutils.NoOption[*Options]{} -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/pkg/blobaccess/maven/utils.go b/pkg/blobaccess/maven/utils.go deleted file mode 100644 index 6c2fdc6ac..000000000 --- a/pkg/blobaccess/maven/utils.go +++ /dev/null @@ -1,168 +0,0 @@ -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/blobaccess" - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" - "github.com/open-component-model/ocm/pkg/blobaccess/file" - "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 file.BlobAccessForTemporaryFilePath(mime.MIME_TGZ, tgz.Name(), file.WithFileSystem(fs), file.WithDigest(dw.Digest()), file.WithSize(dw.Size())), 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/ociartifact/access.go b/pkg/blobaccess/ociartifact/access.go deleted file mode 100644 index f224e0f08..000000000 --- a/pkg/blobaccess/ociartifact/access.go +++ /dev/null @@ -1,55 +0,0 @@ -package ociartifact - -import ( - "fmt" - - "github.com/mandelsoft/goutils/optionutils" - - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" -) - -func BlobAccess(refname string, opts ...Option) (bpi.BlobAccess, string, error) { - eff := optionutils.EvalOptions(opts...) - - eff.Printf("image %s\n", refname) - ref, err := oci.ParseRef(refname) - if err != nil { - return nil, "", err - } - - spec, err := eff.OCIContext().MapUniformRepositorySpec(&ref.UniformRepositorySpec) - if err != nil { - return nil, "", err - } - - repo, err := eff.OCIContext().RepositoryForSpec(spec) - if err != nil { - return nil, "", err - } - ns, err := repo.LookupNamespace(ref.Repository) - if err != nil { - return nil, "", err - } - - version := ref.Version() - if version == "" || version == "latest" { - version = eff.Version - } - if version == "" { - return nil, "", fmt.Errorf("no version specified") - } - blob, err := artifactset.SynthesizeArtifactBlobWithFilter(ns, version, eff.Filter) - if err != nil { - return nil, "", err - } - return blob, version, nil -} - -func Provider(name string, opts ...Option) bpi.BlobAccessProvider { - return bpi.BlobAccessProviderFunction(func() (bpi.BlobAccess, error) { - b, _, err := BlobAccess(name, opts...) - return b, err - }) -} diff --git a/pkg/blobaccess/ociartifact/deprecated.go b/pkg/blobaccess/ociartifact/deprecated.go deleted file mode 100644 index 7531805de..000000000 --- a/pkg/blobaccess/ociartifact/deprecated.go +++ /dev/null @@ -1,17 +0,0 @@ -package ociartifact - -import ( - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" -) - -// BlobAccessForOCIArtifact returns a BlobAccess for the OCI artifact with the given refname. -// Deprecated: use BlobAccess. -func BlobAccessForOCIArtifact(refname string, opts ...Option) (bpi.BlobAccess, string, error) { - return BlobAccess(refname, opts...) -} - -// BlobAccessProviderForOCIArtifact returns a BlobAccessProvider for the OCI artifact with the given name. -// Deprecated: use Provider. -func BlobAccessProviderForOCIArtifact(name string, opts ...Option) bpi.BlobAccessProvider { - return Provider(name, opts...) -} diff --git a/pkg/blobaccess/ociartifact/options.go b/pkg/blobaccess/ociartifact/options.go deleted file mode 100644 index 6e3469306..000000000 --- a/pkg/blobaccess/ociartifact/options.go +++ /dev/null @@ -1,112 +0,0 @@ -package ociartifact - -import ( - "github.com/mandelsoft/goutils/optionutils" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/transfer/filters" -) - -type Option = optionutils.Option[*Options] - -type Filter = filters.Filter - -type Options struct { - Context oci.Context - Version string - Filter Filter - Printer common.Printer -} - -func (o *Options) OCIContext() oci.Context { - if o.Context == nil { - return oci.DefaultContext() - } - return o.Context -} - -func (o *Options) GetPrinter() common.Printer { - if o.Printer == nil { - return common.NewPrinter(nil) - } - return o.Printer -} - -func (o *Options) Printf(msg string, args ...interface{}) { - if o.Printer != nil { - o.Printer.Printf(msg, args...) - } -} - -func (o *Options) ApplyTo(opts *Options) { - if opts == nil { - return - } - if o.Context != nil { - opts.Context = o.Context - } - if o.Version != "" { - opts.Version = o.Version - } - if o.Printer != nil { - opts.Printer = o.Printer - } - if o.Filter != nil { - opts.Filter = o.Filter - } -} - -//////////////////////////////////////////////////////////////////////////////// - -type context struct { - oci.Context -} - -func (o context) ApplyTo(opts *Options) { - opts.Context = o -} - -func WithContext(ctx oci.ContextProvider) Option { - return context{ctx.OCIContext()} -} - -//////////////////////////////////////////////////////////////////////////////// - -type version string - -func (o version) ApplyTo(opts *Options) { - opts.Version = string(o) -} - -func WithVersion(v string) Option { - return version(v) -} - -//////////////////////////////////////////////////////////////////////////////// - -type printer struct { - common.Printer -} - -func (o printer) ApplyTo(opts *Options) { - opts.Printer = o -} - -func WithPrinter(p common.Printer) Option { - return printer{p} -} - -//////////////////////////////////////////////////////////////////////////////// - -type _filter struct { - filters.Filter -} - -func (o _filter) ApplyTo(opts *Options) { - opts.Filter = o.Filter -} - -func WithFilter(f filters.Filter) Option { - return _filter{f} -} diff --git a/pkg/blobaccess/wget/access.go b/pkg/blobaccess/wget/access.go deleted file mode 100644 index b982b97e4..000000000 --- a/pkg/blobaccess/wget/access.go +++ /dev/null @@ -1,181 +0,0 @@ -package wget - -import ( - gocontext "context" - "crypto/tls" - "encoding/base64" - "io" - "mime" - "net/http" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/optionutils" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" - "github.com/open-component-model/ocm/pkg/blobaccess/file" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/wget/identity" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - ocmmime "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/utils" -) - -const ( - CACHE_CONTENT_THRESHOLD = 4096 -) - -func DataAccess(url string, opts ...Option) (bpi.DataAccess, error) { - blobAccess, err := BlobAccess(url, opts...) - if err != nil { - return nil, err - } - return blobAccess, nil -} - -func BlobAccess(url string, opts ...Option) (_ bpi.BlobAccess, rerr error) { - eff := optionutils.EvalOptions(opts...) - log := eff.Logger("URL", url) - - creds, err := eff.GetCredentials(url) - if err != nil { - return nil, err - } - if creds == nil { - log.Debug("no credentials found for url {{url}}", "url", url) - } - - // configure http client - rootCAs, err := credentials.GetRootCAs(eff.CredentialContext, creds) - if rerr != nil { - return nil, err - } - clientCerts, err := credentials.GetClientCerts(eff.CredentialContext, creds) - if err != nil { - return nil, errors.New("client certificate and private key provided in credentials could not be loaded " + - "as tls certificate") - } - - transport := &http.Transport{ - TLSClientConfig: &tls.Config{ - MinVersion: tls.VersionTLS13, - RootCAs: rootCAs, - Certificates: clientCerts, - }, - } - - var redirectFunc func(req *http.Request, via []*http.Request) error = nil - if eff.NoRedirect != nil && *eff.NoRedirect { - redirectFunc = func(req *http.Request, via []*http.Request) error { - return http.ErrUseLastResponse - } - } - - client := &http.Client{ - CheckRedirect: redirectFunc, - Transport: transport, - } - - if eff.Verb == "" { - eff.Verb = http.MethodGet - } - - // configure http request - request, err := http.NewRequestWithContext(gocontext.Background(), eff.Verb, url, eff.Body) - if err != nil { - return nil, err - } - - if eff.Header != nil { - for key, arr := range eff.Header { - for _, el := range arr { - request.Header.Add(key, el) - } - } - } - - if creds != nil { - user := creds.GetProperty(identity.ATTR_USERNAME) - password := creds.GetProperty(identity.ATTR_PASSWORD) - token := creds.GetProperty(identity.ATTR_IDENTITY_TOKEN) - - if user != "" && password != "" { - auth := user + ":" + password - auth = base64.StdEncoding.EncodeToString([]byte(auth)) - request.Header.Add("Authorization", "Basic "+auth) - } else if token != "" { - request.Header.Add("Authorization", "Bearer "+token) - } - } - - // make http request - resp, err := client.Do(request) - if err != nil { - return nil, err - } - defer errors.PropagateError(&rerr, resp.Body.Close) - log.Debug("http status code {{code}}", "code", resp.StatusCode) - - // determine effective mime type - if eff.MimeType == "" { - log.Debug("no mime type provided as option, trying to extract mime type from content type " + - "response header") - - contentType := resp.Header.Get("Content-Type") - eff.MimeType, _, err = mime.ParseMediaType(contentType) - if err != nil { - log.Debug("failed to get mime type from content type response header with error {{err}}", - "err", err) - } - } - if eff.MimeType == "" { - log.Debug("no mime type was provided as content type header of the http response, trying to" + - "extract mime type from url") - ext, err := utils.GetFileExtensionFromUrl(url) - if err == nil && ext != "" { - eff.MimeType = mime.TypeByExtension(ext) - } else if err != nil { - log.Debug(err.Error()) - } - } - if eff.MimeType == "" { - eff.MimeType = ocmmime.MIME_OCTET - log.Debug("no mime type could be extract from the url, defaulting to {{default}}", "default", - eff.MimeType) - } - - // download content - var blob cpi.BlobAccess - if resp.ContentLength < 0 || resp.ContentLength > CACHE_CONTENT_THRESHOLD { - log.Debug("download to file because content length is unknown or greater than {{threshold}}", "threshold", CACHE_CONTENT_THRESHOLD) - f, err := file.NewTempFile("", "wget") - if err != nil { - return nil, err - } - defer errors.PropagateError(&rerr, f.Close) - - n, err := io.Copy(f.Writer(), resp.Body) - if err != nil { - return nil, err - } - log.Debug("downloaded size {{size}} to {{filepath}}", "size", n, "filepath", f.Name()) - - blob = f.AsBlob(eff.MimeType) - } else { - log.Debug("download to memory because content length is less than {{threshold}}", "threshold", CACHE_CONTENT_THRESHOLD) - buf, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - blob = blobaccess.ForData(eff.MimeType, buf) - } - - return blob, nil -} - -func Provider(url string, opts ...Option) bpi.BlobAccessProvider { - return bpi.BlobAccessProviderFunction(func() (bpi.BlobAccess, error) { - b, err := BlobAccess(url, opts...) - return b, err - }) -} diff --git a/pkg/blobaccess/wget/deprecated.go b/pkg/blobaccess/wget/deprecated.go deleted file mode 100644 index 793eb85ef..000000000 --- a/pkg/blobaccess/wget/deprecated.go +++ /dev/null @@ -1,23 +0,0 @@ -package wget - -import ( - "github.com/open-component-model/ocm/pkg/blobaccess/bpi" -) - -// DataAccessForWget returns a DataAccess for the given URL. -// Deprecated: use DataAccess. -func DataAccessForWget(url string, opts ...Option) (bpi.DataAccess, error) { - return DataAccess(url, opts...) -} - -// BlobAccessForWget returns a BlobAccess for the given URL. -// Deprecated: use BlobAccess. -func BlobAccessForWget(url string, opts ...Option) (_ bpi.BlobAccess, rerr error) { - return BlobAccess(url, opts...) -} - -// BlobAccessProviderForWget returns a BlobAccessProvider for the given URL. -// Deprecated: use Provider. -func BlobAccessProviderForWget(url string, opts ...Option) bpi.BlobAccessProvider { - return Provider(url, opts...) -} diff --git a/pkg/blobaccess/wget/logging.go b/pkg/blobaccess/wget/logging.go deleted file mode 100644 index 0814553e5..000000000 --- a/pkg/blobaccess/wget/logging.go +++ /dev/null @@ -1,7 +0,0 @@ -package wget - -import ( - ocmlog "github.com/open-component-model/ocm/pkg/logging" -) - -var REALM = ocmlog.DefineSubRealm("blob access for wget", "blobaccess/wget") diff --git a/pkg/blobaccess/wget/options.go b/pkg/blobaccess/wget/options.go deleted file mode 100644 index a808e4545..000000000 --- a/pkg/blobaccess/wget/options.go +++ /dev/null @@ -1,188 +0,0 @@ -package wget - -import ( - "io" - "net/http" - - "github.com/mandelsoft/goutils/optionutils" - "github.com/mandelsoft/logging" - - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/wget/identity" - ocmlog "github.com/open-component-model/ocm/pkg/logging" - "github.com/open-component-model/ocm/pkg/utils" -) - -type Option = optionutils.Option[*Options] - -type Options struct { - CredentialContext credentials.Context - LoggingContext logging.Context - // Header to be passed in the http request - Header http.Header - // Verb is the http verb to be used for the request - Verb string - // Body is the body to be included in the http request - Body io.Reader - // NoRedirect allows to disable redirects - NoRedirect *bool - // MimeType defines the media type of the downloaded content - MimeType string - // Credentials allows to pass credentials and certificates for the http communication - Credentials credentials.Credentials -} - -func (o *Options) Logger(keyValuePairs ...interface{}) logging.Logger { - return ocmlog.LogContext(o.LoggingContext, o.CredentialContext).Logger(REALM).WithValues(keyValuePairs...) -} - -func (o *Options) GetCredentials(url string) (credentials.Credentials, error) { - switch { - case o.Credentials != nil: - return o.Credentials, nil - case o.CredentialContext != nil: - creds, err := credentials.CredentialsForConsumer(o.CredentialContext, identity.GetConsumerId(url), identity.IdentityMatcher) - if err != nil { - return nil, err - } - return creds, nil - default: - return nil, nil - } -} - -func (o *Options) ApplyTo(opts *Options) { - if opts == nil { - return - } - if o.MimeType != "" { - opts.MimeType = o.MimeType - } - if o.CredentialContext != nil { - opts.CredentialContext = o.CredentialContext - } - if o.LoggingContext != nil { - opts.LoggingContext = o.LoggingContext - } - if o.Credentials != nil { - opts.Credentials = o.Credentials - } - if o.Header != nil { - opts.Header = o.Header - } - if o.Verb != "" { - opts.Verb = o.Verb - } - if o.Body != nil { - opts.Body = o.Body - } - if o.NoRedirect != nil { - opts.NoRedirect = o.NoRedirect - } -} - -//////////////////////////////////////////////////////////////////////////////// - -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 mimeType string - -func (o mimeType) ApplyTo(opts *Options) { - opts.MimeType = string(o) -} - -func WithMimeType(mime string) Option { - return mimeType(mime) -} - -//////////////////////////////////////////////////////////////////////////////// - -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 header http.Header - -func (o header) ApplyTo(opts *Options) { - opts.Header = http.Header(o) -} - -func WithHeader(h http.Header) Option { - return header(h) -} - -//////////////////////////////////////////////////////////////////////////////// - -type verb string - -func (o verb) ApplyTo(opts *Options) { - opts.Verb = string(o) -} - -func WithVerb(v string) Option { - return verb(v) -} - -//////////////////////////////////////////////////////////////////////////////// - -type body struct { - io.Reader -} - -func (o *body) ApplyTo(opts *Options) { - if o.Reader != nil { - opts.Body = io.Reader(o) - } -} - -func WithBody(v io.Reader) Option { - return &body{v} -} - -//////////////////////////////////////////////////////////////////////////////// - -type noredirect bool - -func (o noredirect) ApplyTo(opts *Options) { - opts.NoRedirect = utils.BoolP(o) -} - -func WithNoRedirect(r ...bool) Option { - return noredirect(utils.OptionalDefaultedBool(true, r...)) -} diff --git a/pkg/clisupport/labels.go b/pkg/clisupport/labels.go deleted file mode 100644 index 574bbd1e9..000000000 --- a/pkg/clisupport/labels.go +++ /dev/null @@ -1,94 +0,0 @@ -package clisupport - -import ( - "encoding/json" - "strings" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/vfs/pkg/osfs" - "github.com/mandelsoft/vfs/pkg/vfs" - "sigs.k8s.io/yaml" - - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/utils" -) - -func gkind(kind ...string) string { - for _, k := range kind { - if k != "" { - return k - } - } - return "label" -} - -func ParseLabel(fs vfs.FileSystem, a string, kind ...string) (*metav1.Label, error) { - var err error - - if fs == nil { - fs = osfs.New() - } - i := strings.Index(a, "=") - if i < 0 { - return nil, errors.ErrInvalid(gkind(kind...), a) - } - label := a[:i] - - data, err := utils.ResolveData(a[i+1:], fs) - if err != nil { - return nil, err - } - - var value interface{} - err = yaml.Unmarshal(data, &value) - if err != nil { - return nil, errors.Wrapf(errors.ErrInvalid(gkind(kind...), a), "no yaml or json") - } - data, err = json.Marshal(value) - if err != nil { - return nil, errors.Wrapf(errors.ErrInvalid(gkind(kind...), a), err.Error()) - } - return &metav1.Label{ - Name: strings.TrimSpace(label), - Value: json.RawMessage(data), - }, nil -} - -func AddParsedLabel(fs vfs.FileSystem, labels metav1.Labels, a string, kind ...string) (metav1.Labels, error) { - l, err := ParseLabel(fs, a, kind...) - if err != nil { - return nil, err - } - for _, c := range labels { - if c.Name == l.Name { - return nil, errors.Newf("duplicate %s %q", gkind(kind...), l.Name) - } - } - return append(labels, *l), nil -} - -func ParseLabels(fs vfs.FileSystem, labels []string, kind ...string) (metav1.Labels, error) { - var err error - result := metav1.Labels{} - for _, l := range labels { - result, err = AddParsedLabel(fs, result, l, kind...) - if err != nil { - return nil, err - } - } - return result, err -} - -func SetParsedLabel(fs vfs.FileSystem, labels metav1.Labels, a string, kind ...string) (metav1.Labels, error) { - l, err := ParseLabel(fs, a, kind...) - if err != nil { - return nil, err - } - for i, c := range labels { - if c.Name == l.Name { - labels[i].Value = l.Value - return labels, nil - } - } - return append(labels, *l), nil -} diff --git a/pkg/cobrautils/flagsets/flagsetscheme/scheme.go b/pkg/cobrautils/flagsets/flagsetscheme/scheme.go deleted file mode 100644 index aafc90fd6..000000000 --- a/pkg/cobrautils/flagsets/flagsetscheme/scheme.go +++ /dev/null @@ -1,129 +0,0 @@ -package flagsetscheme - -import ( - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/logging" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/runtime/descriptivetype" - "github.com/open-component-model/ocm/pkg/utils" -) - -// VersionTypedObjectType is the appropriately extended type interface -// based on runtime.VersionTypedObjectType. -type VersionTypedObjectType[T runtime.VersionedTypedObject] interface { - descriptivetype.TypedObjectType[T] - - ConfigOptionTypeSetHandler() flagsets.ConfigOptionTypeSetHandler -} - -//////////////////////////////////////////////////////////////////////////////// - -// ExtendedTypeScheme is the appropriately extended scheme interface based on -// runtime.TypeScheme supporting an extended config provider interface. -type ExtendedTypeScheme[T runtime.VersionedTypedObject, R VersionTypedObjectType[T], P flagsets.ConfigTypeOptionSetConfigProvider] interface { - descriptivetype.TypeScheme[T, R] - - CreateConfigTypeSetConfigProvider() P - - Unwrap() TypeScheme[T, R] -} - -type _TypeScheme[T runtime.VersionedTypedObject, R VersionTypedObjectType[T]] interface { - TypeScheme[T, R] -} - -type typeSchemeWrapper[T runtime.VersionedTypedObject, R VersionTypedObjectType[T], P flagsets.ConfigTypeOptionSetConfigProvider] struct { - _TypeScheme[T, R] -} - -func (s *typeSchemeWrapper[T, R, P]) CreateConfigTypeSetConfigProvider() P { - return s._TypeScheme.CreateConfigTypeSetConfigProvider().(P) -} - -func (s *typeSchemeWrapper[T, R, P]) Unwrap() TypeScheme[T, R] { - return s._TypeScheme -} - -// NewTypeSchemeWrapper wraps a [TypeScheme] into a scheme returning a specialized config provider -// by casting the result. The type scheme constructor provides different implementations based on its -// arguments. This method here can be used to provide a type scheme returning the correct type. -func NewTypeSchemeWrapper[T runtime.VersionedTypedObject, R VersionTypedObjectType[T], P flagsets.ConfigTypeOptionSetConfigProvider](s TypeScheme[T, R]) ExtendedTypeScheme[T, R, P] { - return &typeSchemeWrapper[T, R, P]{s} -} - -// TypeScheme is the appropriately extended scheme interface based on -// runtime.TypeScheme. -type TypeScheme[T runtime.VersionedTypedObject, R VersionTypedObjectType[T]] interface { - ExtendedTypeScheme[T, R, flagsets.ConfigTypeOptionSetConfigProvider] -} - -type _typeScheme[T runtime.VersionedTypedObject, R VersionTypedObjectType[T]] interface { - descriptivetype.TypeScheme[T, R] -} - -type typeScheme[T runtime.VersionedTypedObject, R VersionTypedObjectType[T], S TypeScheme[T, R]] struct { - cfgname string - description string - group string - typeOption string - _typeScheme[T, R] -} - -func flagExtender[T runtime.VersionedTypedObject, R VersionTypedObjectType[T]](ty R) string { - if h := ty.ConfigOptionTypeSetHandler(); h != nil { - return utils.IndentLines(flagsets.FormatConfigOptions(h), " ") - } - return "" -} - -// NewTypeScheme provides an TypeScheme implementation based on the interfaces -// and the default runtime.TypeScheme implementation. -func NewTypeScheme[T runtime.VersionedTypedObject, R VersionTypedObjectType[T], S TypeScheme[T, R]](kindname string, cfgname, typeOption, desc, group string, unknown runtime.Unstructured, acceptUnknown bool, base ...S) TypeScheme[T, R] { - scheme := descriptivetype.NewTypeScheme[T, R](kindname, flagExtender[T, R], unknown, acceptUnknown, utils.Optional(base...)) - return &typeScheme[T, R, S]{ - cfgname: cfgname, - description: desc, - group: group, - typeOption: typeOption, - _typeScheme: scheme, - } -} - -func (s *typeScheme[T, R, S]) Unwrap() TypeScheme[T, R] { - return s -} - -func (t *typeScheme[T, R, S]) CreateConfigTypeSetConfigProvider() flagsets.ConfigTypeOptionSetConfigProvider { - var prov flagsets.ConfigTypeOptionSetConfigProvider - if t.typeOption == "" { - prov = flagsets.NewExplicitlyTypedConfigProvider(t.cfgname, t.description, true) - } else { - prov = flagsets.NewTypedConfigProvider(t.cfgname, t.description, t.typeOption, true) - } - if t.group != "" { - prov.AddGroups(t.group) - } - for _, p := range t.KnownTypes() { - err := prov.AddTypeSet(p.ConfigOptionTypeSetHandler()) - if err != nil { - logging.Logger().LogError(err, "cannot compose type CLI options", "type", t.cfgname) - } - } - if t.BaseScheme() != nil { - base := t.BaseScheme() - for _, s := range base.(S).CreateConfigTypeSetConfigProvider().OptionTypeSets() { - if prov.GetTypeSet(s.GetName()) == nil { - err := prov.AddTypeSet(s) - if err != nil { - logging.Logger().LogError(err, "cannot compose type CLI options", "type", t.cfgname) - } - } - } - } - - return prov -} - -func (t *typeScheme[T, R, S]) KnownTypes() runtime.KnownTypes[T, R] { - return t._typeScheme.KnownTypes() // Goland -} diff --git a/pkg/cobrautils/flagsets/flagsetscheme/types.go b/pkg/cobrautils/flagsets/flagsetscheme/types.go deleted file mode 100644 index 2e9c3483d..000000000 --- a/pkg/cobrautils/flagsets/flagsetscheme/types.go +++ /dev/null @@ -1,62 +0,0 @@ -package flagsetscheme - -import ( - "github.com/mandelsoft/goutils/optionutils" - - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/runtime/descriptivetype" -) - -type additionalTypeInfo interface { - ConfigOptionTypeSetHandler() flagsets.ConfigOptionTypeSetHandler - Description() string - Format() string -} - -type TypedObjectTypeObject[E runtime.VersionedTypedObject] struct { - *descriptivetype.TypedObjectTypeObject[E] - handler flagsets.ConfigOptionTypeSetHandler - validator func(E) error -} - -var _ additionalTypeInfo = (*TypedObjectTypeObject[runtime.VersionedTypedObject])(nil) - -func NewTypedObjectTypeObject[E runtime.VersionedTypedObject](vt runtime.VersionedTypedObjectType[E], opts ...TypeOption) *TypedObjectTypeObject[E] { - t := NewTypeObjectTarget[E](&TypedObjectTypeObject[E]{ - TypedObjectTypeObject: descriptivetype.NewTypedObjectTypeObject[E](vt), - }) - optionutils.ApplyOptions[OptionTarget](t, opts...) - return t.target -} - -func (t *TypedObjectTypeObject[E]) ConfigOptionTypeSetHandler() flagsets.ConfigOptionTypeSetHandler { - return t.handler -} - -func (t *TypedObjectTypeObject[E]) Validate(e E) error { - if t.validator == nil { - return nil - } - return t.validator(e) -} - -//////////////////////////////////////////////////////////////////////////////// - -// TypeObjectTarget is used as target for option functions, it provides -// setters for fields, which should nor be modifiable for a final type object. -type TypeObjectTarget[E runtime.VersionedTypedObject] struct { - *descriptivetype.TypeObjectTarget[E] - target *TypedObjectTypeObject[E] -} - -func NewTypeObjectTarget[E runtime.VersionedTypedObject](target *TypedObjectTypeObject[E]) *TypeObjectTarget[E] { - return &TypeObjectTarget[E]{ - target: target, - TypeObjectTarget: descriptivetype.NewTypeObjectTarget[E](target.TypedObjectTypeObject), - } -} - -func (t TypeObjectTarget[E]) SetConfigHandler(value flagsets.ConfigOptionTypeSetHandler) { - t.target.handler = value -} diff --git a/pkg/cobrautils/flagsets/provider.go b/pkg/cobrautils/flagsets/provider.go deleted file mode 100644 index 2f96f5cce..000000000 --- a/pkg/cobrautils/flagsets/provider.go +++ /dev/null @@ -1,252 +0,0 @@ -package flagsets - -import ( - "fmt" - "strings" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -type ConfigProvider interface { - CreateOptions() ConfigOptions - GetConfigFor(opts ConfigOptions) (Config, error) -} - -type ConfigTypeOptionSetConfigProvider interface { - ConfigProvider - ConfigOptionTypeSet - - GetPlainOptionType() ConfigOptionType - GetTypeOptionType() ConfigOptionType - - IsExplicitlySelected(opts ConfigOptions) bool -} - -type _ConfigTypeOptionSetConfigProvider = ConfigTypeOptionSetConfigProvider - -//////////////////////////////////////////////////////////////////////////////// - -type plainConfigProvider struct { - ConfigOptionTypeSetHandler -} - -var _ ConfigTypeOptionSetConfigProvider = (*plainConfigProvider)(nil) - -func NewPlainConfigProvider(name string, adder ConfigAdder, types ...ConfigOptionType) ConfigTypeOptionSetConfigProvider { - h := NewConfigOptionTypeSetHandler(name, adder, types...) - return &plainConfigProvider{ - ConfigOptionTypeSetHandler: h, - } -} - -func (p *plainConfigProvider) GetConfigOptionTypeSet() ConfigOptionTypeSet { - return p -} - -func (p *plainConfigProvider) GetPlainOptionType() ConfigOptionType { - return nil -} - -func (p *plainConfigProvider) GetTypeOptionType() ConfigOptionType { - return nil -} - -func (p *plainConfigProvider) IsExplicitlySelected(opts ConfigOptions) bool { - return opts.FilterBy(p.HasOptionType).Changed() -} - -func (p *plainConfigProvider) GetConfigFor(opts ConfigOptions) (Config, error) { - if !p.IsExplicitlySelected(opts) { - return nil, nil - } - config := Config{} - err := p.ApplyConfig(opts, config) - return config, err -} - -//////////////////////////////////////////////////////////////////////////////// - -type typedConfigProvider struct { - _ConfigTypeOptionSetConfigProvider - typeOptionType ConfigOptionType -} - -var _ ConfigTypeOptionSetConfigProvider = (*typedConfigProvider)(nil) - -func NewTypedConfigProvider(name string, desc, typeOption string, acceptUnknown ...bool) ConfigTypeOptionSetConfigProvider { - typeOpt := NewStringOptionType(name+"Type", "type of "+desc) - return &typedConfigProvider{NewTypedConfigProviderBase(name, desc, TypeNameProviderFromOptions(typeOption), utils.Optional(acceptUnknown...), typeOpt), typeOpt} -} - -func (p *typedConfigProvider) GetTypeOptionType() ConfigOptionType { - return p.typeOptionType -} - -func (p *typedConfigProvider) IsExplicitlySelected(opts ConfigOptions) bool { - return opts.Changed(p.typeOptionType.GetName(), p.GetName()) -} - -func TypeNameProviderFromOptions(name string) TypeNameProvider { - return func(opts ConfigOptions) (string, error) { - typv, _ := opts.GetValue(name) - typ, ok := typv.(string) - if !ok { - return "", fmt.Errorf("failed to assert type %T as string", typv) - } - return typ, nil - } -} - -/////////////////////////////////////////////////////////////////////////////// - -type ExplicitlyTypedConfigTypeOptionSetConfigProvider interface { - ConfigTypeOptionSetConfigProvider - SetTypeName(n string) -} - -type explicitlyTypedConfigProvider struct { - _ConfigTypeOptionSetConfigProvider - typeName string -} - -var _ ConfigTypeOptionSetConfigProvider = (*typedConfigProvider)(nil) - -func NewExplicitlyTypedConfigProvider(name string, desc string, acceptUnknown ...bool) ExplicitlyTypedConfigTypeOptionSetConfigProvider { - p := &explicitlyTypedConfigProvider{} - p._ConfigTypeOptionSetConfigProvider = NewTypedConfigProviderBase(name, desc, p.getTypeName, utils.Optional(acceptUnknown...)) - return p -} - -func (p *explicitlyTypedConfigProvider) SetTypeName(n string) { - p.typeName = n -} - -func (p *explicitlyTypedConfigProvider) getTypeName(opts ConfigOptions) (string, error) { - return p.typeName, nil -} - -//////////////////////////////////////////////////////////////////////////////// - -type TypeNameProvider func(opts ConfigOptions) (string, error) - -type typedConfigProviderBase struct { - ConfigOptionTypeSet - typeProvider TypeNameProvider - meta ConfigOptionTypeSet - acceptUnknown bool - plainOptionType ConfigOptionType -} - -var _ ConfigTypeOptionSetConfigProvider = (*typedConfigProviderBase)(nil) - -func NewTypedConfigProviderBase(name string, desc string, prov TypeNameProvider, acceptUnknown bool, types ...ConfigOptionType) ConfigTypeOptionSetConfigProvider { - plainType := NewValueMapYAMLOptionType(name, desc+" (YAML)") - set := NewConfigOptionTypeSet(name, append(types, plainType)...) - return &typedConfigProviderBase{ - ConfigOptionTypeSet: set, - typeProvider: prov, - meta: NewConfigOptionTypeSet(name, append(types, NewValueMapYAMLOptionType(name, desc+" (YAML)"))...), - acceptUnknown: acceptUnknown, - plainOptionType: plainType, - } -} - -func (p *typedConfigProviderBase) GetConfigOptionTypeSet() ConfigOptionTypeSet { - return p -} - -func (p *typedConfigProviderBase) GetPlainOptionType() ConfigOptionType { - return p.plainOptionType -} - -func (p *typedConfigProviderBase) GetTypeOptionType() ConfigOptionType { - return nil -} - -func (p *typedConfigProviderBase) IsExplicitlySelected(opts ConfigOptions) bool { - t, err := p.typeProvider(opts) - return err == nil && t != "" -} - -func (p *typedConfigProviderBase) GetConfigFor(opts ConfigOptions) (Config, error) { - typ, err := p.typeProvider(opts) - if err != nil { - return nil, err - } - cfgv, _ := opts.GetValue(p.GetName()) - - var data Config - if cfgv != nil { - var ok bool - data, ok = cfgv.(Config) - if !ok { - return nil, fmt.Errorf("failed to assert type %T as Config", cfgv) - } - } - - opts = opts.FilterBy(p.HasOptionType) - if typ == "" && data != nil && data["type"] != nil { - t := data["type"] - if t != nil { - if s, ok := t.(string); ok { - typ = s - } else { - return nil, fmt.Errorf("type field must be a string") - } - } - } - - if opts.Changed() || typ != "" { - if typ == "" { - return nil, fmt.Errorf("type required for explicitly configured options") - } - if data == nil { - data = Config{} - } - data["type"] = typ - if err := p.applyConfigForType(typ, opts, data); err != nil { - if !p.acceptUnknown || !errors.Is(err, errors.ErrUnknown(typ)) { - return nil, err - } - unexpected := opts.FilterBy(And(Changed(opts), Not(p.meta.HasOptionType))).Names() - if len(unexpected) > 0 { - return nil, fmt.Errorf("unexpected options %s", strings.Join(unexpected, ", ")) - } - } - } - return data, nil -} - -func (p *typedConfigProviderBase) GetTypeSetForType(name string) ConfigOptionTypeSet { - set := p.GetTypeSet(name) - if set == nil { - k, v := runtime.KindVersion(name) - if v == "" { - set = p.GetTypeSet(runtime.TypeName(name, "v1")) - } else if v == "v1" { - set = p.GetTypeSet(k) - } - } - return set -} - -func (p *typedConfigProviderBase) applyConfigForType(name string, opts ConfigOptions, config Config) error { - set := p.GetTypeSetForType(name) - if set == nil { - return errors.ErrUnknown(name) - } - - opts = opts.FilterBy(Not(p.meta.HasOptionType)) - err := opts.Check(set, p.GetName()+" type "+name) - if err != nil { - return err - } - handler, ok := set.(ConfigHandler) - if !ok { - return fmt.Errorf("no config handler defined for %s type %s", p.GetName(), name) - } - return handler.ApplyConfig(opts, config) -} diff --git a/pkg/cobrautils/flagsets/types.go b/pkg/cobrautils/flagsets/types.go deleted file mode 100644 index 8fae19c7b..000000000 --- a/pkg/cobrautils/flagsets/types.go +++ /dev/null @@ -1,553 +0,0 @@ -package flagsets - -import ( - "reflect" - - "github.com/spf13/pflag" - - "github.com/open-component-model/ocm/pkg/cobrautils/flag" - "github.com/open-component-model/ocm/pkg/cobrautils/groups" -) - -type TypeOptionBase struct { - name string - description string -} - -func (b *TypeOptionBase) GetName() string { - return b.name -} - -func (b *TypeOptionBase) GetDescription() string { - return b.description -} - -//////////////////////////////////////////////////////////////////////////////// - -type OptionBase struct { - otyp ConfigOptionType - flag *pflag.Flag - groups []string -} - -func NewOptionBase(otyp ConfigOptionType) OptionBase { - return OptionBase{otyp: otyp} -} - -func (b *OptionBase) Type() ConfigOptionType { - return b.otyp -} - -func (b *OptionBase) GetName() string { - return b.otyp.GetName() -} - -func (b *OptionBase) Description() string { - return b.otyp.GetDescription() -} - -func (b *OptionBase) Changed() bool { - return b.flag.Changed -} - -func (b *OptionBase) AddGroups(groups ...string) { - b.groups = AddGroups(b.groups, groups...) - b.addGroups() -} - -func (b *OptionBase) addGroups() { - if len(b.groups) == 0 || b.flag == nil { - return - } - if b.flag.Annotations == nil { - b.flag.Annotations = map[string][]string{} - } - list := b.flag.Annotations[groups.FlagGroupAnnotation] - b.flag.Annotations[groups.FlagGroupAnnotation] = AddGroups(list, b.groups...) -} - -func (b *OptionBase) TweakFlag(f *pflag.Flag) { - b.flag = f - b.addGroups() -} - -//////////////////////////////////////////////////////////////////////////////// - -type StringOptionType struct { - TypeOptionBase -} - -func NewStringOptionType(name string, description string) ConfigOptionType { - return &StringOptionType{ - TypeOptionBase: TypeOptionBase{name, description}, - } -} - -func (s *StringOptionType) Equal(optionType ConfigOptionType) bool { - return reflect.DeepEqual(s, optionType) -} - -func (s *StringOptionType) Create() Option { - return &StringOption{ - OptionBase: NewOptionBase(s), - } -} - -type StringOption struct { - OptionBase - value string -} - -var _ Option = (*StringOption)(nil) - -func (o *StringOption) AddFlags(fs *pflag.FlagSet) { - o.TweakFlag(flag.StringVarPF(fs, &o.value, o.otyp.GetName(), "", "", o.otyp.GetDescription())) -} - -func (o *StringOption) Value() interface{} { - return o.value -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -type StringArrayOptionType struct { - TypeOptionBase -} - -func NewStringArrayOptionType(name string, description string) ConfigOptionType { - return &StringArrayOptionType{ - TypeOptionBase: TypeOptionBase{name, description}, - } -} - -func (s *StringArrayOptionType) Equal(optionType ConfigOptionType) bool { - return reflect.DeepEqual(s, optionType) -} - -func (s *StringArrayOptionType) Create() Option { - return &StringArrayOption{ - OptionBase: NewOptionBase(s), - } -} - -type StringArrayOption struct { - OptionBase - value []string -} - -var _ Option = (*StringArrayOption)(nil) - -func (o *StringArrayOption) AddFlags(fs *pflag.FlagSet) { - o.TweakFlag(flag.StringArrayVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription())) -} - -func (o *StringArrayOption) Value() interface{} { - return o.value -} - -// PathOptionType ////////////////////////////////////////////////////////////////////////////// - -type PathOptionType struct { - TypeOptionBase -} - -func NewPathOptionType(name string, description string) ConfigOptionType { - return &PathOptionType{ - TypeOptionBase: TypeOptionBase{name, description}, - } -} - -func (s *PathOptionType) Equal(optionType ConfigOptionType) bool { - return reflect.DeepEqual(s, optionType) -} - -func (s *PathOptionType) Create() Option { - return &PathOption{ - OptionBase: NewOptionBase(s), - } -} - -type PathOption struct { - OptionBase - value string -} - -var _ Option = (*PathOption)(nil) - -func (o *PathOption) AddFlags(fs *pflag.FlagSet) { - o.TweakFlag(flag.PathVarPF(fs, &o.value, o.otyp.GetName(), "", "", o.otyp.GetDescription())) -} - -func (o *PathOption) Value() interface{} { - return o.value -} - -// PathArrayOptionType ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -type PathArrayOptionType struct { - TypeOptionBase -} - -func NewPathArrayOptionType(name string, description string) ConfigOptionType { - return &PathArrayOptionType{ - TypeOptionBase: TypeOptionBase{name, description}, - } -} - -func (s *PathArrayOptionType) Equal(optionType ConfigOptionType) bool { - return reflect.DeepEqual(s, optionType) -} - -func (s *PathArrayOptionType) Create() Option { - return &PathArrayOption{ - OptionBase: NewOptionBase(s), - } -} - -type PathArrayOption struct { - OptionBase - value []string -} - -var _ Option = (*PathArrayOption)(nil) - -func (o *PathArrayOption) AddFlags(fs *pflag.FlagSet) { - o.TweakFlag(flag.PathArrayVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription())) -} - -func (o *PathArrayOption) Value() interface{} { - return o.value -} - -//////////////////////////////////////////////////////////////////////////////// - -type BoolOptionType struct { - TypeOptionBase -} - -func NewBoolOptionType(name string, description string) ConfigOptionType { - return &BoolOptionType{ - TypeOptionBase: TypeOptionBase{name, description}, - } -} - -func (s *BoolOptionType) Equal(optionType ConfigOptionType) bool { - return reflect.DeepEqual(s, optionType) -} - -func (s *BoolOptionType) Create() Option { - return &BoolOption{ - OptionBase: NewOptionBase(s), - } -} - -type BoolOption struct { - OptionBase - value bool -} - -var _ Option = (*BoolOption)(nil) - -func (o *BoolOption) AddFlags(fs *pflag.FlagSet) { - o.TweakFlag(flag.BoolVarPF(fs, &o.value, o.otyp.GetName(), "", false, o.otyp.GetDescription())) -} - -func (o *BoolOption) Value() interface{} { - return o.value -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -type IntOptionType struct { - TypeOptionBase -} - -func NewIntOptionType(name string, description string) ConfigOptionType { - return &IntOptionType{ - TypeOptionBase: TypeOptionBase{name, description}, - } -} - -func (s *IntOptionType) Equal(optionType ConfigOptionType) bool { - return reflect.DeepEqual(s, optionType) -} - -func (s *IntOptionType) Create() Option { - return &IntOption{ - OptionBase: NewOptionBase(s), - } -} - -type IntOption struct { - OptionBase - value int -} - -var _ Option = (*IntOption)(nil) - -func (o *IntOption) AddFlags(fs *pflag.FlagSet) { - o.TweakFlag(flag.IntVarPF(fs, &o.value, o.otyp.GetName(), "", 0, o.otyp.GetDescription())) -} - -func (o *IntOption) Value() interface{} { - return o.value -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -type YAMLOptionType struct { - TypeOptionBase -} - -func NewYAMLOptionType(name string, description string) ConfigOptionType { - return &YAMLOptionType{ - TypeOptionBase: TypeOptionBase{name, description}, - } -} - -func (s *YAMLOptionType) Equal(optionType ConfigOptionType) bool { - return reflect.DeepEqual(s, optionType) -} - -func (s *YAMLOptionType) Create() Option { - return &YAMLOption{ - OptionBase: NewOptionBase(s), - } -} - -type YAMLOption struct { - OptionBase - value interface{} -} - -var _ Option = (*YAMLOption)(nil) - -func (o *YAMLOption) AddFlags(fs *pflag.FlagSet) { - o.TweakFlag(flag.YAMLVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription())) -} - -func (o *YAMLOption) Value() interface{} { - return o.value -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -type ValueMapYAMLOptionType struct { - TypeOptionBase -} - -func NewValueMapYAMLOptionType(name string, description string) ConfigOptionType { - return &ValueMapYAMLOptionType{ - TypeOptionBase: TypeOptionBase{name, description}, - } -} - -func (s *ValueMapYAMLOptionType) Equal(optionType ConfigOptionType) bool { - return reflect.DeepEqual(s, optionType) -} - -func (s *ValueMapYAMLOptionType) Create() Option { - return &ValueMapYAMLOption{ - OptionBase: NewOptionBase(s), - } -} - -type ValueMapYAMLOption struct { - OptionBase - value map[string]interface{} -} - -var _ Option = (*ValueMapYAMLOption)(nil) - -func (o *ValueMapYAMLOption) AddFlags(fs *pflag.FlagSet) { - o.TweakFlag(flag.YAMLVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription())) -} - -func (o *ValueMapYAMLOption) Value() interface{} { - return o.value -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -type ValueMapOptionType struct { - TypeOptionBase -} - -func NewValueMapOptionType(name string, description string) ConfigOptionType { - return &ValueMapOptionType{ - TypeOptionBase: TypeOptionBase{name, description}, - } -} - -func (s *ValueMapOptionType) Equal(optionType ConfigOptionType) bool { - return reflect.DeepEqual(s, optionType) -} - -func (s *ValueMapOptionType) Create() Option { - return &ValueMapOption{ - OptionBase: NewOptionBase(s), - } -} - -type ValueMapOption struct { - OptionBase - value map[string]interface{} -} - -var _ Option = (*ValueMapOption)(nil) - -func (o *ValueMapOption) AddFlags(fs *pflag.FlagSet) { - o.TweakFlag(flag.StringToValueVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription())) -} - -func (o *ValueMapOption) Value() interface{} { - return o.value -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -type StringMapOptionType struct { - TypeOptionBase -} - -func NewStringMapOptionType(name string, description string) ConfigOptionType { - return &StringMapOptionType{ - TypeOptionBase: TypeOptionBase{name, description}, - } -} - -func (s *StringMapOptionType) Equal(optionType ConfigOptionType) bool { - return reflect.DeepEqual(s, optionType) -} - -func (s *StringMapOptionType) Create() Option { - return &StringMapOption{ - OptionBase: NewOptionBase(s), - } -} - -type StringMapOption struct { - OptionBase - value map[string]string -} - -var _ Option = (*StringMapOption)(nil) - -func (o *StringMapOption) AddFlags(fs *pflag.FlagSet) { - o.TweakFlag(flag.StringToStringVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription())) -} - -func (o *StringMapOption) Value() interface{} { - return o.value -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -type BytesOptionType struct { - TypeOptionBase -} - -func NewBytesOptionType(name string, description string) ConfigOptionType { - return &BytesOptionType{ - TypeOptionBase: TypeOptionBase{name, description}, - } -} - -func (s *BytesOptionType) Equal(optionType ConfigOptionType) bool { - return reflect.DeepEqual(s, optionType) -} - -func (s *BytesOptionType) Create() Option { - return &BytesOption{ - OptionBase: NewOptionBase(s), - } -} - -type BytesOption struct { - OptionBase - value []byte -} - -var _ Option = (*BytesOption)(nil) - -func (o *BytesOption) AddFlags(fs *pflag.FlagSet) { - o.TweakFlag(flag.BytesBase64VarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription())) -} - -func (o *BytesOption) Value() interface{} { - return o.value -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -type StringSliceMapOptionType struct { - TypeOptionBase -} - -func NewStringSliceMapOptionType(name string, description string) ConfigOptionType { - return &StringSliceMapOptionType{ - TypeOptionBase: TypeOptionBase{name, description}, - } -} - -func (s *StringSliceMapOptionType) Equal(optionType ConfigOptionType) bool { - return reflect.DeepEqual(s, optionType) -} - -func (s *StringSliceMapOptionType) Create() Option { - return &StringSliceMapOption{ - OptionBase: NewOptionBase(s), - } -} - -type StringSliceMapOption struct { - OptionBase - value map[string][]string -} - -var _ Option = (*StringSliceMapOption)(nil) - -func (o *StringSliceMapOption) AddFlags(fs *pflag.FlagSet) { - o.TweakFlag(flag.StringToStringSliceVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription())) -} - -func (o *StringSliceMapOption) Value() interface{} { - return o.value -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -type StringSliceMapColonOptionType struct { - TypeOptionBase -} - -func NewStringSliceMapColonOptionType(name string, description string) ConfigOptionType { - return &StringSliceMapColonOptionType{ - TypeOptionBase: TypeOptionBase{name, description}, - } -} - -func (s *StringSliceMapColonOptionType) Equal(optionType ConfigOptionType) bool { - return reflect.DeepEqual(s, optionType) -} - -func (s *StringSliceMapColonOptionType) Create() Option { - return &StringSliceMapColonOption{ - OptionBase: NewOptionBase(s), - } -} - -type StringSliceMapColonOption struct { - OptionBase - value map[string][]string -} - -var _ Option = (*StringSliceMapColonOption)(nil) - -func (o *StringSliceMapColonOption) AddFlags(fs *pflag.FlagSet) { - o.TweakFlag(flag.StringColonStringSliceVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription())) -} - -func (o *StringSliceMapColonOption) Value() interface{} { - return o.value -} diff --git a/pkg/cobrautils/flagsets/utils_test.go b/pkg/cobrautils/flagsets/utils_test.go deleted file mode 100644 index 91f83b0ba..000000000 --- a/pkg/cobrautils/flagsets/utils_test.go +++ /dev/null @@ -1,112 +0,0 @@ -package flagsets_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" -) - -type Config = flagsets.Config - -var ( - GetField = flagsets.GetField - SetField = flagsets.SetField -) - -var _ = Describe("config", func() { - Context("get", func() { - It("gets a field from empty map", func() { - m := flagsets.Config{} - Expect(GetField(m, "a")).To(BeNil()) - }) - - It("gets a non existing field", func() { - m := Config{} - m["b"] = "vb" - Expect(GetField(m, "a")).To(BeNil()) - }) - - It("gets a flat field", func() { - m := Config{} - m["a"] = "va" - Expect(GetField(m, "a")).To(Equal("va")) - }) - - It("gets a deep field", func() { - m := Config{} - a := Config{} - m["a"] = a - a["b"] = "vb" - Expect(GetField(m, "a", "b")).To(Equal("vb")) - }) - - It("fails for non map", func() { - m := Config{} - m["a"] = "va" - _, err := GetField(m, "a", "b") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("a is no map")) - }) - - It("fails for deep non map", func() { - m := Config{} - a := Config{} - m["a"] = a - a["b"] = "va" - _, err := GetField(m, "a", "b", "c") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("a.b is no map")) - }) - }) - - Context("set", func() { - It("fails for empty map", func() { - err := SetField(nil, "x", "a") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("no map given")) - }) - - It("sets flat field", func() { - m := Config{} - err := SetField(m, "x", "a") - Expect(err).To(Succeed()) - Expect(m).To(Equal(Config{"a": "x"})) - }) - - It("adds flat field", func() { - m := Config{"b": "y"} - err := SetField(m, "x", "a") - Expect(err).To(Succeed()) - Expect(m).To(Equal(Config{"a": "x", "b": "y"})) - }) - - It("sets deep field", func() { - m := Config{"a": Config{}} - err := SetField(m, "x", "a", "b") - Expect(err).To(Succeed()) - Expect(m).To(Equal(Config{"a": Config{"b": "x"}})) - }) - - It("inserts intermediate maps", func() { - m := Config{"a": Config{}} - err := SetField(m, "x", "a", "b", "c", "d") - Expect(err).To(Succeed()) - Expect(m).To(Equal(Config{"a": Config{"b": Config{"c": Config{"d": "x"}}}})) - }) - - It("fails for non map", func() { - m := Config{"a": "va"} - err := SetField(m, "x", "a", "b") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("a is no map")) - }) - - It("fails for non map intermediate", func() { - m := Config{"a": "va"} - err := SetField(m, "x", "a", "b", "c") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("a is no map")) - }) - }) -}) diff --git a/pkg/cobrautils/logopts/close_test.go b/pkg/cobrautils/logopts/close_test.go deleted file mode 100644 index d3dcf3308..000000000 --- a/pkg/cobrautils/logopts/close_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package logopts - -import ( - "runtime" - "time" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/mandelsoft/logging" - "github.com/mandelsoft/vfs/pkg/osfs" - "github.com/mandelsoft/vfs/pkg/vfs" - - logging2 "github.com/open-component-model/ocm/pkg/cobrautils/logopts/logging" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/vfsattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm" -) - -var _ = Describe("log file", func() { - var fs vfs.FileSystem - - BeforeEach(func() { - fs = Must(osfs.NewTempFileSystem()) - }) - - AfterEach(func() { - vfs.Cleanup(fs) - }) - - It("closes log file", func() { - ctx := ocm.New(datacontext.MODE_INITIAL) - lctx := logging.NewDefault() - - vfsattr.Set(ctx, fs) - - opts := &Options{ - ConfigFragment: ConfigFragment{ - LogLevel: "debug", - LogFileName: "debug.log", - }, - } - - MustBeSuccessful(opts.Configure(ctx, lctx)) - - Expect(logging2.GetLogFileFor(opts.LogFileName, fs)).NotTo(BeNil()) - lctx = nil - for i := 1; i < 100; i++ { - time.Sleep(1 * time.Millisecond) - runtime.GC() - } - Expect(logging2.GetLogFileFor(opts.LogFileName, fs)).To(BeNil()) - }) -}) diff --git a/pkg/cobrautils/logopts/config.go b/pkg/cobrautils/logopts/config.go deleted file mode 100644 index d4738f6e3..000000000 --- a/pkg/cobrautils/logopts/config.go +++ /dev/null @@ -1,146 +0,0 @@ -package logopts - -import ( - "strings" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/logging" - "github.com/mandelsoft/logging/config" - "github.com/mandelsoft/logging/logrusl/adapter" - "github.com/mandelsoft/logging/logrusr" - "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/sirupsen/logrus" - "github.com/spf13/pflag" - - logdata "github.com/open-component-model/ocm/pkg/cobrautils/logopts/logging" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/logforward" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/vfsattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/utils" -) - -// ConfigFragment is a serializable log config used -// for CLI commands. -type ConfigFragment struct { - LogLevel string `json:"logLevel,omitempty"` - LogConfig string `json:"logConfig,omitempty"` - LogKeys []string `json:"logKeys,omitempty"` - Json bool `json:"json,omitempty"` - - // LogFileName is a CLI option, only. Do not serialize and forward - LogFileName string `json:"-"` -} - -func (c *ConfigFragment) AddFlags(fs *pflag.FlagSet) { - fs.BoolVarP(&c.Json, "logJson", "", false, "log as json instead of human readable logs") - fs.StringVarP(&c.LogLevel, "loglevel", "l", "", "set log level") - fs.StringVarP(&c.LogFileName, "logfile", "L", "", "set log file") - fs.StringVarP(&c.LogConfig, "logconfig", "", "", "log config") - fs.StringArrayVarP(&c.LogKeys, "logkeys", "", nil, "log tags/realms(with leading /) to be enabled ([/[+]]name{,[/[+]]name}[=level])") -} - -func (c *ConfigFragment) GetLogConfig(fss ...vfs.FileSystem) (*config.Config, error) { - var ( - err error - cfg *config.Config - ) - - if c.LogConfig != "" { - var data []byte - if strings.HasPrefix(c.LogConfig, "@") { - data, err = utils.ReadFile(c.LogConfig[1:], utils.FileSystem(fss...)) - if err != nil { - return nil, errors.Wrapf(err, "cannot read logging config file %q", c.LogConfig[1:]) - } - } else { - data = []byte(c.LogConfig) - } - if cfg, err = config.EvaluateFromData(data); err != nil { - return nil, errors.Wrapf(err, "invalid logging config: %q", c.LogConfig) - } - } else { - cfg = &config.Config{DefaultLevel: "Warn"} - } - - for _, t := range c.LogKeys { - level := logging.InfoLevel - i := strings.Index(t, "=") - if i >= 0 { - level, err = logging.ParseLevel(t[i+1:]) - if err != nil { - return nil, errors.Wrapf(err, "invalid log tag setting") - } - t = t[:i] - } - var cfgcond []config.Condition - - for _, tag := range strings.Split(t, ",") { - tag = strings.TrimSpace(tag) - if strings.HasPrefix(tag, "/") { - realm := tag[1:] - if strings.HasPrefix(realm, "+") { - cfgcond = append(cfgcond, config.RealmPrefix(realm[1:])) - } else { - cfgcond = append(cfgcond, config.Realm(realm)) - } - } else { - cfgcond = append(cfgcond, config.Tag(tag)) - } - } - cfg.Rules = append(cfg.Rules, config.ConditionalRule(logging.LevelName(level), cfgcond...)) - } - - if c.LogLevel != "" { - _, err := logging.ParseLevel(c.LogLevel) - if err != nil { - return nil, errors.Wrapf(err, "invalid log level %q", c.LogLevel) - } - cfg.DefaultLevel = c.LogLevel - } - - return cfg, nil -} - -func (c *ConfigFragment) Evaluate(ctx ocm.Context, logctx logging.Context, main bool) (*EvaluatedOptions, error) { - var err error - var opts EvaluatedOptions - - for logctx.Tree().GetBaseContext() != nil { - logctx = logctx.Tree().GetBaseContext() - } - - fs := vfsattr.Get(ctx) - if main && c.LogFileName != "" && logdata.GlobalLogFileOverride == "" { - if opts.LogFile == nil { - opts.LogFile, err = logdata.LogFileFor(c.LogFileName, fs) - if err != nil { - return nil, errors.Wrapf(err, "cannot open log file %q", opts.LogFile) - } - } - logdata.ConfigureLogrusFor(logctx, !c.Json, opts.LogFile) - if logctx == logging.DefaultContext() { - logdata.GlobalLogFile = opts.LogFile - } - } else { - // overwrite current log formatter in case of a logrus logger is - // used as logging backend. - var f logrus.Formatter = adapter.NewJSONFormatter() - if !c.Json { - f = adapter.NewTextFmtFormatter() - } - logrusr.SetFormatter(logging.UnwrapLogSink(logctx.GetSink()), f) - } - - cfg, err := c.GetLogConfig(fs) - if err != nil { - return &opts, err - } - err = config.Configure(logctx, cfg) - if err != nil { - return &opts, err - } - opts.LogForward = cfg - logforward.Set(ctx.AttributesContext(), opts.LogForward) - - return &opts, nil -} diff --git a/pkg/cobrautils/logopts/options.go b/pkg/cobrautils/logopts/options.go deleted file mode 100644 index 0a4b3c931..000000000 --- a/pkg/cobrautils/logopts/options.go +++ /dev/null @@ -1,71 +0,0 @@ -package logopts - -import ( - "github.com/mandelsoft/logging" - "github.com/mandelsoft/logging/config" - "github.com/spf13/pflag" - - logging2 "github.com/open-component-model/ocm/pkg/cobrautils/logopts/logging" - "github.com/open-component-model/ocm/pkg/contexts/ocm" -) - -var Description = ` -The --log* options can be used to configure the logging behaviour. -For details see ocm logging. - -There is a quick config option --logkeys to configure simple -tag/realm based condition rules. The comma-separated names build an AND rule. -Hereby, names starting with a slash (/) denote a realm (without -the leading slash). A realm is a slash separated sequence of identifiers. If -the realm name starts with a plus (+) character the generated rule -will match the realm and all its sub-realms, otherwise, only the dedicated -realm is affected. For example /+ocm=trace will enable all log output of the -OCM library. - -A tag directly matches the logging tags. Used tags and realms can be found under -topic ocm logging. The ocm coding basically uses the realm ocm. -The default level to enable is info. Separated by an equal sign (=) -optionally a dedicated level can be specified. Log levels can be (error, -warn, info, debug and trace. -The default level is warn. -The --logconfig* options can be used to configure a complete -logging configuration (yaml/json) via command line. If the argument starts with -an @, the logging configuration is taken from a file. -` - -//////////////////////////////////////////////////////////////////////////////// - -type EvaluatedOptions struct { - LogForward *config.Config - LogFile *logging2.LogFile -} - -func (o *EvaluatedOptions) Close() error { - if o.LogFile == nil { - return nil - } - return o.LogFile.Close() -} - -type Options struct { - ConfigFragment - *EvaluatedOptions -} - -func (o *Options) AddFlags(fs *pflag.FlagSet) { - o.ConfigFragment.AddFlags(fs) -} - -func (o *Options) Close() error { - if o.EvaluatedOptions == nil { - return nil - } - return o.EvaluatedOptions.Close() -} - -func (o *Options) Configure(ctx ocm.Context, logctx logging.Context) error { - var err error - - o.EvaluatedOptions, err = o.ConfigFragment.Evaluate(ctx, logctx, true) - return err -} diff --git a/pkg/cobrautils/logopts/options_test.go b/pkg/cobrautils/logopts/options_test.go deleted file mode 100644 index 13c914c90..000000000 --- a/pkg/cobrautils/logopts/options_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package logopts - -import ( - "encoding/json" - "fmt" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/mandelsoft/logging" - "github.com/mandelsoft/logging/config" - "sigs.k8s.io/yaml" - - "github.com/open-component-model/ocm/pkg/contexts/clictx" -) - -var _ = Describe("log configuration", func() { - It("provides forward config", func() { - ctx := clictx.New() - - opts := Options{ - ConfigFragment: ConfigFragment{ - LogLevel: "debug", - LogKeys: []string{ - "tag=trace", - "/realm=info", - "/+all=info", - }, - }, - } - - MustBeSuccessful(opts.Configure(ctx.OCMContext(), ctx.LoggingContext())) - Expect(opts.LogForward).NotTo(BeNil()) - - data := Must(yaml.Marshal(opts.LogForward)) - fmt.Printf("%s\n", string(data)) - logctx := logging.NewWithBase(nil) - MustBeSuccessful(config.Configure(logctx, opts.LogForward)) - - Expect(logctx.GetDefaultLevel()).To(Equal(logging.DebugLevel)) - Expect(logctx.Logger(logging.NewTag("tag")).Enabled(logging.TraceLevel)).To(BeTrue()) - Expect(logctx.Logger(logging.NewRealm("all")).Enabled(logging.DebugLevel)).To(BeFalse()) - Expect(logctx.Logger(logging.NewRealm("all/test")).Enabled(logging.DebugLevel)).To(BeFalse()) - Expect(logctx.Logger(logging.NewRealm("realm")).Enabled(logging.InfoLevel)).To(BeTrue()) - Expect(logctx.Logger(logging.NewRealm("realm")).Enabled(logging.DebugLevel)).To(BeFalse()) - Expect(logctx.Logger(logging.NewRealm("realm/test")).Enabled(logging.InfoLevel)).To(BeTrue()) - Expect(logctx.Logger(logging.NewRealm("realm/test")).Enabled(logging.DebugLevel)).To(BeTrue()) - }) - - Context("serialize", func() { - It("does not serialize log file name", func() { - var c ConfigFragment - c.LogFileName = "test" - data := Must(json.Marshal(&c)) - Expect(string(data)).To(Equal("{}")) - }) - }) -}) diff --git a/pkg/common/accessio/access.go b/pkg/common/accessio/access.go deleted file mode 100644 index e4245cf2c..000000000 --- a/pkg/common/accessio/access.go +++ /dev/null @@ -1,98 +0,0 @@ -package accessio - -import ( - "math/rand" - "time" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/iotools" - "github.com/open-component-model/ocm/pkg/refmgmt" -) - -var ( - ErrClosed = refmgmt.ErrClosed - ErrReadOnly = errors.ErrReadOnly() -) - -//////////////////////////////////////////////////////////////////////////////// - -type NopCloser = iotools.NopCloser - -//////////////////////////////////////////////////////////////////////////////// - -type retriableError struct { - wrapped error -} - -func IsRetriableError(err error) bool { - if err == nil { - return false - } - return errors.IsA(err, &retriableError{}) -} - -func RetriableError(err error) error { - if err == nil { - return nil - } - return &retriableError{err} -} - -func RetriableError1[T any](r T, err error) (T, error) { - if err == nil { - return r, nil - } - return r, &retriableError{err} -} - -func RetriableError2[S, T any](s S, r T, err error) (S, T, error) { - if err == nil { - return s, r, nil - } - return s, r, &retriableError{err} -} - -func (e *retriableError) Error() string { - return e.wrapped.Error() -} - -func (e *retriableError) Unwrap() error { - return e.wrapped -} - -func Retry(cnt int, d time.Duration, f func() error) error { - for { - err := f() - if err == nil || cnt <= 0 || !IsRetriableError(err) { - return err - } - jitter := time.Duration(rand.Int63n(int64(d))) //nolint: gosec // just an random number - d = 2*d + (d/2-jitter)/10 - cnt-- - } -} - -func Retry1[T any](cnt int, d time.Duration, f func() (T, error)) (T, error) { - for { - r, err := f() - if err == nil || cnt <= 0 || !IsRetriableError(err) { - return r, err - } - jitter := time.Duration(rand.Int63n(int64(d))) //nolint: gosec // just an random number - d = 2*d + (d/2-jitter)/10 - cnt-- - } -} - -func Retry2[S, T any](cnt int, d time.Duration, f func() (S, T, error)) (S, T, error) { - for { - s, t, err := f() - if err == nil || cnt <= 0 || !IsRetriableError(err) { - return s, t, err - } - jitter := time.Duration(rand.Int63n(int64(d))) //nolint: gosec // just an random number - d = 2*d + (d/2-jitter)/10 - cnt-- - } -} diff --git a/pkg/common/accessio/cache.go b/pkg/common/accessio/cache.go deleted file mode 100644 index 8f770c339..000000000 --- a/pkg/common/accessio/cache.go +++ /dev/null @@ -1,653 +0,0 @@ -package accessio - -import ( - "fmt" - "io" - "os" - "strings" - "sync" - "time" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/vfs/pkg/osfs" - "github.com/mandelsoft/vfs/pkg/projectionfs" - "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/marstr/guid" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/blobaccess/file" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/iotools" - "github.com/open-component-model/ocm/pkg/refmgmt" -) - -type StaticAllocatable struct{} - -func (_ StaticAllocatable) Ref() error { return nil } -func (_ StaticAllocatable) Unref() error { return nil } - -type BlobSource interface { - refmgmt.Allocatable - GetBlobData(digest digest.Digest) (int64, blobaccess.DataAccess, error) -} - -type BlobSink interface { - refmgmt.Allocatable - AddBlob(blob blobaccess.BlobAccess) (int64, digest.Digest, error) -} - -type RootedCache interface { - Root() (string, vfs.FileSystem) -} - -type CleanupCache interface { - // Cleanup can be implemented to offer a cache reorg. - // It returns the number and size of - // - handled entries (cnt, size) - // - not handled entries (ncnt, nsize) - // - failing entries (fcnt, fsize) - Cleanup(p common.Printer, before *time.Time, dryrun bool) (cnt int, ncnt int, fcnt int, size int64, nsize int64, fsize int64, err error) -} - -type BlobCache interface { - BlobSource - BlobSink - AddData(data blobaccess.DataAccess) (int64, digest.Digest, error) -} - -type blobCache struct { - refmgmt.Allocatable - lock sync.RWMutex - cache vfs.FileSystem -} - -var ( - _ sync.Locker = (*blobCache)(nil) - _ RootedCache = (*blobCache)(nil) -) - -// ACCESS_SUFFIX is the suffix of an additional blob related -// file used to track the last access time by its modification time, -// because Go does not support a platform independent way to access the -// last access time attribute of a filesystem. -const ACCESS_SUFFIX = ".acc" - -func NewDefaultBlobCache(fss ...vfs.FileSystem) (BlobCache, error) { - var err error - fs := DefaultedFileSystem(nil, fss...) - if fs == nil { - fs, err = osfs.NewTempFileSystem() - if err != nil { - return nil, err - } - } - c := &blobCache{ - cache: fs, - } - c.Allocatable = refmgmt.NewAllocatable(c.cleanup) - return c, nil -} - -func NewStaticBlobCache(path string, fss ...vfs.FileSystem) (BlobCache, error) { - fs := FileSystem(fss...) - err := fs.MkdirAll(path, 0o700) - if err != nil { - return nil, err - } - fs, err = projectionfs.New(fs, path) - if err != nil { - return nil, err - } - return NewDefaultBlobCache(fs) -} - -func (c *blobCache) Root() (string, vfs.FileSystem) { - return vfs.PathSeparatorString, c.cache -} - -func (c *blobCache) Lock() { - c.lock.Lock() -} - -func (c *blobCache) Unlock() { - c.lock.Unlock() -} - -func (c *blobCache) Cleanup(p common.Printer, before *time.Time, dryrun bool) (cnt int, ncnt int, fcnt int, size int64, nsize int64, fsize int64, err error) { - c.Lock() - defer c.Unlock() - - if p == nil { - p = common.NewPrinter(nil) - } - path, fs := c.Root() - - entries, err := vfs.ReadDir(fs, path) - if err != nil { - return 0, 0, 0, 0, 0, 0, err - } - for _, e := range entries { - if strings.HasSuffix(e.Name(), ACCESS_SUFFIX) { - continue - } - base := vfs.Join(fs, path, e.Name()) - if before != nil && !before.IsZero() { - fi, err := fs.Stat(base + ACCESS_SUFFIX) - if err != nil { - if !vfs.IsErrNotExist(err) { - if p != nil { - p.Printf("cannot stat %q: %s", e.Name(), err) - } - fcnt++ - fsize += e.Size() - continue - } - } else { - if fi.ModTime().After(*before) { - ncnt++ - nsize += e.Size() - continue - } - } - } - if !dryrun { - err := fs.RemoveAll(base) - if err != nil { - if p != nil { - p.Printf("cannot delete %q: %s", e.Name(), err) - } - fcnt++ - fsize += e.Size() - continue - } - fs.RemoveAll(base + ACCESS_SUFFIX) - } - cnt++ - size += e.Size() - } - return cnt, ncnt, fcnt, size, nsize, fsize, nil -} - -func (c *blobCache) cleanup() error { - return vfs.Cleanup(c.cache) -} - -func (c *blobCache) GetBlobData(digest digest.Digest) (int64, blobaccess.DataAccess, error) { - err := c.Ref() - if err == nil { - defer c.Unref() - c.lock.RLock() - defer c.lock.RUnlock() - - path := common.DigestToFileName(digest) - fi, err := c.cache.Stat(path) - if err == nil { - vfs.WriteFile(c.cache, path+ACCESS_SUFFIX, []byte{}, 0o600) - // now := time.Now() - // c.cache.Chtimes(path+ACCESS_SUFFIX, now, now) - return fi.Size(), file.DataAccess(c.cache, path), nil - } - if os.IsNotExist(err) { - return -1, nil, blobaccess.ErrBlobNotFound(digest) - } - } - return blobaccess.BLOB_UNKNOWN_SIZE, nil, err -} - -func (c *blobCache) AddBlob(blob blobaccess.BlobAccess) (int64, digest.Digest, error) { - err := c.Ref() - if err != nil { - return blobaccess.BLOB_UNKNOWN_SIZE, blobaccess.BLOB_UNKNOWN_DIGEST, err - } - defer c.Unref() - - var digester *iotools.DigestReader - - if blob.DigestKnown() { - c.lock.RLock() - path := common.DigestToFileName(blob.Digest()) - if ok, err := vfs.Exists(c.cache, path); ok || err != nil { - c.lock.RUnlock() - return blob.Size(), blob.Digest(), err - } - c.lock.RUnlock() - } - - tmp := "TMP" + guid.NewGUID().String() - - br, err := blob.Reader() - if err != nil { - return blobaccess.BLOB_UNKNOWN_SIZE, "", errors.Wrapf(err, "cannot get blob content") - } - defer br.Close() - - reader := io.Reader(br) - if !blob.DigestKnown() { - digester = iotools.NewDefaultDigestReader(reader) - reader = digester - } - - writer, err := c.cache.Create(tmp) - if err != nil { - return blobaccess.BLOB_UNKNOWN_SIZE, "", errors.Wrapf(err, "cannot create blob file in cache") - } - size, err := io.Copy(writer, reader) - writer.Close() - if err != nil { - c.cache.Remove(tmp) - return blobaccess.BLOB_UNKNOWN_SIZE, "", err - } - - var digest digest.Digest - var ok bool - if digester != nil { - digest = digester.Digest() - } else { - digest = blob.Digest() - } - target := common.DigestToFileName(digest) - - c.lock.Lock() - defer c.lock.Unlock() - if ok, err = vfs.Exists(c.cache, target); err != nil || !ok { - err = c.cache.Rename(tmp, target) - } - c.cache.Remove(tmp) - vfs.WriteFile(c.cache, target+ACCESS_SUFFIX, []byte{}, 0o600) - return size, digest, err -} - -func (c *blobCache) AddData(data blobaccess.DataAccess) (int64, digest.Digest, error) { - return c.AddBlob(blobaccess.ForDataAccess(blobaccess.BLOB_UNKNOWN_DIGEST, blobaccess.BLOB_UNKNOWN_SIZE, "", data)) -} - -//////////////////////////////////////////////////////////////////////////////// - -type cascadedCache struct { - refmgmt.Allocatable - lock sync.RWMutex - parent BlobSource - source BlobSource - sink BlobSink -} - -var _ BlobCache = (*cascadedCache)(nil) - -func NewCascadedBlobCache(parent BlobCache) (BlobCache, error) { - if parent != nil { - err := parent.Ref() - if err != nil { - return nil, err - } - } - c := &cascadedCache{ - parent: parent, - } - c.Allocatable = refmgmt.NewAllocatable(c.cleanup) - return c, nil -} - -func NewCascadedBlobCacheForSource(parent BlobSource, src BlobSource) (BlobCache, error) { - if parent != nil { - err := parent.Ref() - if err != nil { - return nil, err - } - } - if src != nil { - err := src.Ref() - if err != nil { - return nil, err - } - } - c := &cascadedCache{ - parent: parent, - source: src, - } - c.Allocatable = refmgmt.NewAllocatable(c.cleanup) - return c, nil -} - -func NewCascadedBlobCacheForCache(parent BlobSource, src BlobCache) (BlobCache, error) { - if parent != nil { - err := parent.Ref() - if err != nil { - return nil, err - } - } - if src != nil { - err := src.Ref() - if err != nil { - return nil, err - } - } - c := &cascadedCache{ - parent: parent, - source: src, - sink: src, - } - c.Allocatable = refmgmt.NewAllocatable(c.cleanup) - return c, nil -} - -func (c *cascadedCache) cleanup() error { - list := errors.ErrListf("closing cascaded blob cache") - if c.source != nil { - list.Add(c.source.Unref()) - } - if c.parent != nil { - list.Add(c.parent.Unref()) - } - return list.Result() -} - -func (c *cascadedCache) GetBlobData(digest digest.Digest) (int64, blobaccess.DataAccess, error) { - err := c.Ref() - if err != nil { - return blobaccess.BLOB_UNKNOWN_SIZE, nil, err - } - defer c.Unref() - - c.lock.RLock() - defer c.lock.RUnlock() - - if c.source != nil { - size, acc, err := c.source.GetBlobData(digest) - if err == nil { - return size, acc, err - } - if !blobaccess.IsErrBlobNotFound(err) { - return blobaccess.BLOB_UNKNOWN_SIZE, nil, err - } - } - if c.parent != nil { - return c.parent.GetBlobData(digest) - } - return blobaccess.BLOB_UNKNOWN_SIZE, nil, blobaccess.ErrBlobNotFound(digest) -} - -func (c *cascadedCache) AddData(data blobaccess.DataAccess) (int64, digest.Digest, error) { - return c.AddBlob(blobaccess.ForDataAccess(blobaccess.BLOB_UNKNOWN_DIGEST, blobaccess.BLOB_UNKNOWN_SIZE, "", data)) -} - -func (c *cascadedCache) AddBlob(blob blobaccess.BlobAccess) (int64, digest.Digest, error) { - err := c.Ref() - if err == nil { - defer c.Unref() - c.lock.Lock() - defer c.lock.Unlock() - - if c.source == nil { - cache, err := NewDefaultBlobCache() - if err != nil { - return blobaccess.BLOB_UNKNOWN_SIZE, blobaccess.BLOB_UNKNOWN_DIGEST, err - } - c.source = cache - c.sink = cache - } - if c.sink != nil { - return c.sink.AddBlob(blob) - } - if c.parent != nil { - if sink, ok := c.parent.(BlobSink); ok { - return sink.AddBlob(blob) - } - } - } - return blobaccess.BLOB_UNKNOWN_SIZE, blobaccess.BLOB_UNKNOWN_DIGEST, err -} - -//////////////////////////////////////////////////////////////////////////////// - -type cached struct { - refmgmt.Allocatable - lock sync.RWMutex - source BlobSource - sink BlobSink - cache BlobCache -} - -var _ BlobCache = (*cached)(nil) - -func (c *cached) cleanup() error { - list := errors.ErrListf("closing cached blob store") - if c.sink != nil { - list.Add(c.sink.Unref()) - } - if c.source != nil { - list.Add(c.source.Unref()) - } - c.cache.Unref() - return list.Result() -} - -func (a *cached) GetBlobData(digest digest.Digest) (int64, blobaccess.DataAccess, error) { - err := a.Ref() - if err != nil { - return blobaccess.BLOB_UNKNOWN_SIZE, nil, err - } - defer a.Unref() - - size, acc, err := a.cache.GetBlobData(digest) - if err != nil { - if !blobaccess.IsErrBlobNotFound(err) { - return blobaccess.BLOB_UNKNOWN_SIZE, nil, err - } - size, acc, err = a.source.GetBlobData(digest) - if err == nil { - acc = newCachedAccess(a, acc, size, digest) - } - } - return size, acc, err -} - -func (a *cached) AddBlob(blob blobaccess.BlobAccess) (int64, digest.Digest, error) { - err := a.Ref() - if err != nil { - return blobaccess.BLOB_UNKNOWN_SIZE, blobaccess.BLOB_UNKNOWN_DIGEST, err - } - defer a.Unref() - - if a.sink == nil { - return blobaccess.BLOB_UNKNOWN_SIZE, blobaccess.BLOB_UNKNOWN_DIGEST, fmt.Errorf("no blob sink") - } - size, digest, err := a.cache.AddBlob(blob) - if err != nil { - return blobaccess.BLOB_UNKNOWN_SIZE, blobaccess.BLOB_UNKNOWN_DIGEST, err - } - _, acc, err := a.cache.GetBlobData(digest) - if err != nil { - return blobaccess.BLOB_UNKNOWN_SIZE, blobaccess.BLOB_UNKNOWN_DIGEST, err - } - size, digest, err = a.sink.AddBlob(blobaccess.ForDataAccess(digest, size, blob.MimeType(), acc)) - if err != nil { - return blobaccess.BLOB_UNKNOWN_SIZE, blobaccess.BLOB_UNKNOWN_DIGEST, err - } - return size, digest, err -} - -func (c *cached) AddData(data blobaccess.DataAccess) (int64, digest.Digest, error) { - return c.AddBlob(blobaccess.ForDataAccess(blobaccess.BLOB_UNKNOWN_DIGEST, blobaccess.BLOB_UNKNOWN_SIZE, "", data)) -} - -///////////////////////////////////////// - -type cachedAccess struct { - lock sync.Mutex - cache *cached - access blobaccess.DataAccess - digest digest.Digest - size int64 - orig blobaccess.DataAccess -} - -var _ blobaccess.DataAccess = (*cachedAccess)(nil) - -func CachedAccess(src BlobSource, dst BlobSink, cache BlobCache) (BlobCache, error) { - var err error - if cache == nil { - cache, err = NewDefaultBlobCache() - if err != nil { - return nil, err - } - } else { - err = cache.Ref() - if err != nil { - return nil, err - } - } - if src != nil { - err = src.Ref() - if err != nil { - return nil, err - } - } - if dst != nil { - err = dst.Ref() - if err != nil { - return nil, err - } - } - c := &cached{source: src, sink: dst, cache: cache} - c.Allocatable = refmgmt.NewAllocatable(c.cleanup) - return c, nil -} - -func newCachedAccess(cache *cached, blob blobaccess.DataAccess, size int64, digest digest.Digest) blobaccess.DataAccess { - return &cachedAccess{ - cache: cache, - size: size, - digest: digest, - orig: blob, - } -} - -func (c *cachedAccess) Get() ([]byte, error) { - var err error - - c.lock.Lock() - defer c.lock.Unlock() - if c.access == nil && c.digest != "" { - c.size, c.access, _ = c.cache.cache.GetBlobData(c.digest) - } - if c.access == nil { - c.cache.lock.Lock() - defer c.cache.lock.Unlock() - - if c.digest != "" { - c.size, c.access, err = c.cache.cache.GetBlobData(c.digest) - if err != nil && !blobaccess.IsErrBlobNotFound(err) { - return nil, err - } - } - if c.access == nil { - data, err := c.orig.Get() - if err != nil { - return nil, err - } - c.size, c.digest, err = c.cache.cache.AddData(blobaccess.DataAccessForData(data)) - if err == nil { - c.orig.Close() - c.orig = nil - } - return data, err - } - } - return c.access.Get() -} - -func (c *cachedAccess) Reader() (io.ReadCloser, error) { - var err error - - c.lock.Lock() - defer c.lock.Unlock() - if c.access == nil && c.digest != "" { - c.size, c.access, _ = c.cache.cache.GetBlobData(c.digest) - } - if c.access == nil { - c.cache.lock.Lock() - defer c.cache.lock.Unlock() - - if c.digest != "" { - c.size, c.access, err = c.cache.cache.GetBlobData(c.digest) - if err != nil && !blobaccess.IsErrBlobNotFound(err) { - return nil, err - } - } - if c.access == nil { - c.size, c.digest, err = c.cache.cache.AddData(c.orig) - if err == nil { - _, c.access, err = c.cache.cache.GetBlobData(c.digest) - } - if err != nil { - return nil, err - } - c.orig.Close() - c.orig = nil - } - } - return c.access.Reader() -} - -func (c *cachedAccess) Close() error { - return nil -} - -func (c *cachedAccess) Size() int64 { - return c.size -} - -//////////////////////////////////////////////////////////////////////////////// - -type norefBlobSource struct { - BlobSource -} - -var _ BlobSource = (*norefBlobSource)(nil) - -func NoRefBlobSource(s BlobSource) BlobSource { return &norefBlobSource{s} } - -func (norefBlobSource) Ref() error { - return nil -} - -func (norefBlobSource) Unref() error { - return nil -} - -//////////////////////////////////////////////////////////////////////////////// - -type norefBlobSink struct { - BlobSink -} - -var _ BlobSink = (*norefBlobSink)(nil) - -func NoRefBlobSink(s BlobSink) BlobSink { return &norefBlobSink{s} } - -func (norefBlobSink) Ref() error { - return nil -} - -func (norefBlobSink) Unref() error { - return nil -} - -//////////////////////////////////////////////////////////////////////////////// - -type norefBlobCache struct { - BlobCache -} - -var _ BlobCache = (*norefBlobCache)(nil) - -func NoRefBlobCache(s BlobCache) BlobCache { return &norefBlobCache{s} } - -func (norefBlobCache) Ref() error { - return nil -} - -func (norefBlobCache) Unref() error { - return nil -} diff --git a/pkg/common/accessio/deprecated.go b/pkg/common/accessio/deprecated.go deleted file mode 100644 index 6513bcb2f..000000000 --- a/pkg/common/accessio/deprecated.go +++ /dev/null @@ -1,233 +0,0 @@ -package accessio - -import ( - "crypto" - "io" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/sliceutils" - "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/blobaccess/file" - "github.com/open-component-model/ocm/pkg/iotools" - "github.com/open-component-model/ocm/pkg/utils" -) - -const ( - // Deprecated: use blobaccess.BLOB_UNKNOWN_SIZE. - BLOB_UNKNOWN_SIZE = blobaccess.BLOB_UNKNOWN_SIZE - // Deprecated: use blobaccess.BLOB_UNKNOWN_DIGEST. - BLOB_UNKNOWN_DIGEST = blobaccess.BLOB_UNKNOWN_DIGEST -) - -const ( - // Deprecated: use blobaccess.KIND_BLOB. - KIND_BLOB = blobaccess.KIND_BLOB - // Deprecated: use blobaccess.KIND_MEDIATYPE. - KIND_MEDIATYPE = blobaccess.KIND_MEDIATYPE -) - -// Deprecated: use blobaccess.ErrBlobNotFound. -func ErrBlobNotFound(digest digest.Digest) error { - return errors.ErrNotFound(blobaccess.KIND_BLOB, digest.String()) -} - -// Deprecated: use blobaccess.IsErrBlobNotFound. -func IsErrBlobNotFound(err error) bool { - return errors.IsErrNotFoundKind(err, blobaccess.KIND_BLOB) -} - -//////////////////////////////////////////////////////////////////////////////// - -// DataSource describes some data plus its origin. -// Deprecated: use blobaccess.DataSource. -type DataSource = blobaccess.DataSource - -//////////////////////////////////////////////////////////////////////////////// - -// DataAccess describes the access to sequence of bytes. -// Deprecated: use blobaccess.DataAccess. -type DataAccess = blobaccess.DataAccess - -// BlobAccess describes the access to a blob. -// Deprecated: use blobaccess.BlobAccess. -type BlobAccess = blobaccess.BlobAccess - -//////////////////////////////////////////////////////////////////////////////// - -// Deprecated: use blobaccess.DataAccessForReaderFunction. -func DataAccessForReaderFunction(reader func() (io.ReadCloser, error), origin string) blobaccess.DataAccess { - return blobaccess.DataAccessForReaderFunction(reader, origin) -} - -//////////////////////////////////////////////////////////////////////////////// - -// Deprecated: use blobaccess.DataAccessForFile. -func DataAccessForFile(fs vfs.FileSystem, path string) blobaccess.DataAccess { - return file.DataAccess(fs, path) -} - -//////////////////////////////////////////////////////////////////////////////// - -// Deprecated: use blobaccess.DataAccessForBytes. -func DataAccessForBytes(data []byte, origin ...string) blobaccess.DataSource { - return blobaccess.DataAccessForData(data, origin...) -} - -// Deprecated: use blobaccess.DataAccessForString. -func DataAccessForString(data string, origin ...string) blobaccess.DataSource { - return blobaccess.DataAccessForData([]byte(data), origin...) -} - -//////////////////////////////////////////////////////////////////////////////// - -// BlobWithMimeType changes the mime type for a blob access -// by wrapping the given blob access. It does NOT provide -// a new view for the given blob access, so closing the resulting -// blob access will directly close the backing blob access. -// Deprecated: use blobaccess.WithMimeType. -func BlobWithMimeType(mimeType string, blob blobaccess.BlobAccess) blobaccess.BlobAccess { - return blobaccess.WithMimeType(mimeType, blob) -} - -//////////////////////////////////////////////////////////////////////////////// - -// AnnotatedBlobAccess provides access to the original underlying data source. -// Deprecated: use blobaccess.AnnotatedBlobAccess. -type AnnotatedBlobAccess[T blobaccess.DataAccess] interface { - blobaccess.BlobAccess - Source() T -} - -// BlobAccessForDataAccess wraps the general access object into a blob access. -// It closes the wrapped access, if closed. -// Deprecated: use blobaccess.ForDataAccess. -func BlobAccessForDataAccess[T blobaccess.DataAccess](digest digest.Digest, size int64, mimeType string, access T) blobaccess.AnnotatedBlobAccess[T] { - return blobaccess.ForDataAccess[T](digest, size, mimeType, access) -} - -// Deprecated: use blobaccess.ForString. -func BlobAccessForString(mimeType string, data string) blobaccess.BlobAccess { - return blobaccess.ForData(mimeType, []byte(data)) -} - -// Deprecated: use blobaccess.ForData. -func BlobAccessForData(mimeType string, data []byte) blobaccess.BlobAccess { - return blobaccess.ForData(mimeType, data) -} - -//////////////////////////////////////////////////////////////////////////////// - -// Deprecated: use blobaccess.ForFile. -func BlobAccessForFile(mimeType string, path string, fss ...vfs.FileSystem) blobaccess.BlobAccess { - return file.BlobAccess(mimeType, path, fss...) -} - -// Deprecated: use blobaccess.ForFileWithCloser. -func BlobAccessForFileWithCloser(closer io.Closer, mimeType string, path string, fss ...vfs.FileSystem) blobaccess.BlobAccess { - return file.BlobAccessWithCloser(closer, mimeType, path, fss...) -} - -//////////////////////////////////////////////////////////////////////////////// - -// Deprecated: use blobaccess.ForTemporaryFile. -func BlobAccessForTemporaryFile(mime string, temp vfs.File, fss ...vfs.FileSystem) blobaccess.BlobAccess { - return file.BlobAccessForTemporaryFile(mime, temp, file.WithFileSystem(fss...)) -} - -// Deprecated: use blobaccess.ForTemporaryFilePath. -func BlobAccessForTemporaryFilePath(mime string, temp string, fss ...vfs.FileSystem) blobaccess.BlobAccess { - return file.BlobAccessForTemporaryFilePath(mime, temp, file.WithFileSystem(fss...)) -} - -//////////////////////////////////////////////////////////////////////////////// - -// Deprecated: use blobaccess.NewTempFile. -func NewTempFile(fs vfs.FileSystem, dir string, pattern string) (*file.TempFile, error) { - return file.NewTempFile(dir, pattern, fs) -} - -//////////////////////////////////////////////////////////////////////////////// - -// Deprecated: use iotools.DigestReader. -type DigestReader = iotools.DigestReader - -// Deprecated: use iotools.NewDefaultDigestReader. -func NewDefaultDigestReader(r io.Reader) *iotools.DigestReader { - return iotools.NewDigestReaderWith(digest.Canonical, r) -} - -// Deprecated: use iotools.NewDigestReaderWith. -func NewDigestReaderWith(algorithm digest.Algorithm, r io.Reader) *iotools.DigestReader { - return iotools.NewDigestReaderWith(algorithm, r) -} - -// Deprecated: use iotools.NewDigestReaderWithHash. -func NewDigestReaderWithHash(hash crypto.Hash, r io.Reader) *iotools.DigestReader { - return iotools.NewDigestReaderWithHash(hash, r) -} - -// Deprecated: use iotools.VerifyingReader. -func VerifyingReader(r io.ReadCloser, digest digest.Digest) io.ReadCloser { - return iotools.VerifyingReader(r, digest) -} - -// Deprecated: use iotools.VerifyingReaderWithHash. -func VerifyingReaderWithHash(r io.ReadCloser, hash crypto.Hash, digest string) io.ReadCloser { - return iotools.VerifyingReaderWithHash(r, hash, digest) -} - -// Deprecated: use blobaccess.Digest. -func Digest(access blobaccess.DataAccess) (digest.Digest, error) { - return blobaccess.Digest(access) -} - -//////////////////////////////////////////////////////////////////////////////// - -// Deprecated: use iotools.DigestWriter. -type DigestWriter = iotools.DigestWriter - -// Deprecated: use iotools.NewDefaultDigestWriter. -func NewDefaultDigestWriter(w io.WriteCloser) *iotools.DigestWriter { - return iotools.NewDefaultDigestWriter(w) -} - -// Deprecated: use iotools.NewDigestWriterWith. -func NewDigestWriterWith(algorithm digest.Algorithm, w io.WriteCloser) *iotools.DigestWriter { - return iotools.NewDigestWriterWith(algorithm, w) -} - -//////////////////////////////////////////////////////////////////////////////// - -// Deprecated: use blobaccess.BlobData. -func BlobData(blob blobaccess.DataGetter, err error) ([]byte, error) { - if err != nil { - return nil, err - } - return blob.Get() -} - -// Deprecated: use blobaccess.BlobReader. -func BlobReader(blob blobaccess.DataReader, err error) (io.ReadCloser, error) { - if err != nil { - return nil, err - } - return blob.Reader() -} - -// Deprecated: use utils.FileSystem. -func FileSystem(fss ...vfs.FileSystem) vfs.FileSystem { - return utils.FileSystem(fss...) -} - -// Deprecated: use utils.DefaultedFileSystem. -func DefaultedFileSystem(def vfs.FileSystem, fss ...vfs.FileSystem) vfs.FileSystem { - return utils.DefaultedFileSystem(def, fss...) -} - -// Deprecated: use iotools.AddReaderCloser. -func AddCloser(reader io.ReadCloser, closer io.Closer, msg ...string) io.ReadCloser { - return iotools.AddReaderCloser(reader, closer, sliceutils.AsAny(msg)...) -} diff --git a/pkg/common/accessio/downloader/http/downloader.go b/pkg/common/accessio/downloader/http/downloader.go deleted file mode 100644 index 484c0989d..000000000 --- a/pkg/common/accessio/downloader/http/downloader.go +++ /dev/null @@ -1,45 +0,0 @@ -package http - -import ( - "bytes" - "context" - "fmt" - "io" - "net/http" - - "github.com/open-component-model/ocm/pkg/common/accessio/downloader" -) - -// Downloader simply uses the default HTTP client to download the contents of a URL. -type Downloader struct { - link string -} - -func NewDownloader(link string) downloader.Downloader { - return &Downloader{ - link: link, - } -} - -func (h *Downloader) Download(w io.WriterAt) error { - req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, h.link, nil) - if err != nil { - return err - } - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return fmt.Errorf("failed to get link: %w", err) - } - defer resp.Body.Close() - - var blob []byte - buf := bytes.NewBuffer(blob) - if _, err := io.Copy(buf, resp.Body); err != nil { - return fmt.Errorf("failed to copy response body: %w", err) - } - if _, err := w.WriteAt(buf.Bytes(), 0); err != nil { - return fmt.Errorf("failed to WriteAt to the writer: %w", err) - } - return nil -} diff --git a/pkg/common/accessio/format.go b/pkg/common/accessio/format.go deleted file mode 100644 index 9ef0d43f3..000000000 --- a/pkg/common/accessio/format.go +++ /dev/null @@ -1,176 +0,0 @@ -package accessio - -import ( - "archive/tar" - "compress/gzip" - "io" - "sort" - "strings" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/common/compression" - "github.com/open-component-model/ocm/pkg/utils/tarutils" -) - -const KIND_FILEFORMAT = "file format" - -type FileFormat string - -func (f FileFormat) String() string { - return string(f) -} - -func (f FileFormat) Suffix() string { - return suffixes[f] -} - -func (o FileFormat) ApplyOption(options Options) error { - if o != "" { - options.SetFileFormat(o) - } - return nil -} - -const ( - FormatTar FileFormat = "tar" - FormatTGZ FileFormat = "tgz" - FormatDirectory FileFormat = "directory" - FormatNone FileFormat = "" -) - -var suffixes = map[FileFormat]string{ - FormatTar: "." + string(FormatTar), - FormatTGZ: "." + string(FormatTGZ), -} - -func ErrInvalidFileFormat(fmt string) error { - return errors.ErrInvalid(KIND_FILEFORMAT, fmt) -} - -//////////////////////////////////////////////////////////////////////////////// - -func GetFormats() []string { - return []string{string(FormatDirectory), string(FormatTar), string(FormatTGZ)} -} - -func GetFormatsFor[T any](fileFormats map[FileFormat]T) []string { - var def FileFormat - - list := []string{} - for k := range fileFormats { - // as favorite default, directory should be the first entry in the list - if k != FormatDirectory { - list = append(list, string(k)) - } else { - def = k - } - } - sort.Strings(list) - if def != "" { - return append(append(list[:0:0], string(def)), list...) - } - return list -} - -// FileFormatForTypeSpec returns the format hint provided -// by a type specification.The format hint is an optional -// suffix separated by a +. -func FileFormatForTypeSpec(t string) FileFormat { - i := strings.Index(t, "+") - if i < 0 { - return "" - } - return FileFormat(t[i+1:]) -} - -// TypeForTypeSpec returns the pure type info provided -// by a type specification.The format hint is an optional -// suffix separated by a +. -func TypeForTypeSpec(t string) string { - i := strings.Index(t, "+") - if i < 0 { - return t - } - return t[:i] -} - -//////////////////////////////////////////////////////////////////////////////// - -func CopyFileSystem(format FileFormat, srcfs vfs.FileSystem, src string, dstfs vfs.FileSystem, dst string, perm vfs.FileMode) error { - compr := compression.None - switch format { - case FormatDirectory: - return vfs.CopyDir(srcfs, src, dstfs, dst) - case FormatTGZ: - compr = compression.Gzip - fallthrough - case FormatTar: - file, err := dstfs.OpenFile(dst, vfs.O_CREATE|vfs.O_TRUNC|vfs.O_WRONLY, perm) - if err != nil { - return err - } - defer file.Close() - w, err := compr.Compressor(file, nil, nil) - if err != nil { - return err - } - return tarutils.PackFsIntoTar(srcfs, src, w, tarutils.TarFileSystemOptions{}) - default: - return errors.ErrUnknown(KIND_FILEFORMAT, format.String()) - } -} - -//////////////////////////////////////////////////////////////////////////////// - -func DetectFormat(path string, fs vfs.FileSystem) (*FileFormat, error) { - if fs == nil { - fs = _osfs - } - - fi, err := fs.Stat(path) - if err != nil { - return nil, err - } - - format := FormatDirectory - if !fi.IsDir() { - file, err := fs.Open(path) - if err != nil { - return nil, err - } - defer file.Close() - return DetectFormatForFile(file) - } - return &format, nil -} - -func DetectFormatForFile(file vfs.File) (*FileFormat, error) { - fi, err := file.Stat() - if err != nil { - return nil, err - } - format := FormatDirectory - if !fi.IsDir() { - var r io.Reader - - defer file.Seek(0, io.SeekStart) - zip, err := gzip.NewReader(file) - if err == nil { - format = FormatTGZ - defer zip.Close() - r = zip - } else { - file.Seek(0, io.SeekStart) - format = FormatTar - r = file - } - t := tar.NewReader(r) - _, err = t.Next() - if err != nil { - return nil, err - } - } - return &format, nil -} diff --git a/pkg/common/accessio/utils.go b/pkg/common/accessio/utils.go deleted file mode 100644 index bc9a1c62a..000000000 --- a/pkg/common/accessio/utils.go +++ /dev/null @@ -1,84 +0,0 @@ -package accessio - -import ( - "fmt" - "io" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/common/compression" -) - -type closableReader struct { - reader io.Reader -} - -func ReadCloser(r io.Reader) io.ReadCloser { return closableReader{r} } - -func (r closableReader) Read(p []byte) (n int, err error) { - return r.reader.Read(p) -} - -func (r closableReader) Close() error { - return nil -} - -//////////////////////////////////////////////////////////////////////////////// - -// NopWriteCloser returns a ReadCloser with a no-op Close method wrapping -// the provided Reader r. -func NopWriteCloser(w io.Writer) io.WriteCloser { - return compression.NopWriteCloser(w) -} - -//////////////////////////////////////////////////////////////////////////////// - -type once struct { - callbacks []CloserCallback - closer io.Closer -} - -type CloserCallback func() - -func OnceCloser(c io.Closer, callbacks ...CloserCallback) io.Closer { - return &once{callbacks, c} -} - -func (c *once) Close() error { - if c.closer == nil { - return nil - } - - t := c.closer - c.closer = nil - err := t.Close() - - for _, cb := range c.callbacks { - cb() - } - - if err != nil { - return fmt.Errorf("unable to close: %w", err) - } - - return nil -} - -func Close(closer ...io.Closer) error { - if len(closer) == 0 { - return nil - } - list := errors.ErrList() - for _, c := range closer { - if c != nil { - list.Add(c.Close()) - } - } - return list.Result() -} - -type Closer func() error - -func (c Closer) Close() error { - return c() -} diff --git a/pkg/common/accessobj/check.go b/pkg/common/accessobj/check.go deleted file mode 100644 index 0bd554aa2..000000000 --- a/pkg/common/accessobj/check.go +++ /dev/null @@ -1,81 +0,0 @@ -package accessobj - -import ( - "archive/tar" - "io" - - "github.com/mandelsoft/filepath/pkg/filepath" - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/common/compression" -) - -func mapErr(forced bool, err error) (bool, bool, error) { - if !forced { - return false, false, nil - } - return false, true, err -} - -// CheckFile returns create, acceptable, error. -func CheckFile(kind string, createHint string, forcedType bool, path string, fs vfs.FileSystem, descriptorname string) (bool, bool, error) { - info, err := fs.Stat(path) - if err != nil { - if createHint == kind { - if vfs.IsErrNotExist(err) { - return true, true, nil - } - } - return mapErr(forcedType, err) - } - accepted := false - if !info.IsDir() { - file, err := fs.Open(path) - if err != nil { - return mapErr(forcedType, err) - } - defer file.Close() - forcedType = false - r, _, err := compression.AutoDecompress(file) - if err != nil { - return mapErr(forcedType, err) - } - tr := tar.NewReader(r) - for { - header, err := tr.Next() - if err != nil { - if errors.Is(err, io.EOF) { - break - } - return mapErr(forcedType, err) - } - - switch header.Typeflag { - case tar.TypeReg: - if header.Name == descriptorname { - accepted = true - break - } - } - } - } else { - if forcedType { - entries, err := vfs.ReadDir(fs, path) - if err == nil && len(entries) > 0 { - forcedType = false - } - } - if ok, err := vfs.FileExists(fs, filepath.Join(path, descriptorname)); !ok || err != nil { - if err != nil { - return mapErr(forcedType, err) - } - } else { - accepted = ok - } - } - if !accepted { - return mapErr(forcedType, errors.Newf("%s: no %s", path, kind)) - } - return false, true, nil -} diff --git a/pkg/common/accessobj/filesystemaccess.go b/pkg/common/accessobj/filesystemaccess.go deleted file mode 100644 index bdf667c7e..000000000 --- a/pkg/common/accessobj/filesystemaccess.go +++ /dev/null @@ -1,155 +0,0 @@ -package accessobj - -import ( - "fmt" - "io" - "os" - "sync" - - "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/blobaccess/file" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/utils/tarutils" -) - -type FileSystemBlobAccess struct { - sync.RWMutex - base *AccessObject -} - -func NewFileSystemBlobAccess(access *AccessObject) *FileSystemBlobAccess { - return &FileSystemBlobAccess{ - base: access, - } -} - -func (a *FileSystemBlobAccess) Access() *AccessObject { - return a.base -} - -func (a *FileSystemBlobAccess) SetReadOnly() { - a.base.SetReadOnly() -} - -func (a *FileSystemBlobAccess) IsReadOnly() bool { - return a.base.IsReadOnly() -} - -func (a *FileSystemBlobAccess) IsClosed() bool { - return a.base.IsClosed() -} - -func (a *FileSystemBlobAccess) Write(path string, mode vfs.FileMode, opts ...accessio.Option) error { - return a.base.Write(path, mode, opts...) -} - -func (a *FileSystemBlobAccess) Update() error { - return a.base.Update() -} - -func (a *FileSystemBlobAccess) Close() error { - return a.base.Close() -} - -func (a *FileSystemBlobAccess) GetState() State { - return a.base.GetState() -} - -// DigestPath returns the path to the blob for a given name. -func (a *FileSystemBlobAccess) DigestPath(digest digest.Digest) string { - return a.BlobPath(common.DigestToFileName(digest)) -} - -// BlobPath returns the path to the blob for a given name. -func (a *FileSystemBlobAccess) BlobPath(name string) string { - return a.base.GetInfo().SubPath(name) -} - -func (a *FileSystemBlobAccess) GetBlobData(digest digest.Digest) (int64, blobaccess.DataAccess, error) { - if a.IsClosed() { - return blobaccess.BLOB_UNKNOWN_SIZE, nil, accessio.ErrClosed - } - path := a.DigestPath(digest) - if ok, err := vfs.FileExists(a.base.GetFileSystem(), path); ok { - return blobaccess.BLOB_UNKNOWN_SIZE, file.DataAccess(a.base.GetFileSystem(), path), nil - } else { - if err != nil { - return blobaccess.BLOB_UNKNOWN_SIZE, nil, err - } - return blobaccess.BLOB_UNKNOWN_SIZE, nil, blobaccess.ErrBlobNotFound(digest) - } -} - -func (a *FileSystemBlobAccess) GetBlobDataByName(name string) (blobaccess.DataAccess, error) { - if a.IsClosed() { - return nil, accessio.ErrClosed - } - - path := a.BlobPath(name) - if ok, err := vfs.IsDir(a.base.GetFileSystem(), path); ok { - tempfile, err := file.NewTempFile(os.TempDir(), "COMPARCH") - if err != nil { - return nil, err - } - err = tarutils.PackFsIntoTar(a.base.GetFileSystem(), path, tempfile.Writer(), tarutils.TarFileSystemOptions{}) - if err != nil { - return nil, err - } - return tempfile.AsBlob(mime.MIME_TAR), nil - } else { - if err != nil { - return nil, err - } - - if ok, err := vfs.FileExists(a.base.GetFileSystem(), path); ok { - return file.DataAccess(a.base.GetFileSystem(), path), nil - } else { - if err != nil { - return nil, err - } - return nil, blobaccess.ErrBlobNotFound(digest.Digest(name)) - } - } -} - -func (a *FileSystemBlobAccess) AddBlob(blob blobaccess.BlobAccess) error { - if a.base.IsClosed() { - return accessio.ErrClosed - } - - if a.base.IsReadOnly() { - return accessio.ErrReadOnly - } - - path := a.DigestPath(blob.Digest()) - - if ok, err := vfs.FileExists(a.base.GetFileSystem(), path); ok { - return nil - } else if err != nil { - return fmt.Errorf("failed to check if '%s' file exists: %w", path, err) - } - - r, err := blob.Reader() - if err != nil { - return fmt.Errorf("unable to read blob: %w", err) - } - - defer r.Close() - w, err := a.base.GetFileSystem().OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, a.base.GetMode()&0o666) - if err != nil { - return fmt.Errorf("unable to open file '%s': %w", path, err) - } - - _, err = io.Copy(w, r) - if err != nil { - w.Close() - - return fmt.Errorf("unable to copy blob content: %w", err) - } - return w.Close() -} diff --git a/pkg/common/accessobj/format-tgz.go b/pkg/common/accessobj/format-tgz.go deleted file mode 100644 index c5c348ec7..000000000 --- a/pkg/common/accessobj/format-tgz.go +++ /dev/null @@ -1,12 +0,0 @@ -package accessobj - -import ( - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/common/compression" -) - -var FormatTGZ = NewTarHandlerWithCompression(accessio.FormatTGZ, compression.Gzip) - -func init() { - RegisterFormat(FormatTGZ) -} diff --git a/pkg/common/accessobj/format.go b/pkg/common/accessobj/format.go deleted file mode 100644 index bb9055c8a..000000000 --- a/pkg/common/accessobj/format.go +++ /dev/null @@ -1,174 +0,0 @@ -package accessobj - -import ( - "fmt" - "io" - "sync" - "time" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/common/accessio" -) - -const KIND_FILEFORMAT = accessio.KIND_FILEFORMAT - -const ( - DirMode = 0o755 - FileMode = 0o644 -) - -var ModTime = time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) - -type FileFormat = accessio.FileFormat - -type FormatHandler interface { - accessio.Option - - Format() accessio.FileFormat - - Open(info AccessObjectInfo, acc AccessMode, path string, opts accessio.Options) (*AccessObject, error) - Create(info AccessObjectInfo, path string, opts accessio.Options, mode vfs.FileMode) (*AccessObject, error) - Write(obj *AccessObject, path string, opts accessio.Options, mode vfs.FileMode) error -} - -//////////////////////////////////////////////////////////////////////////////// - -var ( - fileFormats = map[FileFormat]FormatHandler{} - lock sync.RWMutex -) - -func RegisterFormat(f FormatHandler) { - lock.Lock() - defer lock.Unlock() - fileFormats[f.Format()] = f -} - -func GetFormat(name FileFormat) FormatHandler { - lock.RLock() - defer lock.RUnlock() - return fileFormats[name] -} - -func GetFormats() map[accessio.FileFormat]FormatHandler { - lock.RLock() - defer lock.RUnlock() - - m := map[accessio.FileFormat]FormatHandler{} - for k, v := range fileFormats { - m[k] = v - } - return m -} - -//////////////////////////////////////////////////////////////////////////////// - -type Closer interface { - Close(*AccessObject) error -} - -type CloserFunction func(*AccessObject) error - -func (f CloserFunction) Close(obj *AccessObject) error { - return f(obj) -} - -//////////////////////////////////////////////////////////////////////////////// - -type Setup interface { - Setup(vfs.FileSystem) error -} - -type SetupFunction func(vfs.FileSystem) error - -func (f SetupFunction) Setup(fs vfs.FileSystem) error { - return f(fs) -} - -//////////////////////////////////////////////////////////////////////////////// - -type StandardReaderHandler interface { - Write(obj *AccessObject, path string, opts accessio.Options, mode vfs.FileMode) error - NewFromReader(info AccessObjectInfo, acc AccessMode, in io.Reader, opts accessio.Options, closer Closer) (*AccessObject, error) -} - -func DefaultOpenOptsFileHandling(kind string, info AccessObjectInfo, acc AccessMode, path string, opts accessio.Options, handler StandardReaderHandler) (*AccessObject, error) { - if err := opts.ValidForPath(path); err != nil { - return nil, err - } - var file vfs.File - var err error - var closer Closer - - reader := opts.GetReader() - switch { - case reader != nil: - case opts.GetFile() == nil: - // we expect that the path point to a tar - file, err = opts.GetPathFileSystem().Open(path) - if err != nil { - return nil, fmt.Errorf("unable to open %s from %s: %w", kind, path, err) - } - defer file.Close() - default: - file = opts.GetFile() - } - if file != nil { - reader = file - fi, err := file.Stat() - if err != nil { - return nil, err - } - closer = CloserFunction(func(obj *AccessObject) error { return handler.Write(obj, path, opts, fi.Mode()) }) - } - return handler.NewFromReader(info, acc, reader, opts, closer) -} - -func DefaultCreateOptsFileHandling(kind string, info AccessObjectInfo, path string, opts accessio.Options, mode vfs.FileMode, handler StandardReaderHandler) (*AccessObject, error) { - if err := opts.ValidForPath(path); err != nil { - return nil, err - } - if opts.GetReader() != nil { - return nil, errors.ErrNotSupported("reader option not supported") - } - if opts.GetFile() == nil { - ok, err := vfs.Exists(opts.GetPathFileSystem(), path) - if err != nil { - return nil, err - } - if ok { - return nil, vfs.ErrExist - } - } - - return NewAccessObject(info, ACC_CREATE, opts.GetRepresentation(), nil, CloserFunction(func(obj *AccessObject) error { return handler.Write(obj, path, opts, mode) }), DirMode) -} - -//////////////////////////////////////////////////////////////////////////////// - -// MapType maps a given type name to an effective type and a format. -func MapType(hint string, efftyp string, deffmt accessio.FileFormat, useFormats bool, alt ...string) (string, accessio.FileFormat) { - typ := accessio.TypeForTypeSpec(hint) - f := accessio.FileFormatForTypeSpec(hint) - if f != "" { - deffmt = f - } - if typ == efftyp { - return efftyp, deffmt - } - for _, t := range alt { - if typ == t { - return efftyp, deffmt - } - } - if useFormats { - for _, f := range accessio.GetFormats() { - if hint == f { - return efftyp, accessio.FileFormat(f) - } - } - } - return "", "" -} diff --git a/pkg/common/accessobj/utils.go b/pkg/common/accessobj/utils.go deleted file mode 100644 index 308c46c0c..000000000 --- a/pkg/common/accessobj/utils.go +++ /dev/null @@ -1,58 +0,0 @@ -package accessobj - -import ( - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/vfs/pkg/osfs" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/common/accessio" -) - -type FilesystemSetup func(fs vfs.FileSystem, mode vfs.FileMode) error - -// InternalRepresentationFilesystem defaults a filesystem to temp filesystem and adapts. -func InternalRepresentationFilesystem(acc AccessMode, fs vfs.FileSystem, setup FilesystemSetup, mode vfs.FileMode) (bool, vfs.FileSystem, error) { - var err error - - tmp := false - if fs == nil { - fs, err = osfs.NewTempFileSystem() - if err != nil { - return false, nil, err - } - tmp = true - } - if !acc.IsReadonly() && setup != nil { - err = setup(fs, mode) - if err != nil { - return false, nil, err - } - } - return tmp, fs, err -} - -func HandleAccessMode(acc AccessMode, path string, opts accessio.Options, olist ...accessio.Option) (accessio.Options, bool, error) { - ok := true - o, err := accessio.AccessOptions(opts, olist...) - if err != nil { - return nil, false, err - } - if o.GetFile() == nil && o.GetReader() == nil { - ok, err = vfs.Exists(o.GetPathFileSystem(), path) - if err != nil { - return o, false, err - } - } - if !ok { - if !acc.IsCreate() { - return o, false, errors.ErrNotFoundWrap(vfs.ErrNotExist, "file", path) - } - if o.GetFileFormat() == nil { - o.SetFileFormat(accessio.FormatDirectory) - } - return o, true, nil - } - - err = o.DefaultForPath(path) - return o, false, err -} diff --git a/pkg/common/types.go b/pkg/common/types.go deleted file mode 100644 index 170a65f7e..000000000 --- a/pkg/common/types.go +++ /dev/null @@ -1,86 +0,0 @@ -package common - -import ( - "encoding/json" - "fmt" - "strings" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/semverutils" -) - -// VersionedElement describes an element that has a name and a version. -type VersionedElement interface { - // GetName gets the name of the element - GetName() string - // GetVersion gets the version of the element - GetVersion() string -} - -type NameVersion struct { - name string - version string -} - -var ( - _ json.Marshaler = (*NameVersion)(nil) - _ VersionedElement = (*NameVersion)(nil) -) - -func NewNameVersion(name, version string) NameVersion { - return NameVersion{name, version} -} - -func VersionedElementKey(v VersionedElement) NameVersion { - if k, ok := v.(NameVersion); ok { - return k - } - return NameVersion{v.GetName(), v.GetVersion()} -} - -func (n NameVersion) GetName() string { - return n.name -} - -func (n NameVersion) GetVersion() string { - return n.version -} - -func (n NameVersion) MarshalJSON() ([]byte, error) { - return json.Marshal(fmt.Sprintf("%s:%s", n.GetName(), n.GetVersion())) -} - -func (n NameVersion) Compare(o NameVersion) int { - c := strings.Compare(n.name, o.name) - if c == 0 { - return semverutils.Compare(n.version, o.version) - } - return c -} - -func (n NameVersion) String() string { - if n.version == "" { - return n.name - } - if n.name == "" { - return n.version - } - return n.name + ":" + n.version -} - -func ParseNameVersion(s string) (NameVersion, error) { - a := strings.Split(s, ":") - if len(a) != 2 { - return NameVersion{}, errors.ErrInvalid("name:version", s) - } - return NewNameVersion(strings.TrimSpace(a[0]), strings.TrimSpace(a[1])), nil -} - -func CompareNameVersion(a, b NameVersion) int { - d := strings.Compare(a.name, b.name) - if d == 0 { - d = strings.Compare(a.version, b.version) - } - return d -} diff --git a/pkg/common/walk.go b/pkg/common/walk.go deleted file mode 100644 index 9707c88e5..000000000 --- a/pkg/common/walk.go +++ /dev/null @@ -1,52 +0,0 @@ -package common - -import ( - "github.com/mandelsoft/logging" - - ocmlog "github.com/open-component-model/ocm/pkg/logging" - "github.com/open-component-model/ocm/pkg/utils" -) - -type NameVersionInfo[T any] map[NameVersion]T - -func (s NameVersionInfo[T]) Add(nv NameVersion, data ...T) bool { - if _, ok := s[nv]; !ok { - s[nv] = utils.Optional(data...) - return true - } - return false -} - -func (s NameVersionInfo[T]) Contains(nv NameVersion) bool { - _, ok := s[nv] - return ok -} - -type WalkingState[T any, C any] struct { - LogCtx logging.Context - Logger logging.Logger - Context C - Closure NameVersionInfo[T] - History History -} - -func NewWalkingState[T any, C any](ctx C, lctx ...logging.Context) WalkingState[T, C] { - logctx := utils.OptionalDefaulted[logging.Context](ocmlog.Context(), lctx...) - return WalkingState[T, C]{Context: ctx, Closure: NameVersionInfo[T]{}, LogCtx: logctx, Logger: logctx.Logger()} -} - -func (s *WalkingState[T, C]) Add(kind string, nv NameVersion) (bool, error) { - if err := s.History.Add(kind, nv); err != nil { - return false, err - } - return s.Closure.Add(nv), nil -} - -func (s *WalkingState[T, C]) Contains(nv NameVersion) bool { - _, ok := s.Closure[nv] - return ok -} - -func (s *WalkingState[T, C]) Get(nv NameVersion) T { - return s.Closure[nv] -} diff --git a/pkg/contexts/clictx/builder.go b/pkg/contexts/clictx/builder.go deleted file mode 100644 index c1f5cc7a5..000000000 --- a/pkg/contexts/clictx/builder.go +++ /dev/null @@ -1,44 +0,0 @@ -package clictx - -import ( - "context" - "io" - - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/contexts/clictx/internal" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/ocm" -) - -func WithContext(ctx context.Context) internal.Builder { - return internal.Builder{}.WithContext(ctx) -} - -func WithSharedAttributes(ctx datacontext.AttributesContext) internal.Builder { - return internal.Builder{}.WithSharedAttributes(ctx) -} - -func WithOCM(ctx ocm.Context) internal.Builder { - return internal.Builder{}.WithOCM(ctx) -} - -func WithFileSystem(fs vfs.FileSystem) internal.Builder { - return internal.Builder{}.WithFileSystem(fs) -} - -func WithOutput(w io.Writer) internal.Builder { - return internal.Builder{}.WithOutput(w) -} - -func WithErrorOutput(w io.Writer) internal.Builder { - return internal.Builder{}.WithErrorOutput(w) -} - -func WithInput(r io.Reader) internal.Builder { - return internal.Builder{}.WithInput(r) -} - -func New(mode ...datacontext.BuilderMode) internal.Context { - return internal.Builder{}.New(mode...) -} diff --git a/pkg/contexts/clictx/config/config_test.go b/pkg/contexts/clictx/config/config_test.go deleted file mode 100644 index b005a7059..000000000 --- a/pkg/contexts/clictx/config/config_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package config_test - -import ( - "encoding/json" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/clictx" - "github.com/open-component-model/ocm/pkg/contexts/clictx/config" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ocireg" - ocmocireg "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ocireg" -) - -var DefaultContext = clictx.New() - -func normalize(i interface{}) ([]byte, error) { - data, err := json.Marshal(i) - if err != nil { - return nil, err - } - var generic map[string]interface{} - err = json.Unmarshal(data, &generic) - if err != nil { - return nil, err - } - return json.Marshal(generic) -} - -var _ = Describe("command config", func() { - ocispec := ocireg.NewRepositorySpec("ghcr.io") - - ocidata, err := normalize(ocispec) - Expect(err).To(Succeed()) - - ocmspec := ocmocireg.NewRepositorySpec("gcr.io", nil) - ocmdata, err := normalize(ocmspec) - Expect(err).To(Succeed()) - - specdata := "{\"ociRepositories\":{\"oci\":" + string(ocidata) + "},\"ocmRepositories\":{\"ocm\":" + string(ocmdata) + "},\"type\":\"" + config.OCMCmdConfigType + "\"}" - - Context("serialize", func() { - It("serializes config", func() { - cfg := config.New() - err := cfg.AddOCIRepository("oci", ocispec) - Expect(err).To(Succeed()) - err = cfg.AddOCMRepository("ocm", ocmspec) - Expect(err).To(Succeed()) - - data, err := normalize(cfg) - - Expect(err).To(Succeed()) - Expect(data).To(Equal([]byte(specdata))) - - cfg2 := config.New() - err = json.Unmarshal(data, cfg2) - Expect(err).To(Succeed()) - Expect(cfg2).To(Equal(cfg)) - }) - }) -}) diff --git a/pkg/contexts/clictx/config/type.go b/pkg/contexts/clictx/config/type.go deleted file mode 100644 index ec39f96f8..000000000 --- a/pkg/contexts/clictx/config/type.go +++ /dev/null @@ -1,100 +0,0 @@ -package config - -import ( - "fmt" - - "github.com/open-component-model/ocm/pkg/contexts/clictx/internal" - "github.com/open-component-model/ocm/pkg/contexts/config" - "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - ocicpi "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - ocmcpi "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - OCMCmdConfigType = "ocm.cmd" + cpi.OCM_CONFIG_TYPE_SUFFIX - OCMCmdConfigTypeV1 = OCMCmdConfigType + runtime.VersionSeparator + "v1" -) - -func init() { - cpi.RegisterConfigType(cpi.NewConfigType[*Config](OCMCmdConfigType, usage)) - cpi.RegisterConfigType(cpi.NewConfigType[*Config](OCMCmdConfigTypeV1, usage)) -} - -// Config describes a memory based repository interface. -type Config struct { - runtime.ObjectVersionedType `json:",inline"` - OCMRepositories map[string]*ocmcpi.GenericRepositorySpec `json:"ocmRepositories,omitempty"` - OCIRepositories map[string]*ocicpi.GenericRepositorySpec `json:"ociRepositories,omitempty"` -} - -// New creates a new memory ConfigSpec. -func New() *Config { - return &Config{ - ObjectVersionedType: runtime.NewVersionedTypedObject(OCMCmdConfigType), - } -} - -func (a *Config) GetType() string { - return OCMCmdConfigType -} - -func (a *Config) AddOCIRepository(name string, spec ocicpi.RepositorySpec) error { - g, err := ocicpi.ToGenericRepositorySpec(spec) - if err != nil { - return fmt.Errorf("unable to convert oci repository spec to generic spec: %w", err) - } - - if a.OCIRepositories == nil { - a.OCIRepositories = map[string]*ocicpi.GenericRepositorySpec{} - } - - a.OCIRepositories[name] = g - - return nil -} - -func (a *Config) AddOCMRepository(name string, spec ocmcpi.RepositorySpec) error { - g, err := ocmcpi.ToGenericRepositorySpec(spec) - if err != nil { - return fmt.Errorf("unable to convert ocm repository spec to generic spec: %w", err) - } - - if a.OCMRepositories == nil { - a.OCMRepositories = map[string]*ocmcpi.GenericRepositorySpec{} - } - - a.OCMRepositories[name] = g - - return nil -} - -func (a *Config) ApplyTo(ctx config.Context, target interface{}) error { - t, ok := target.(internal.Context) - if !ok { - return config.ErrNoContext(OCMCmdConfigType) - } - for n, s := range a.OCIRepositories { - t.OCI().Context().SetAlias(n, s) - } - for n, s := range a.OCMRepositories { - t.OCM().Context().SetAlias(n, s) - } - return nil -} - -const usage = ` -The config type ` + OCMCmdConfigType + ` can be used to -configure predefined aliases for dedicated OCM repositories and -OCI registries. - -
-   type: ` + OCMCmdConfigType + `
-   ocmRepositories:
-       <name>: <specification of OCM repository>
-   ...
-   ociRepositories:
-       <name>: <specification of OCI registry>
-   ...
-
-` diff --git a/pkg/contexts/clictx/interface.go b/pkg/contexts/clictx/interface.go deleted file mode 100644 index 694a825c2..000000000 --- a/pkg/contexts/clictx/interface.go +++ /dev/null @@ -1,15 +0,0 @@ -package clictx - -import ( - "github.com/open-component-model/ocm/pkg/contexts/clictx/internal" -) - -type ( - Context = internal.Context - OCI = internal.OCI - OCM = internal.OCM -) - -func DefaultContext() Context { - return internal.DefaultContext -} diff --git a/pkg/contexts/clictx/internal/builder.go b/pkg/contexts/clictx/internal/builder.go deleted file mode 100644 index d065c7216..000000000 --- a/pkg/contexts/clictx/internal/builder.go +++ /dev/null @@ -1,84 +0,0 @@ -package internal - -import ( - "context" - "io" - - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/out" -) - -type Builder struct { - ctx context.Context - shared datacontext.AttributesContext - ocm ocm.Context - out out.Context - filesystem vfs.FileSystem -} - -func (b *Builder) getContext() context.Context { - if b.ctx == nil { - return context.Background() - } - return b.ctx -} - -func (b Builder) WithContext(ctx context.Context) Builder { - b.ctx = ctx - return b -} - -func (b Builder) WithFileSystem(fs vfs.FileSystem) Builder { - b.filesystem = fs - return b -} - -func (b Builder) WithSharedAttributes(ctx datacontext.AttributesContext) Builder { - b.shared = ctx - return b -} - -func (b Builder) WithOCM(ctx ocm.Context) Builder { - b.ocm = ctx - return b -} - -func (b Builder) WithOutput(w io.Writer) Builder { - b.out = out.WithOutput(b.out, w) - return b -} - -func (b Builder) WithErrorOutput(w io.Writer) Builder { - b.out = out.WithErrorOutput(b.out, w) - return b -} - -func (b Builder) WithInput(r io.Reader) Builder { - b.out = out.WithInput(b.out, r) - return b -} - -func (b Builder) Bound() (Context, context.Context) { - c := b.New() - return c, context.WithValue(b.getContext(), key, c) -} - -func (b Builder) New(m ...datacontext.BuilderMode) Context { - mode := datacontext.Mode(m...) - ctx := b.getContext() - - if b.ocm == nil { - var ok bool - b.ocm, ok = ocm.DefinedForContext(ctx) - if !ok && mode != datacontext.MODE_SHARED { - b.ocm = ocm.New(mode) - } - } - if b.shared == nil { - b.shared = b.ocm.AttributesContext() - } - return datacontext.SetupContext(mode, newContext(b.shared, b.ocm, out.NewFor(b.out), b.filesystem, b.shared)) -} diff --git a/pkg/contexts/clictx/internal/context.go b/pkg/contexts/clictx/internal/context.go deleted file mode 100644 index f0230b7a0..000000000 --- a/pkg/contexts/clictx/internal/context.go +++ /dev/null @@ -1,313 +0,0 @@ -package internal - -import ( - "context" - "io" - "reflect" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/general" - "github.com/mandelsoft/vfs/pkg/vfs" - - "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/config" - cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "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/vfsattr" - "github.com/open-component-model/ocm/pkg/contexts/oci" - ctfoci "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - ctfocm "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/out" -) - -const CONTEXT_TYPE = "ocm.cmd" + datacontext.OCM_CONTEXT_SUFFIX - -type OCI interface { - Context() oci.Context - OpenCTF(path string) (oci.Repository, error) -} - -type OCM interface { - Context() ocm.Context - OpenCTF(path string) (ocm.Repository, error) -} - -type FileSystem struct { - vfs.FileSystem -} - -func (f *FileSystem) ApplyOption(options accessio.Options) error { - options.SetPathFileSystem(f.FileSystem) - return nil -} - -type ContextProvider interface { - CLIContext() Context -} - -type Context interface { - datacontext.Context - ContextProvider - datacontext.ContextProvider - config.ContextProvider - credentials.ContextProvider - oci.ContextProvider - ocm.ContextProvider - - FileSystem() *FileSystem - - OCI() OCI - OCM() OCM - - ApplyOption(options accessio.Options) error - - out.Context - WithStdIO(r io.Reader, o io.Writer, e io.Writer) Context -} - -var key = reflect.TypeOf(_context{}) - -// DefaultContext is the default context initialized by init functions. -var DefaultContext = Builder{}.New(datacontext.MODE_SHARED) - -// ForContext returns the Context to use for context.Context. -// This is either an explicit context or the default context. -// The returned context incorporates the given context. -func ForContext(ctx context.Context) Context { - c, _ := datacontext.ForContextByKey(ctx, key, DefaultContext) - return c.(Context) -} - -func DefinedForContext(ctx context.Context) (Context, bool) { - c, ok := datacontext.ForContextByKey(ctx, key, DefaultContext) - if c != nil { - return c.(Context), ok - } - return nil, ok -} - -//////////////////////////////////////////////////////////////////////////////// - -type _InternalContext = datacontext.InternalContext - -type _context struct { - _InternalContext - updater cfgcpi.Updater - - sharedAttributes datacontext.AttributesContext - - credentials credentials.Context - oci *_oci - ocm *_ocm - - out out.Context -} - -var ( - _ Context = (*_context)(nil) - _ datacontext.ViewCreator[Context] = (*_context)(nil) -) - -// gcWrapper is used as garbage collectable -// wrapper for a context implementation -// to establish a runtime finalizer. -type gcWrapper struct { - datacontext.GCWrapper - *_context -} - -func newView(c *_context, ref ...bool) Context { - if general.Optional(ref...) { - return datacontext.FinalizedContext[gcWrapper](c) - } - return c -} - -func (w *gcWrapper) SetContext(c *_context) { - w._context = c -} - -func newContext(shared datacontext.AttributesContext, ocmctx ocm.Context, outctx out.Context, fs vfs.FileSystem, delegates datacontext.Delegates) Context { - if outctx == nil { - outctx = out.New() - } - if shared == nil { - shared = ocmctx.AttributesContext() - } - c := &_context{ - sharedAttributes: datacontext.PersistentContextRef(shared), - credentials: datacontext.PersistentContextRef(ocmctx.CredentialsContext()), - out: outctx, - } - c._InternalContext = datacontext.NewContextBase(c, CONTEXT_TYPE, key, ocmctx.GetAttributes(), delegates) - c.updater = cfgcpi.NewUpdater(datacontext.PersistentContextRef(ocmctx.CredentialsContext().ConfigContext()), c) - ocmctx = datacontext.PersistentContextRef(ocmctx) - c.oci = newOCI(c, ocmctx) - c.ocm = newOCM(c, ocmctx) - if fs != nil { - vfsattr.Set(c.AttributesContext(), fs) - } - return newView(c, true) -} - -func (c *_context) CreateView() Context { - return newView(c, true) -} - -func (c *_context) CLIContext() Context { - return newView(c) -} - -func (c *_context) Update() error { - return c.updater.Update() -} - -func (c *_context) AttributesContext() datacontext.AttributesContext { - return c.sharedAttributes -} - -func (c *_context) ConfigContext() config.Context { - return c.updater.GetContext() -} - -func (c *_context) CredentialsContext() credentials.Context { - return c.credentials -} - -func (c *_context) OCIContext() oci.Context { - return c.oci.Context() -} - -func (c *_context) OCMContext() ocm.Context { - return c.ocm.Context() -} - -func (c *_context) FileSystem() *FileSystem { - return &FileSystem{vfsattr.Get(c.CLIContext())} -} - -func (c *_context) OCI() OCI { - return c.oci -} - -func (c *_context) OCM() OCM { - return c.ocm -} - -func (c *_context) ApplyOption(options accessio.Options) error { - options.SetPathFileSystem(c.FileSystem()) - return nil -} - -func (c *_context) StdOut() io.Writer { - return c.out.StdOut() -} - -func (c *_context) StdErr() io.Writer { - return c.out.StdErr() -} - -func (c *_context) StdIn() io.Reader { - return c.out.StdIn() -} - -func (c *_context) WithStdIO(r io.Reader, o io.Writer, e io.Writer) Context { - return &_view{ - Context: c.CLIContext(), - out: out.NewFor(out.WithStdIO(c.out, r, o, e)), - } -} - -//////////////////////////////////////////////////////////////////////////////// - -type _view struct { - Context - out out.Context -} - -func (c *_view) StdOut() io.Writer { - return c.out.StdOut() -} - -func (c *_view) StdErr() io.Writer { - return c.out.StdErr() -} - -func (c *_view) StdIn() io.Reader { - return c.out.StdIn() -} - -func (c *_view) WithStdIO(r io.Reader, o io.Writer, e io.Writer) Context { - return &_view{ - Context: c.CLIContext(), - out: out.NewFor(out.WithStdIO(c.out, r, o, e)), - } -} - -//////////////////////////////////////////////////////////////////////////////// -// the coding for _oci and _ocm is identical except the package used: -// _oci uses oci and ctfoci -// _ocm uses ocm and ctfocm - -type _oci struct { - cli *_context - ctx oci.Context - repos map[string]oci.RepositorySpec -} - -func newOCI(ctx *_context, ocmctx ocm.Context) *_oci { - return &_oci{ - cli: ctx, - ctx: ocmctx.OCIContext(), - repos: map[string]oci.RepositorySpec{}, - } -} - -func (c *_oci) Context() oci.Context { - return c.ctx -} - -func (c *_oci) OpenCTF(path string) (oci.Repository, error) { - ok, err := vfs.Exists(c.cli.FileSystem(), path) - if err != nil { - return nil, err - } - if !ok { - return nil, errors.ErrNotFound("file", path) - } - return ctfoci.Open(c.ctx, accessobj.ACC_WRITABLE, path, 0, accessio.PathFileSystem(c.cli.FileSystem())) -} - -//////////////////////////////////////////////////////////////////////////////// - -type _ocm struct { - cli *_context - ctx ocm.Context - repos map[string]ocm.RepositorySpec -} - -func newOCM(ctx *_context, ocmctx ocm.Context) *_ocm { - return &_ocm{ - cli: ctx, - ctx: ocmctx, - repos: map[string]ocm.RepositorySpec{}, - } -} - -func (c *_ocm) Context() ocm.Context { - return c.ctx -} - -func (c *_ocm) OpenCTF(path string) (ocm.Repository, error) { - ok, err := vfs.Exists(c.cli.FileSystem(), path) - if err != nil { - return nil, err - } - if !ok { - return nil, errors.ErrNotFound("file", path) - } - return ctfocm.Open(c.ctx, accessobj.ACC_WRITABLE, path, 0, c.cli.FileSystem()) -} diff --git a/pkg/contexts/config/builder.go b/pkg/contexts/config/builder.go deleted file mode 100644 index fd618ae8d..000000000 --- a/pkg/contexts/config/builder.go +++ /dev/null @@ -1,24 +0,0 @@ -package config - -import ( - "context" - - "github.com/open-component-model/ocm/pkg/contexts/config/internal" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" -) - -func WithContext(ctx context.Context) internal.Builder { - return internal.Builder{}.WithContext(ctx) -} - -func WithSharedAttributes(ctx datacontext.AttributesContext) internal.Builder { - return internal.Builder{}.WithSharedAttributes(ctx) -} - -func WithConfigTypeScheme(scheme ConfigTypeScheme) internal.Builder { - return internal.Builder{}.WithConfigTypeScheme(scheme) -} - -func New(mode ...datacontext.BuilderMode) Context { - return internal.Builder{}.New(mode...) -} diff --git a/pkg/contexts/config/config/context_test.go b/pkg/contexts/config/config/context_test.go deleted file mode 100644 index 8f24fea51..000000000 --- a/pkg/contexts/config/config/context_test.go +++ /dev/null @@ -1,178 +0,0 @@ -package config_test - -import ( - "os" - "reflect" - "runtime" - "time" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/mandelsoft/goutils/general" - "sigs.k8s.io/yaml" - - "github.com/open-component-model/ocm/pkg/contexts/config" - local "github.com/open-component-model/ocm/pkg/contexts/config/config" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" -) - -func CheckRefs(ctx config.Context, n int) { - runtime.GC() - time.Sleep(time.Second) - Expect(datacontext.GetContextRefCount(ctx)).To(Equal(n)) // all temp refs have been finalized -} - -var _ = Describe("generic config handling", func() { - var scheme config.ConfigTypeScheme - var cfgctx config.Context - - testdataconfig, _ := os.ReadFile("testdata/config.yaml") - testdatajson, _ := yaml.YAMLToJSON(testdataconfig) - - nesteddataconfig, _ := os.ReadFile("testdata/nested.yaml") - - _ = testdatajson - - BeforeEach(func() { - scheme = config.NewConfigTypeScheme() - scheme.AddKnownTypes(config.DefaultContext().ConfigTypes()) - cfgctx = config.WithConfigTypeScheme(scheme).New() - }) - - It("can deserialize config", func() { - result, err := cfgctx.GetConfigForData(testdataconfig, nil) - Expect(err).To(Succeed()) - Expect(config.IsGeneric(result)).To(BeFalse()) - Expect(reflect.TypeOf(result).String()).To(Equal("*config.Config")) - - CheckRefs(cfgctx, 1) - }) - - It("it applies to existing context", func() { - RegisterAt(scheme) - d := newDummy(cfgctx) - - cfg, err := cfgctx.GetConfigForData(testdataconfig, nil) - Expect(err).To(Succeed()) - - err = cfgctx.ApplyConfig(cfg, "testconfig") - Expect(err).To(Succeed()) - gen, cfgs := cfgctx.GetConfig(config.AllGenerations, nil) - Expect(gen).To(Equal(int64(3))) - Expect(len(cfgs)).To(Equal(3)) - - Expect(d.getApplied()).To(Equal([]*Config{NewConfig("alice", ""), NewConfig("", "bob")})) - - CheckRefs(cfgctx, 1) - }) - - It("it applies nested to existing context", func() { - RegisterAt(scheme) - d := newDummy(cfgctx) - - cfg, err := cfgctx.GetConfigForData(nesteddataconfig, nil) - Expect(err).To(Succeed()) - - err = cfgctx.ApplyConfig(cfg, "testconfig") - Expect(err).To(Succeed()) - gen, cfgs := cfgctx.GetConfig(config.AllGenerations, nil) - Expect(gen).To(Equal(int64(4))) - Expect(len(cfgs)).To(Equal(4)) - - Expect(d.getApplied()).To(Equal([]*Config{NewConfig("alice", ""), NewConfig("", "bob")})) - - CheckRefs(cfgctx, 1) - }) - - It("it applies unknown type to existing context", func() { - cfg, err := cfgctx.GetConfigForData(testdataconfig, nil) - Expect(err).To(Succeed()) - - err = cfgctx.ApplyConfig(cfg, "testconfig") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(StringEqualWithContext("testconfig: config apply errors: {config entry 0--testconfig: config type \"Dummy\" is unknown, config entry 1--testconfig: config type \"Dummy\" is unknown}")) - gen, cfgs := cfgctx.GetConfig(config.AllGenerations, nil) - Expect(gen).To(Equal(int64(3))) - Expect(len(cfgs)).To(Equal(3)) - - RegisterAt(scheme) - d := newDummy(cfgctx) - Expect(d.getApplied()).To(Equal([]*Config{NewConfig("alice", ""), NewConfig("", "bob")})) - - CheckRefs(cfgctx, 1) - }) - - It("it applies composed config to existing context", func() { - RegisterAt(scheme) - d := newDummy(cfgctx) - - cfg := local.New() - - nested := NewConfig("alice", "") - cfg.AddConfig(nested) - nested = NewConfig("", "bob") - cfg.AddConfig(nested) - - err := cfgctx.ApplyConfig(cfg, "testconfig") - Expect(err).To(Succeed()) - gen, cfgs := cfgctx.GetConfig(config.AllGenerations, nil) - Expect(gen).To(Equal(int64(3))) - Expect(len(cfgs)).To(Equal(3)) - - Expect(d.getApplied()).To(Equal([]*Config{NewConfig("alice", ""), NewConfig("", "bob")})) - - CheckRefs(cfgctx, 1) - }) - - It("it applies composed config set to existing context", func() { - RegisterAt(scheme) - d := newDummy(cfgctx) - - cfg := local.New() - - nested := NewConfig("alice", "") - cfg.AddConfigToSet("test", nested) - nested = NewConfig("", "bob") - cfg.AddConfig(nested) - - err := cfgctx.ApplyConfig(cfg, "testconfig") - Expect(err).To(Succeed()) - gen, cfgs := cfgctx.GetConfig(config.AllGenerations, nil) - Expect(gen).To(Equal(int64(2))) - Expect(len(cfgs)).To(Equal(2)) - - Expect(d.getApplied()).To(Equal([]*Config{NewConfig("", "bob")})) - - err = cfgctx.ApplyConfigSet("test") - Expect(err).To(Succeed()) - - gen, cfgs = cfgctx.GetConfig(config.AllGenerations, nil) - Expect(gen).To(Equal(int64(3))) - Expect(len(cfgs)).To(Equal(3)) - Expect(d.getApplied()).To(Equal([]*Config{NewConfig("", "bob"), NewConfig("alice", "")})) - - CheckRefs(cfgctx, 1) - }) - - It("it applies compig to storing target", func() { - RegisterAt(scheme) - d := newDummy(cfgctx) - - cfg := NewConfig("alice", "") - - err := cfgctx.ApplyConfig(cfg, "testconfig") - Expect(err).To(Succeed()) - - Expect(d.getApplied()).To(Equal([]*Config{NewConfig("alice", "")})) - - target := dummyTarget{} - MustBeSuccessful(cfgctx.ApplyTo(0, &target)) - Expect(target.used).NotTo(BeNil()) - Expect(target.used.GetId()).To(Equal(cfgctx.GetId())) - - CheckRefs(cfgctx, general.Conditional(datacontext.MULTI_REF, 2, 1)) // config context stored in target with separate ref - target.used.GetId() - }) -}) diff --git a/pkg/contexts/config/config/dummy_test.go b/pkg/contexts/config/config/dummy_test.go deleted file mode 100644 index 3c8c2bae3..000000000 --- a/pkg/contexts/config/config/dummy_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package config_test - -import ( - "github.com/open-component-model/ocm/pkg/contexts/config" - "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - DummyType = "Dummy" - DummyTypeV1 = DummyType + "/v1" -) - -func RegisterAt(reg cpi.ConfigTypeScheme) { - reg.Register(cpi.NewConfigType[*Config](DummyType)) - reg.Register(cpi.NewConfigType[*Config](DummyTypeV1)) -} - -// Config describes a a dummy config -type Config struct { - runtime.ObjectVersionedType `json:",inline"` - Alice string `json:"alice,omitempty"` - Bob string `json:"bob,omitempty"` -} - -// NewConfig creates a new memory ConfigSpec -func NewConfig(a, b string) *Config { - return &Config{ - ObjectVersionedType: runtime.NewVersionedTypedObject(DummyType), - Alice: a, - Bob: b, - } -} - -func (a *Config) GetType() string { - return DummyType -} - -func (a *Config) Info() string { - return "dummy config" -} - -func (a *Config) ApplyTo(ctx config.Context, target interface{}) error { - d, ok := target.(*dummyContext) - if ok { - d.applied = append(d.applied, a) - return nil - } - c, ok := target.(*dummyTarget) - if ok { - c.used = ctx - return nil - } - return cpi.ErrNoContext(DummyType) -} - -//////////////////////////////////////////////////////////////////////////////// - -type dummyTarget struct { - used config.Context -} - -//////////////////////////////////////////////////////////////////////////////// - -func newDummy(ctx config.Context) *dummyContext { - d := &dummyContext{ - config: ctx, - } - d.update() - return d -} - -type dummyContext struct { - config config.Context - lastGeneration int64 - applied []*Config -} - -func (d *dummyContext) getApplied() []*Config { - d.update() - return d.applied -} - -func (d *dummyContext) update() error { - gen, err := d.config.ApplyTo(d.lastGeneration, d) - d.lastGeneration = gen - return err -} diff --git a/pkg/contexts/config/config/type.go b/pkg/contexts/config/config/type.go deleted file mode 100644 index c5451db45..000000000 --- a/pkg/contexts/config/config/type.go +++ /dev/null @@ -1,97 +0,0 @@ -package config - -import ( - "fmt" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - ConfigType = "generic" + cpi.OCM_CONFIG_TYPE_SUFFIX - ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" -) - -func init() { - cpi.RegisterConfigType(cpi.NewConfigType[*Config](ConfigType, usage)) - cpi.RegisterConfigType(cpi.NewConfigType[*Config](ConfigTypeV1, usage)) -} - -// Config describes a memory based repository interface. -type Config struct { - runtime.ObjectVersionedType `json:",inline"` - cpi.ConfigurationList `json:",inline"` - Sets map[string]cpi.ConfigSet `json:"sets,omitempty"` -} - -// New creates a new memory ConfigSpec. -func New() *Config { - return &Config{ - ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), - ConfigurationList: cpi.ConfigurationList{[]*cpi.GenericConfig{}}, - Sets: map[string]cpi.ConfigSet{}, - } -} - -func (c *Config) AddSet(name, desc string) { - set := c.Sets[name] - set.Description = desc - c.Sets[name] = set -} - -func (c *Config) AddConfigToSet(name string, cfg cpi.Config) error { - set := c.Sets[name] - err := set.AddConfig(cfg) - if err == nil { - c.Sets[name] = set - } - return err -} - -func (c *Config) GetType() string { - return ConfigType -} - -func (c *Config) ApplyTo(ctx cpi.Context, target interface{}) error { - if cctx, ok := target.(cpi.Context); ok { - for n, s := range c.Sets { - set := s - cctx.AddConfigSet(n, &set) - } - - list := errors.ErrListf("applying generic config list") - for i, cfg := range c.Configurations { - sub := fmt.Sprintf("config entry %d", i) - list.Add(cctx.ApplyConfig(cfg, ctx.WithInfo(sub).Info())) - } - return list.Result() - } - return cpi.ErrNoContext(ConfigType) -} - -const usage = ` -The config type ` + ConfigType + ` can be used to define a list -of arbitrary configuration specifications and named configuration sets: - -
-    type: ` + ConfigType + `
-    configurations:
-      - type: <any config type>
-        ...
-      ...
-    sets:
-       standard:
-          description: my selectable standard config
-          configurations:
-            - type: ...
-              ...
-            ...
-
- -Configurations are directly applied. Configuration sets are -just stored in the configuration context and can be applied -on-demand. On the CLI, this can be done using the main command option ---config-set <name>. -` diff --git a/pkg/contexts/config/config/utils.go b/pkg/contexts/config/config/utils.go deleted file mode 100644 index 126ebdb7f..000000000 --- a/pkg/contexts/config/config/utils.go +++ /dev/null @@ -1,60 +0,0 @@ -package config - -import ( - "github.com/open-component-model/ocm/pkg/contexts/config/cpi" -) - -type Aggregator struct { - cfg cpi.Config - aggr *Config - optimized bool -} - -func NewAggregator(optimized bool, cfgs ...cpi.Config) (*Aggregator, error) { - a := &Aggregator{optimized: optimized} - for _, c := range cfgs { - err := a.AddConfig(c) - if err != nil { - return nil, err - } - } - return a, nil -} - -func (a *Aggregator) Get() cpi.Config { - return a.cfg -} - -func (a *Aggregator) AddConfig(cfg cpi.Config) error { - if a.cfg == nil { - a.cfg = cfg - if aggr, ok := cfg.(*Config); ok && a.optimized { - a.aggr = aggr - } - } else { - if a.aggr == nil { - a.aggr = New() - if m, ok := a.cfg.(*Config); ok { - // transfer initial config aggregation - for _, c := range m.Configurations { - err := a.aggr.AddConfig(c) - if err != nil { - return err - } - } - } else { - // add initial config to new aggregation - err := a.aggr.AddConfig(a.cfg) - if err != nil { - return err - } - } - a.cfg = a.aggr - } - err := a.aggr.AddConfig(cfg) - if err != nil { - return err - } - } - return nil -} diff --git a/pkg/contexts/config/configutils/configure.go b/pkg/contexts/config/configutils/configure.go deleted file mode 100644 index 29fb04d3e..000000000 --- a/pkg/contexts/config/configutils/configure.go +++ /dev/null @@ -1,20 +0,0 @@ -package configutils - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/datacontext/config" - - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/contexts/config" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" -) - -func Configure(path string, fss ...vfs.FileSystem) error { - _, err := utils.Configure(config.DefaultContext(), path, fss...) - return err -} - -func ConfigureContext(ctxp config.ContextProvider, path string, fss ...vfs.FileSystem) error { - _, err := utils.Configure(ctxp, path, fss...) - return err -} diff --git a/pkg/contexts/config/context_test.go b/pkg/contexts/config/context_test.go deleted file mode 100644 index 19f75f469..000000000 --- a/pkg/contexts/config/context_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package config_test - -import ( - "encoding/json" - "reflect" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/config" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" -) - -var _ = Describe("config handling", func() { - var scheme config.ConfigTypeScheme - var cfgctx config.Context - - BeforeEach(func() { - scheme = config.NewConfigTypeScheme() - cfgctx = config.WithConfigTypeScheme(scheme).New() - Expect(cfgctx.AttributesContext().GetId()).NotTo(BeIdenticalTo(datacontext.DefaultContext.GetId())) - }) - - It("can deserialize unknown", func() { - cfg := NewConfig("a", "b") - data, err := json.Marshal(cfg) - Expect(err).To(Succeed()) - - result, err := cfgctx.GetConfigForData(data, nil) - Expect(err).To(Succeed()) - Expect(config.IsGeneric(result)).To(BeTrue()) - }) - - It("can deserialize known", func() { - RegisterAt(scheme) - - cfg := NewConfig("a", "b") - data, err := json.Marshal(cfg) - Expect(err).To(Succeed()) - - result, err := cfgctx.GetConfigForData(data, nil) - Expect(err).To(Succeed()) - Expect(config.IsGeneric(result)).To(BeFalse()) - Expect(reflect.TypeOf(result).String()).To(Equal("*config_test.Config")) - }) - - It("it applies to existing context", func() { - RegisterAt(scheme) - - d := newDummy(cfgctx) - - cfg := NewConfig("a", "b") - - err := cfgctx.ApplyConfig(cfg, "test") - - Expect(err).To(Succeed()) - - Expect(d.getApplied()).To(Equal([]*Config{cfg})) - }) - - It("it applies to new context", func() { - RegisterAt(scheme) - - cfg := NewConfig("a", "b") - - err := cfgctx.ApplyConfig(cfg, "test") - Expect(err).To(Succeed()) - - d := newDummy(cfgctx) - Expect(d.applied).To(Equal([]*Config{cfg})) - }) - - It("it applies generic to new context", func() { - cfg := NewConfig("a", "b") - data, err := json.Marshal(cfg) - Expect(err).To(Succeed()) - - gen, err := cfgctx.ApplyData(data, nil, "test") - Expect(err).To(HaveOccurred()) - Expect(errors.IsErrUnknownKind(err, config.KIND_CONFIGTYPE)).To(BeTrue()) - Expect(config.IsGeneric(gen)).To(BeTrue()) - - RegisterAt(scheme) - d := newDummy(cfgctx) - Expect(d.getApplied()).To(Equal([]*Config{cfg})) - }) -}) diff --git a/pkg/contexts/config/cpi/config.go b/pkg/contexts/config/cpi/config.go deleted file mode 100644 index 07c6da453..000000000 --- a/pkg/contexts/config/cpi/config.go +++ /dev/null @@ -1,54 +0,0 @@ -package cpi - -import ( - "strings" - - "github.com/open-component-model/ocm/pkg/contexts/config/internal" - "github.com/open-component-model/ocm/pkg/runtime" -) - -type ConfigTypeVersionScheme = runtime.TypeVersionScheme[Config, ConfigType] - -func NewConfigTypeVersionScheme(kind string) ConfigTypeVersionScheme { - return runtime.NewTypeVersionScheme[Config, ConfigType](kind, internal.NewStrictConfigTypeScheme()) -} - -func RegisterConfigType(rtype ConfigType) { - internal.DefaultConfigTypeScheme.Register(rtype) -} - -func RegisterConfigTypeVersions(s ConfigTypeVersionScheme) { - internal.DefaultConfigTypeScheme.AddKnownTypes(s) -} - -//////////////////////////////////////////////////////////////////////////////// - -type configType struct { - runtime.VersionedTypedObjectType[Config] - usage string -} - -func NewConfigType[I Config](name string, usages ...string) ConfigType { - return &configType{ - VersionedTypedObjectType: runtime.NewVersionedTypedObjectType[Config, I](name), - usage: strings.Join(usages, "\n"), - } -} - -func NewConfigTypeyConverter[I Config, V runtime.TypedObject](name string, converter runtime.Converter[I, V], usages ...string) ConfigType { - return &configType{ - VersionedTypedObjectType: runtime.NewVersionedTypedObjectTypeByConverter[Config, I](name, converter), - usage: strings.Join(usages, "\n"), - } -} - -func NewConfigTypeByFormatVersion(name string, fmt runtime.FormatVersion[Config], usages ...string) ConfigType { - return &configType{ - VersionedTypedObjectType: runtime.NewVersionedTypedObjectTypeByFormatVersion[Config](name, fmt), - usage: strings.Join(usages, "\n"), - } -} - -func (t *configType) Usage() string { - return t.usage -} diff --git a/pkg/contexts/config/cpi/interface.go b/pkg/contexts/config/cpi/interface.go deleted file mode 100644 index b61ab01eb..000000000 --- a/pkg/contexts/config/cpi/interface.go +++ /dev/null @@ -1,77 +0,0 @@ -package cpi - -// This is the Context Provider Interface for credential providers - -import ( - "github.com/open-component-model/ocm/pkg/contexts/config/internal" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const KIND_CONFIGTYPE = internal.KIND_CONFIGTYPE - -const OCM_CONFIG_TYPE_SUFFIX = internal.OCM_CONFIG_TYPE_SUFFIX - -const CONTEXT_TYPE = internal.CONTEXT_TYPE - -type ( - Context = internal.Context - ContextProvider = internal.ContextProvider - Config = internal.Config - ConfigType = internal.ConfigType - ConfigTypeScheme = internal.ConfigTypeScheme - GenericConfig = internal.GenericConfig - - ConfigSet = internal.ConfigSet - ConfigurationList = internal.ConfigurationList - - ConfigApplier = internal.ConfigApplier - ConfigApplierFunction = internal.ConfigApplierFunction -) - -var DefaultContext = internal.DefaultContext - -func FromProvider(p ContextProvider) Context { - return internal.FromProvider(p) -} - -func NewGenericConfig(data []byte, unmarshaler runtime.Unmarshaler) (Config, error) { - return internal.NewGenericConfig(data, unmarshaler) -} - -func ToGenericConfig(c Config) (*GenericConfig, error) { - return internal.ToGenericConfig(c) -} - -func NewConfigTypeScheme() ConfigTypeScheme { - return internal.NewConfigTypeScheme(nil) -} - -func IsGeneric(cfg Config) bool { - return internal.IsGeneric(cfg) -} - -//////////////////////////////////////////////////////////////////////////////// - -type Updater = internal.Updater - -func NewUpdater(ctx ContextProvider, target interface{}) Updater { - return internal.NewUpdater(ctx, target) -} - -func NewUpdaterForFactory[T any](ctx ContextProvider, f func() T) Updater { - return internal.NewUpdaterForFactory(ctx, f) -} - -//////////////////////////////////////////////////////////////////////////////// - -func ErrNoContext(name string) error { - return internal.ErrNoContext(name) -} - -func IsErrNoContext(err error) bool { - return internal.IsErrNoContext(err) -} - -func IsErrConfigNotApplicable(err error) bool { - return internal.IsErrConfigNotApplicable(err) -} diff --git a/pkg/contexts/config/dummy_test.go b/pkg/contexts/config/dummy_test.go deleted file mode 100644 index 72f59e1cd..000000000 --- a/pkg/contexts/config/dummy_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package config_test - -import ( - "github.com/open-component-model/ocm/pkg/contexts/config" - "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - DummyType = "Dummy" - DummyTypeV1 = DummyType + "/v1" -) - -func RegisterAt(reg cpi.ConfigTypeScheme) { - reg.Register(cpi.NewConfigType[*Config](DummyType)) - reg.Register(cpi.NewConfigType[*Config](DummyTypeV1)) -} - -// Config describes a a dummy config -type Config struct { - runtime.ObjectVersionedType `json:",inline"` - Alice string `json:"alice,omitempty"` - Bob string `json:"bob,omitempty"` -} - -// NewConfig creates a new memory Config -func NewConfig(a, b string) *Config { - return &Config{ - ObjectVersionedType: runtime.NewVersionedTypedObject(DummyType), - Alice: a, - Bob: b, - } -} - -func (a *Config) GetType() string { - return DummyType -} - -func (a *Config) ApplyTo(ctx config.Context, target interface{}) error { - d, ok := target.(*dummyContext) - if ok { - d.applied = append(d.applied, a) - return nil - } - return cpi.ErrNoContext(DummyType) -} - -//////////////////////////////////////////////////////////////////////////////// - -func newDummy(ctx config.Context) *dummyContext { - d := &dummyContext{ - config: ctx, - } - d.update() - return d -} - -type dummyContext struct { - config config.Context - lastGeneration int64 - applied []*Config -} - -func (d *dummyContext) getApplied() []*Config { - d.update() - return d.applied -} - -func (d *dummyContext) update() error { - gen, err := d.config.ApplyTo(d.lastGeneration, d) - d.lastGeneration = gen - return err -} diff --git a/pkg/contexts/config/gc_test.go b/pkg/contexts/config/gc_test.go deleted file mode 100644 index 996cb2338..000000000 --- a/pkg/contexts/config/gc_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package config_test - -import ( - "runtime" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - me "github.com/open-component-model/ocm/pkg/contexts/config" - "github.com/open-component-model/ocm/pkg/runtimefinalizer" -) - -var _ = Describe("area test", func() { - It("can be garbage collected", func() { - ctx := me.New() - - r := runtimefinalizer.GetRuntimeFinalizationRecorder(ctx) - Expect(r).NotTo(BeNil()) - - runtime.GC() - time.Sleep(time.Second) - ctx.GetType() - Expect(r.Get()).To(BeNil()) - - ctx = nil - for i := 0; i < 100; i++ { - runtime.GC() - time.Sleep(time.Millisecond) - } - - Expect(r.Get()).To(ContainElement(ContainSubstring(me.CONTEXT_TYPE))) - }) -}) diff --git a/pkg/contexts/config/init.go b/pkg/contexts/config/init.go deleted file mode 100644 index 0e5ec5827..000000000 --- a/pkg/contexts/config/init.go +++ /dev/null @@ -1,6 +0,0 @@ -package config - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/config/config" - _ "github.com/open-component-model/ocm/pkg/contexts/datacontext/config" -) diff --git a/pkg/contexts/config/interface.go b/pkg/contexts/config/interface.go deleted file mode 100644 index e7311f634..000000000 --- a/pkg/contexts/config/interface.go +++ /dev/null @@ -1,76 +0,0 @@ -package config - -import ( - "context" - - "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/contexts/config/internal" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const KIND_CONFIGTYPE = internal.KIND_CONFIGTYPE - -const OCM_CONFIG_TYPE_SUFFIX = internal.OCM_CONFIG_TYPE_SUFFIX - -const CONTEXT_TYPE = internal.CONTEXT_TYPE - -var AllConfigs = internal.AllConfigs - -const AllGenerations = internal.AllGenerations - -type ( - Context = internal.Context - ContextProvider = internal.ContextProvider - Config = internal.Config - ConfigType = internal.ConfigType - ConfigTypeScheme = internal.ConfigTypeScheme - GenericConfig = internal.GenericConfig - ConfigSelector = internal.ConfigSelector - ConfigSelectorFunction = internal.ConfigSelectorFunction - ConfigApplier = internal.ConfigApplier - ConfigApplierFunction = internal.ConfigApplierFunction -) - -func DefaultContext() internal.Context { - return internal.DefaultContext -} - -func ForContext(ctx context.Context) Context { - return internal.FromContext(ctx) -} - -func FromProvider(p ContextProvider) Context { - return internal.FromProvider(p) -} - -func DefinedForContext(ctx context.Context) (Context, bool) { - return internal.DefinedForContext(ctx) -} - -func NewGenericConfig(data []byte, unmarshaler runtime.Unmarshaler) (Config, error) { - return internal.NewGenericConfig(data, unmarshaler) -} - -func ToGenericConfig(c Config) (*GenericConfig, error) { - return internal.ToGenericConfig(c) -} - -func NewConfigTypeScheme() ConfigTypeScheme { - return internal.NewConfigTypeScheme(nil) -} - -func IsGeneric(cfg Config) bool { - return internal.IsGeneric(cfg) -} - -func ErrNoContext(name string) error { - return internal.ErrNoContext(name) -} - -func IsErrNoContext(err error) bool { - return cpi.IsErrNoContext(err) -} - -func IsErrConfigNotApplicable(err error) bool { - return cpi.IsErrConfigNotApplicable(err) -} diff --git a/pkg/contexts/config/internal/builder.go b/pkg/contexts/config/internal/builder.go deleted file mode 100644 index 255fe3912..000000000 --- a/pkg/contexts/config/internal/builder.go +++ /dev/null @@ -1,69 +0,0 @@ -package internal - -import ( - "context" - - "github.com/open-component-model/ocm/pkg/contexts/datacontext" -) - -type Builder struct { - ctx context.Context - shared datacontext.AttributesContext - reposcheme ConfigTypeScheme -} - -func (b *Builder) getContext() context.Context { - if b.ctx == nil { - return context.Background() - } - return b.ctx -} - -func (b Builder) WithContext(ctx context.Context) Builder { - b.ctx = ctx - return b -} - -func (b Builder) WithSharedAttributes(ctx datacontext.AttributesContext) Builder { - b.shared = ctx - return b -} - -func (b Builder) WithConfigTypeScheme(scheme ConfigTypeScheme) Builder { - b.reposcheme = scheme - return b -} - -func (b Builder) Bound() (Context, context.Context) { - c := b.New() - return c, context.WithValue(b.getContext(), key, c) -} - -func (b Builder) New(m ...datacontext.BuilderMode) Context { - mode := datacontext.Mode(m...) - ctx := b.getContext() - - if b.shared == nil { - if mode == datacontext.MODE_SHARED { - b.shared = datacontext.ForContext(ctx) - } else { - b.shared = datacontext.New(nil) - } - } - if b.reposcheme == nil { - switch mode { - case datacontext.MODE_INITIAL: - b.reposcheme = NewConfigTypeScheme(nil) - case datacontext.MODE_CONFIGURED: - b.reposcheme = NewConfigTypeScheme(nil) - b.reposcheme.AddKnownTypes(DefaultConfigTypeScheme) - case datacontext.MODE_EXTENDED: - b.reposcheme = NewConfigTypeScheme(nil, DefaultConfigTypeScheme) - case datacontext.MODE_DEFAULTED: - fallthrough - case datacontext.MODE_SHARED: - b.reposcheme = DefaultConfigTypeScheme - } - } - return datacontext.SetupContext(mode, newContext(b.shared, b.reposcheme, b.shared)) -} diff --git a/pkg/contexts/config/internal/builder_test.go b/pkg/contexts/config/internal/builder_test.go deleted file mode 100644 index aff15ecc8..000000000 --- a/pkg/contexts/config/internal/builder_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package internal_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - local "github.com/open-component-model/ocm/pkg/contexts/config/internal" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" -) - -var _ = Describe("builder test", func() { - It("creates local", func() { - ctx := local.Builder{}.New(datacontext.MODE_SHARED) - - Expect(ctx.AttributesContext()).To(BeIdenticalTo(datacontext.DefaultContext)) - Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) - Expect(ctx.ConfigTypes()).To(BeIdenticalTo(local.DefaultConfigTypeScheme)) - }) - - It("creates configured", func() { - ctx := local.Builder{}.New(datacontext.MODE_CONFIGURED) - - Expect(ctx.AttributesContext()).NotTo(BeIdenticalTo(datacontext.DefaultContext)) - Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) - Expect(ctx.ConfigTypes()).NotTo(BeIdenticalTo(local.DefaultConfigTypeScheme)) - Expect(ctx.ConfigTypes().KnownTypeNames()).To(Equal(local.DefaultConfigTypeScheme.KnownTypeNames())) - }) - - It("creates iniial", func() { - ctx := local.Builder{}.New(datacontext.MODE_INITIAL) - - Expect(ctx.AttributesContext()).NotTo(BeIdenticalTo(datacontext.DefaultContext)) - Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) - Expect(ctx.ConfigTypes()).NotTo(BeIdenticalTo(local.DefaultConfigTypeScheme)) - Expect(len(ctx.ConfigTypes().KnownTypeNames())).To(Equal(0)) - }) -}) diff --git a/pkg/contexts/config/internal/config.go b/pkg/contexts/config/internal/config.go deleted file mode 100644 index 0b8923ec8..000000000 --- a/pkg/contexts/config/internal/config.go +++ /dev/null @@ -1,61 +0,0 @@ -package internal - -import ( - "fmt" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/runtime" -) - -const KIND_CONFIGSET = "config set" - -type ConfigApplier interface { - ApplyConfigTo(Context, cfg, tgt interface{}) error -} - -type Config interface { - runtime.VersionedTypedObject - - ApplyTo(Context, interface{}) error -} - -type ConfigApplierFunction func(ctx Context, cfg, tgt interface{}) error - -func (f ConfigApplierFunction) ApplyConfigTo(ctx Context, cfg, tgt interface{}) error { - return f(ctx, cfg, tgt) -} - -type ConfigSet struct { - Description string `json:"description,omitempty"` - ConfigurationList `json:",inline"` -} - -type ConfigurationList struct { - Configurations []*GenericConfig `json:"configurations,omitempty"` -} - -func (c *ConfigurationList) AddConfig(cfg Config) error { - g, err := ToGenericConfig(cfg) - if err != nil { - return fmt.Errorf("unable to convert config to generic: %w", err) - } - - c.Configurations = append(c.Configurations, g) - - return nil -} - -func (c *ConfigurationList) AddConfigData(ctx Context, data []byte) error { - cfg, err := ctx.GetConfigForData(data, nil) - if err != nil { - return errors.Wrapf(err, "invalid config specification") - } - g, err := ToGenericConfig(cfg) - if err != nil { - return fmt.Errorf("unable to convert config to generic: %w", err) - } - - c.Configurations = append(c.Configurations, g) - return nil -} diff --git a/pkg/contexts/config/internal/context.go b/pkg/contexts/config/internal/context.go deleted file mode 100644 index 02777ceaf..000000000 --- a/pkg/contexts/config/internal/context.go +++ /dev/null @@ -1,356 +0,0 @@ -package internal - -import ( - "context" - "reflect" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -// OCM_CONFIG_TYPE_SUFFIX is the standard suffix used for configuration -// types provided by this library. -const OCM_CONFIG_TYPE_SUFFIX = ".config" + common.OCM_TYPE_GROUP_SUFFIX - -type ConfigSelector interface { - Select(Config) bool -} -type ConfigSelectorFunction func(Config) bool - -func (f ConfigSelectorFunction) Select(cfg Config) bool { return f(cfg) } - -var AllConfigs = AppliedConfigSelectorFunction(func(*AppliedConfig) bool { return true }) - -const AllGenerations int64 = 0 - -const CONTEXT_TYPE = "config" + datacontext.OCM_CONTEXT_SUFFIX - -type ContextProvider interface { - ConfigContext() Context -} - -type Context interface { - datacontext.Context - ContextProvider - - AttributesContext() datacontext.AttributesContext - - // Info provides the context for nested configuration evaluation - Info() string - // WithInfo provides the same context with additional nesting info - WithInfo(desc string) Context - - ConfigTypes() ConfigTypeScheme - - // SkipUnknownConfig can be used to control the behaviour - // for processing unknown configuration object types. - // It returns the previous mode valid before setting the - // new one. - SkipUnknownConfig(bool) bool - - // Validate validates the applied configuration for not using - // unknown configuration types, anymore. This can be used after setting - // SkipUnknownConfig, to check whether there are still unknown types - // which will be skipped. It does not provide information, whether - // config objects were skipped for previous object configuration - // requests. - Validate() error - - // GetConfigForData deserialize configuration objects for known - // configuration types. - GetConfigForData(data []byte, unmarshaler runtime.Unmarshaler) (Config, error) - - // ApplyData applies the config given by a byte stream to the config store - // If the config type is not known, a generic config is stored and returned. - // In this case an unknown error for kind KIND_CONFIGTYPE is returned. - ApplyData(data []byte, unmarshaler runtime.Unmarshaler, desc string) (Config, error) - // ApplyConfig applies the config to the config store - ApplyConfig(spec Config, desc string) error - - GetConfigForType(generation int64, typ string) (int64, []Config) - GetConfigForName(generation int64, name string) (int64, []Config) - GetConfig(generation int64, selector ConfigSelector) (int64, []Config) - - AddConfigSet(name string, set *ConfigSet) - ApplyConfigSet(name string) error - - // Reset all configs applied so far, subsequent calls to ApplyTo will - // ony see configs allpied after the last reset. - Reset() int64 - // Generation return the actual config generation. - // this is a strictly increasing number, regardless of the number - // of Reset calls. - Generation() int64 - // ApplyTo applies all configurations applied after the last reset with - // a generation larger than the given watermark to the specified target. - // A target may be any object. The applied configuration objects decide - // on their own whether they are applicable for the given target. - // The generation of the last applied object is returned to be used as - // new watermark. - ApplyTo(gen int64, target interface{}) (int64, error) -} - -var key = reflect.TypeOf(_context{}) - -// DefaultContext is the default context initialized by init functions. -var DefaultContext = Builder{}.New(datacontext.MODE_SHARED) - -// FromContext returns the Context to use for context.Context. -// This is either an explicit context or the default context. -// The returned context incorporates the given context. -func FromContext(ctx context.Context) Context { - c, _ := datacontext.ForContextByKey(ctx, key, DefaultContext) - return c.(Context) -} - -func FromProvider(p ContextProvider) Context { - if p == nil { - return nil - } - return p.ConfigContext() -} - -func DefinedForContext(ctx context.Context) (Context, bool) { - c, ok := datacontext.ForContextByKey(ctx, key, DefaultContext) - if c != nil { - return c.(Context), ok - } - return nil, ok -} - -//////////////////////////////////////////////////////////////////////////////// - -type _InternalContext = datacontext.InternalContext - -type coreContext struct { - _InternalContext - updater Updater - - sharedAttributes datacontext.AttributesContext - - knownConfigTypes ConfigTypeScheme - - configs *ConfigStore - skipUnknownConfig bool -} - -type _context struct { - *coreContext - description string -} - -var ( - _ Context = (*_context)(nil) - _ datacontext.ViewCreator[Context] = (*_context)(nil) -) - -// gcWrapper is used as garbage collectable -// wrapper for a context implementation -// to establish a runtime finalizer. -type gcWrapper struct { - datacontext.GCWrapper - *_context -} - -func newView(c *_context, ref ...bool) Context { - if utils.Optional(ref...) { - return datacontext.FinalizedContext[gcWrapper](c) - } - return c -} - -func (w *gcWrapper) SetContext(c *_context) { - w._context = c -} - -func newContext(shared datacontext.AttributesContext, reposcheme ConfigTypeScheme, delegates datacontext.Delegates) Context { - c := &_context{ - coreContext: &coreContext{ - sharedAttributes: shared, - knownConfigTypes: reposcheme, - configs: NewConfigStore(), - }, - } - c._InternalContext = datacontext.NewContextBase(c, CONTEXT_TYPE, key, shared.GetAttributes(), delegates) - c.updater = NewUpdaterForFactory(c, c.ConfigContext) // provide target as new view to internal context - datacontext.AssureUpdater(shared, NewUpdater(c, datacontext.PersistentContextRef(shared))) - - return newView(c, true) -} - -func (c *_context) CreateView() Context { - return newView(c, true) -} - -func (c *_context) ConfigContext() Context { - return newView(c) -} - -func (c *_context) Update() error { - return c.updater.Update() -} - -var _ datacontext.Updater = (*_context)(nil) - -func (c *_context) Info() string { - return c.description -} - -func (c *_context) WithInfo(desc string) Context { - if c.description != "" { - desc = desc + "--" + c.description - } - return newView(&_context{c.coreContext, desc}) -} - -func (c *_context) AttributesContext() datacontext.AttributesContext { - c.updater.Update() - return c.sharedAttributes -} - -func (c *_context) ConfigTypes() ConfigTypeScheme { - return c.knownConfigTypes -} - -func (c *_context) SkipUnknownConfig(b bool) bool { - old := c.skipUnknownConfig - c.skipUnknownConfig = b - return old -} - -func (c *_context) ConfigForData(data []byte, unmarshaler runtime.Unmarshaler) (Config, error) { - return c.knownConfigTypes.Decode(data, unmarshaler) -} - -func (c *_context) GetConfigForData(data []byte, unmarshaler runtime.Unmarshaler) (Config, error) { - spec, err := c.knownConfigTypes.Decode(data, unmarshaler) - if err != nil { - return nil, err - } - return spec, nil -} - -func (c *_context) ApplyConfig(spec Config, desc string) error { - var unknown error - - // use temporary view for outbound calls - spec, err := (&AppliedConfig{config: spec}).eval(newView(c)) - if err != nil { - if !errors.IsErrUnknownKind(err, KIND_CONFIGTYPE) { - return errors.Wrapf(err, "%s", desc) - } - if !c.skipUnknownConfig { - unknown = err - } - err = nil - } - - c.configs.Apply(spec, desc) - - for { - // apply directly and also indirectly described configurations - if gen, in := c.updater.State(); err != nil || in || gen >= c.configs.Generation() { - break - } - err = c.Update() - if IsErrNoContext(err) { - err = unknown - } - } - - return errors.Wrapf(err, "%s", desc) -} - -func (c *_context) ApplyData(data []byte, unmarshaler runtime.Unmarshaler, desc string) (Config, error) { - spec, err := c.knownConfigTypes.Decode(data, unmarshaler) - if err != nil { - return nil, err - } - return spec, c.ApplyConfig(spec, desc) -} - -func (c *_context) selector(gen int64, selector ConfigSelector) AppliedConfigSelector { - if gen <= 0 { - return AppliedConfigSelectorFor(selector) - } - if selector == nil { - return AppliedGenerationSelector(gen) - } - return AppliedAndSelector(AppliedGenerationSelector(gen), AppliedConfigSelectorFor(selector)) -} - -func (c *_context) Generation() int64 { - return c.configs.Generation() -} - -func (c *_context) Reset() int64 { - return c.configs.Reset() -} - -func (c *_context) ApplyTo(gen int64, target interface{}) (int64, error) { - cur := c.configs.Generation() - if cur <= gen { - return gen, nil - } - cur, cfgs := c.configs.GetConfigForSelector(c, AppliedGenerationSelector(gen)) - - list := errors.ErrListf("config apply errors") - for _, cfg := range cfgs { - err := cfg.config.ApplyTo(c.WithInfo(cfg.description), target) - if c.skipUnknownConfig && errors.IsErrUnknownKind(err, KIND_CONFIGTYPE) { - err = nil - } - err = errors.Wrapf(err, "%s", cfg.description) - if !IsErrNoContext(err) { - list.Add(err) - } - } - return cur, list.Result() -} - -func (c *_context) Validate() error { - list := errors.ErrList() - - _, cfgs := c.configs.GetConfigForSelector(c, AllAppliedConfigs) - for _, cfg := range cfgs { - _, err := cfg.eval(newView(c)) - list.Add(err) - } - return list.Result() -} - -func (c *_context) AddConfigSet(name string, set *ConfigSet) { - c.configs.AddSet(name, set) -} - -func (c *_context) ApplyConfigSet(name string) error { - set := c.configs.GetSet(name) - if set == nil { - return errors.ErrUnknown(KIND_CONFIGSET, name) - } - desc := "config set " + name - list := errors.ErrListf("applying %s", desc) - for _, cfg := range set.Configurations { - list.Add(c.ApplyConfig(cfg, desc)) - } - return list.Result() -} - -func (c *_context) GetConfig(gen int64, selector ConfigSelector) (int64, []Config) { - gen, cfgs := c.configs.GetConfigForSelector(c, c.selector(gen, selector)) - return gen, cfgs.Configs() -} - -func (c *_context) GetConfigForName(gen int64, name string) (int64, []Config) { - gen, cfgs := c.configs.GetConfigForName(c, name, c.selector(gen, nil)) - return gen, cfgs.Configs() -} - -func (c *_context) GetConfigForType(gen int64, typ string) (int64, []Config) { - gen, cfgs := c.configs.GetConfigForType(c, typ, c.selector(gen, nil)) - return gen, cfgs.Configs() -} diff --git a/pkg/contexts/config/internal/logging.go b/pkg/contexts/config/internal/logging.go deleted file mode 100644 index 396e75025..000000000 --- a/pkg/contexts/config/internal/logging.go +++ /dev/null @@ -1,9 +0,0 @@ -package internal - -import ( - ocmlog "github.com/open-component-model/ocm/pkg/logging" -) - -var Realm = ocmlog.DefineSubRealm("configuration management", "config") - -var Logger = ocmlog.DynamicLogger(Realm) diff --git a/pkg/contexts/config/internal/setup_test.go b/pkg/contexts/config/internal/setup_test.go deleted file mode 100644 index bfe489c86..000000000 --- a/pkg/contexts/config/internal/setup_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package internal_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/config" - "github.com/open-component-model/ocm/pkg/contexts/config/internal" -) - -var _ = Describe("setup", func() { - It("creates initial", func() { - Expect(len(config.DefaultContext().ConfigTypes().KnownTypeNames())).To(Equal(6)) - Expect(len(internal.DefaultConfigTypeScheme.KnownTypeNames())).To(Equal(6)) - }) -}) diff --git a/pkg/contexts/config/logging.go b/pkg/contexts/config/logging.go deleted file mode 100644 index cc3916117..000000000 --- a/pkg/contexts/config/logging.go +++ /dev/null @@ -1,17 +0,0 @@ -package config - -import ( - "github.com/open-component-model/ocm/pkg/contexts/config/internal" -) - -var Realm = internal.Realm - -var Logger = internal.Logger - -func Debug(c Context, msg string, keypairs ...interface{}) { - c.LoggingContext().Logger(Realm).Debug(msg, append(keypairs, "id", c.GetId())...) -} - -func Info(c Context, msg string, keypairs ...interface{}) { - c.LoggingContext().Logger(Realm).Info(msg, append(keypairs, "id", c.GetId())...) -} diff --git a/pkg/contexts/config/plugin/type.go b/pkg/contexts/config/plugin/type.go deleted file mode 100644 index a240644ff..000000000 --- a/pkg/contexts/config/plugin/type.go +++ /dev/null @@ -1,21 +0,0 @@ -package plugin - -import ( - "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/contexts/config/internal" - "github.com/open-component-model/ocm/pkg/runtime" -) - -var _ cpi.Config = (*Config)(nil) - -type Config struct { - runtime.UnstructuredVersionedTypedObject `json:",inline"` -} - -func (c *Config) ApplyTo(context internal.Context, i interface{}) error { - return nil -} - -func New(name string, desc string) cpi.ConfigType { - return cpi.NewConfigType[*Config](name, desc) -} diff --git a/pkg/contexts/credentials/builder.go b/pkg/contexts/credentials/builder.go deleted file mode 100644 index fcb4c2568..000000000 --- a/pkg/contexts/credentials/builder.go +++ /dev/null @@ -1,29 +0,0 @@ -package credentials - -import ( - "context" - - "github.com/open-component-model/ocm/pkg/contexts/config" - "github.com/open-component-model/ocm/pkg/contexts/credentials/internal" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" -) - -func WithContext(ctx context.Context) internal.Builder { - return internal.Builder{}.WithContext(ctx) -} - -func WithConfigs(ctx config.Context) internal.Builder { - return internal.Builder{}.WithConfig(ctx) -} - -func WithRepositoyTypeScheme(scheme RepositoryTypeScheme) internal.Builder { - return internal.Builder{}.WithRepositoyTypeScheme(scheme) -} - -func WithStandardConumerMatchers(matchers internal.IdentityMatcherRegistry) internal.Builder { - return internal.Builder{}.WithStandardConumerMatchers(matchers) -} - -func New(mode ...datacontext.BuilderMode) Context { - return internal.Builder{}.New(mode...) -} diff --git a/pkg/contexts/credentials/builtin/github/github.go b/pkg/contexts/credentials/builtin/github/github.go deleted file mode 100644 index 733820d04..000000000 --- a/pkg/contexts/credentials/builtin/github/github.go +++ /dev/null @@ -1,22 +0,0 @@ -package github - -import ( - "os" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/github/identity" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" -) - -func init() { - t := os.Getenv("GITHUB_TOKEN") - if t != "" { - us := os.Getenv("GITHUB_SERVER_URL") - id := identity.GetConsumerId(us) - - if src, err := cpi.DefaultContext.GetCredentialsForConsumer(id); err != nil || src == nil { - creds := cpi.NewCredentials(common.Properties{cpi.ATTR_TOKEN: t}) - cpi.DefaultContext.SetCredentialsForConsumer(id, creds) - } - } -} diff --git a/pkg/contexts/credentials/builtin/github/identity/identity.go b/pkg/contexts/credentials/builtin/github/identity/identity.go deleted file mode 100644 index c07a16906..000000000 --- a/pkg/contexts/credentials/builtin/github/identity/identity.go +++ /dev/null @@ -1,83 +0,0 @@ -package identity - -import ( - "net/url" - "path" - "strings" - - "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/listformat" -) - -const CONSUMER_TYPE = "Github" - -// identity properties -const ( - ID_HOSTNAME = hostpath.ID_HOSTNAME - ID_PORT = hostpath.ID_PORT - ID_PATHPREFIX = hostpath.ID_PATHPREFIX -) - -// credential properties -const ( - ATTR_TOKEN = cpi.ATTR_TOKEN -) - -const GITHUB = "github.com" - -var identityMatcher = hostpath.IdentityMatcher(CONSUMER_TYPE) - -func IdentityMatcher(pattern, cur, id cpi.ConsumerIdentity) bool { - return identityMatcher(pattern, cur, id) -} - -func init() { - attrs := listformat.FormatListElements("", listformat.StringElementDescriptionList{ - ATTR_TOKEN, "GitHub personal access token", - }) - cpi.RegisterStandardIdentity(CONSUMER_TYPE, identityMatcher, - `GitHub credential matcher - -This matcher is a hostpath matcher.`, - attrs) -} - -func PATCredentials(pat string) cpi.Credentials { - return cpi.DirectCredentials{ - ATTR_TOKEN: pat, - } -} - -func GetConsumerId(serverurl string, repo ...string) cpi.ConsumerIdentity { - host := GITHUB - port := "" - if serverurl != "" { - u, err := url.Parse(serverurl) - if err == nil { - host = u.Host - } - } - if idx := strings.Index(host, ":"); idx > 0 { - port = host[idx+1:] - host = host[:idx] - } - - id := cpi.ConsumerIdentity{ - cpi.ID_TYPE: CONSUMER_TYPE, - ID_HOSTNAME: host, - } - if port != "" { - id[ID_PORT] = port - } - p := path.Join(repo...) - if p != "" { - id[ID_PATHPREFIX] = p - } - return id -} - -func GetCredentials(ctx cpi.ContextProvider, serverurl string, repo ...string) (cpi.Credentials, error) { - id := GetConsumerId(serverurl, repo...) - return cpi.CredentialsForConsumer(ctx.CredentialsContext(), id, identityMatcher) -} diff --git a/pkg/contexts/credentials/builtin/helm/identity/identity.go b/pkg/contexts/credentials/builtin/helm/identity/identity.go deleted file mode 100644 index eac827125..000000000 --- a/pkg/contexts/credentials/builtin/helm/identity/identity.go +++ /dev/null @@ -1,103 +0,0 @@ -package identity - -import ( - "strings" - - "helm.sh/helm/v3/pkg/registry" - - "github.com/open-component-model/ocm/pkg/common" - ociidentity "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "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/listformat" -) - -// CONSUMER_TYPE is the Helm chart repository type. -const CONSUMER_TYPE = "HelmChartRepository" - -// ID_TYPE is the type field of a consumer identity. -const ID_TYPE = cpi.ID_TYPE - -// ID_SCHEME is the scheme of the repository. -const ID_SCHEME = hostpath.ID_SCHEME - -// ID_HOSTNAME is the hostname of a repository. -const ID_HOSTNAME = hostpath.ID_HOSTNAME - -// ID_PORT is the port number of a repository. -const ID_PORT = hostpath.ID_PORT - -// ID_PATHPREFIX is the path of a repository. -const ID_PATHPREFIX = hostpath.ID_PATHPREFIX - -func init() { - attrs := listformat.FormatListElements("", listformat.StringElementDescriptionList{ - ATTR_USERNAME, "the basic auth user name", - ATTR_PASSWORD, "the basic auth password", - ATTR_CERTIFICATE, "TLS client certificate", - ATTR_PRIVATE_KEY, "TLS private key", - ATTR_CERTIFICATE_AUTHORITY, "TLS certificate authority", - }) - - cpi.RegisterStandardIdentity(CONSUMER_TYPE, IdentityMatcher, `Helm chart repository - -It matches the `+CONSUMER_TYPE+` consumer type and additionally acts like -the `+hostpath.IDENTITY_TYPE+` type.`, - attrs) -} - -var identityMatcher = hostpath.IdentityMatcher("") - -func IdentityMatcher(pattern, cur, id cpi.ConsumerIdentity) bool { - return identityMatcher(pattern, cur, id) -} - -// used credential attributes - -const ( - ATTR_USERNAME = cpi.ATTR_USERNAME - ATTR_PASSWORD = cpi.ATTR_PASSWORD - ATTR_CERTIFICATE_AUTHORITY = cpi.ATTR_CERTIFICATE_AUTHORITY - ATTR_CERTIFICATE = cpi.ATTR_CERTIFICATE - ATTR_PRIVATE_KEY = cpi.ATTR_PRIVATE_KEY -) - -func OCIRepoURL(repourl string, chartname string) string { - repourl = strings.TrimSuffix(repourl, "/")[3+len(registry.OCIScheme):] - if chartname != "" { - repourl += "/" + chartname - } - return repourl -} - -func SimpleCredentials(user, passwd string) cpi.Credentials { - return cpi.DirectCredentials{ - ATTR_USERNAME: user, - ATTR_PASSWORD: passwd, - } -} - -func GetConsumerId(repourl string, chartname string) cpi.ConsumerIdentity { - i := strings.LastIndex(chartname, ":") - if i >= 0 { - chartname = chartname[:i] - } - if registry.IsOCI(repourl) { - repourl = strings.TrimSuffix(repourl, "/") - return ociidentity.GetConsumerId(OCIRepoURL(repourl, ""), chartname) - } else { - return hostpath.GetConsumerIdentity(CONSUMER_TYPE, repourl) - } -} - -func GetCredentials(ctx cpi.ContextProvider, repourl string, chartname string) common.Properties { - id := GetConsumerId(repourl, chartname) - if id == nil { - return nil - } - creds, err := cpi.CredentialsForConsumer(ctx.CredentialsContext(), id) - if creds == nil || err != nil { - return nil - } - return creds.Properties() -} diff --git a/pkg/contexts/credentials/builtin/helm/identity/identity_test.go b/pkg/contexts/credentials/builtin/helm/identity/identity_test.go deleted file mode 100644 index 752637555..000000000 --- a/pkg/contexts/credentials/builtin/helm/identity/identity_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package identity_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/helm/identity" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - ociidentity "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/oci" -) - -var _ = Describe("consumer id handling", func() { - Context("id deternation", func() { - It("handles helm repos", func() { - id := GetConsumerId("https://acme.org/charts", "demo:v1") - Expect(id).To(Equal(credentials.NewConsumerIdentity(CONSUMER_TYPE, - "pathprefix", "charts", - "port", "443", - "hostname", "acme.org", - "scheme", "https", - ))) - }) - - It("handles oci repos", func() { - id := GetConsumerId("oci://acme.org/charts", "demo:v1") - Expect(id).To(Equal(credentials.NewConsumerIdentity(ociidentity.CONSUMER_TYPE, - "pathprefix", "charts/demo", - "hostname", "acme.org", - ))) - }) - }) - - Context("query credentials", func() { - var ctx oci.Context - var credctx credentials.Context - - BeforeEach(func() { - ctx = oci.New(datacontext.MODE_EXTENDED) - credctx = ctx.CredentialsContext() - }) - - It("queries helm credentials", func() { - id := GetConsumerId("https://acme.org/charts", "demo:v1") - credctx.SetCredentialsForConsumer(id, - credentials.CredentialsFromList( - ATTR_USERNAME, "helm", - ATTR_PASSWORD, "helmpass", - ), - ) - - creds := GetCredentials(ctx, "https://acme.org/charts", "demo:v1") - Expect(creds).To(Equal(common.Properties{ - ATTR_USERNAME: "helm", - ATTR_PASSWORD: "helmpass", - })) - }) - - It("queries oci credentials", func() { - id := GetConsumerId("oci://acme.org/charts", "demo:v1") - credctx.SetCredentialsForConsumer(id, - credentials.CredentialsFromList( - ATTR_USERNAME, "oci", - ATTR_PASSWORD, "ocipass", - ), - ) - - creds := GetCredentials(ctx, "oci://acme.org/charts", "demo:v1") - Expect(creds).To(Equal(common.Properties{ - ATTR_USERNAME: "oci", - ATTR_PASSWORD: "ocipass", - })) - }) - }) -}) diff --git a/pkg/contexts/credentials/builtin/init.go b/pkg/contexts/credentials/builtin/init.go deleted file mode 100644 index 7996eab6a..000000000 --- a/pkg/contexts/credentials/builtin/init.go +++ /dev/null @@ -1,8 +0,0 @@ -package builtin - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/github" - _ "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/helm/identity" - _ "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - _ "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/wget/identity" -) diff --git a/pkg/contexts/credentials/builtin/maven/identity/identity.go b/pkg/contexts/credentials/builtin/maven/identity/identity.go deleted file mode 100644 index 491e38b45..000000000 --- a/pkg/contexts/credentials/builtin/maven/identity/identity.go +++ /dev/null @@ -1,62 +0,0 @@ -package identity - -import ( - . "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/listformat" - "github.com/open-component-model/ocm/pkg/logging" -) - -const ( - // CONSUMER_TYPE is the maven repository type. - CONSUMER_TYPE = "MavenRepository" - - // 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 maven registry. - ATTR_PASSWORD = cpi.ATTR_PASSWORD -) - -// REALM the logging realm / prefix. -var REALM = logging.DefineSubRealm("Maven repository", "maven") - -func init() { - attrs := listformat.FormatListElements("", listformat.StringElementDescriptionList{ - ATTR_USERNAME, "the basic auth user name", - ATTR_PASSWORD, "the basic auth password", - }) - - cpi.RegisterStandardIdentity(CONSUMER_TYPE, hostpath.IdentityMatcher(CONSUMER_TYPE), `MVN repository - -It matches the `+CONSUMER_TYPE+` consumer type and additionally acts like -the `+hostpath.IDENTITY_TYPE+` type.`, - attrs) -} - -var identityMatcher = hostpath.IdentityMatcher(CONSUMER_TYPE) - -func IdentityMatcher(pattern, cur, id cpi.ConsumerIdentity) bool { - return identityMatcher(pattern, cur, id) -} - -func GetConsumerId(rawURL, groupId string) (cpi.ConsumerIdentity, error) { - url, err := JoinPath(rawURL, groupId) - if err != nil { - return nil, err - } - return hostpath.GetConsumerIdentity(CONSUMER_TYPE, url), nil -} - -func GetCredentials(ctx cpi.ContextProvider, repoUrl, groupId string) (cpi.Credentials, error) { - id, err := GetConsumerId(repoUrl, groupId) - if err != nil { - return nil, err - } - if id == nil { - logging.DynamicLogger(REALM).Debug("No consumer identity found.", "url", repoUrl, "groupId", groupId) - return nil, nil - } - return cpi.CredentialsForConsumer(ctx.CredentialsContext(), id) -} diff --git a/pkg/contexts/credentials/builtin/npm/identity/identity.go b/pkg/contexts/credentials/builtin/npm/identity/identity.go deleted file mode 100644 index dd90ef107..000000000 --- a/pkg/contexts/credentials/builtin/npm/identity/identity.go +++ /dev/null @@ -1,68 +0,0 @@ -package identity - -import ( - "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/listformat" - "github.com/open-component-model/ocm/pkg/logging" -) - -const ( - // CONSUMER_TYPE is the npm repository type. - CONSUMER_TYPE = "NpmRegistry" - - // ATTR_USERNAME is the username attribute. Required for login at any npm registry. - ATTR_USERNAME = cpi.ATTR_USERNAME - // ATTR_PASSWORD is the password attribute. Required for login at any npm registry. - ATTR_PASSWORD = cpi.ATTR_PASSWORD - // ATTR_EMAIL is the email attribute. Required for login at any npm registry. - ATTR_EMAIL = cpi.ATTR_EMAIL - // ATTR_TOKEN is the token attribute. May exist after login at any npm registry. - ATTR_TOKEN = cpi.ATTR_TOKEN -) - -// Logging Realm. -var REALM = logging.DefineSubRealm("NPM registry", "npm") - -func init() { - attrs := listformat.FormatListElements("", listformat.StringElementDescriptionList{ - ATTR_USERNAME, "the basic auth user name", - ATTR_PASSWORD, "the basic auth password", - ATTR_EMAIL, "NPM registry, require an email address", - ATTR_TOKEN, "the token attribute. May exist after login at any npm registry. Check your .npmrc file!", - }) - - cpi.RegisterStandardIdentity(CONSUMER_TYPE, hostpath.IdentityMatcher(CONSUMER_TYPE), `NPM registry - -It matches the `+CONSUMER_TYPE+` consumer type and additionally acts like -the `+hostpath.IDENTITY_TYPE+` type.`, - attrs) -} - -var identityMatcher = hostpath.IdentityMatcher(CONSUMER_TYPE) - -func IdentityMatcher(pattern, cur, id cpi.ConsumerIdentity) bool { - return identityMatcher(pattern, cur, id) -} - -func GetConsumerId(rawURL, groupId string) (cpi.ConsumerIdentity, error) { - _url, err := url.JoinPath(rawURL, groupId) - if err != nil { - return nil, err - } - return hostpath.GetConsumerIdentity(CONSUMER_TYPE, _url), nil -} - -func GetCredentials(ctx cpi.ContextProvider, repoUrl string, pkgName string) (cpi.Credentials, error) { - id, err := GetConsumerId(repoUrl, pkgName) - if err != nil { - return nil, err - } - if id == nil { - logging.DynamicLogger(REALM).Debug("No consumer identity found.", "url", repoUrl, "groupId", pkgName) - return nil, nil - } - return cpi.CredentialsForConsumer(ctx.CredentialsContext(), id) -} diff --git a/pkg/contexts/credentials/builtin/oci/identity/id_test.go b/pkg/contexts/credentials/builtin/oci/identity/id_test.go deleted file mode 100644 index e516ef0ed..000000000 --- a/pkg/contexts/credentials/builtin/oci/identity/id_test.go +++ /dev/null @@ -1,145 +0,0 @@ -package identity_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" -) - -var _ = Describe("ctf management", func() { - Context("with path", func() { - pat := credentials.ConsumerIdentity{ - identity.ID_HOSTNAME: "host", - identity.ID_PATHPREFIX: "a/b", - identity.ID_PORT: "4711", - } - - It("complete", func() { - id := credentials.ConsumerIdentity{ - identity.ID_HOSTNAME: "host", - identity.ID_PATHPREFIX: "a/b", - identity.ID_PORT: "4711", - } - Expect(identity.IdentityMatcher(pat, nil, id)).To(BeTrue()) - Expect(identity.IdentityMatcher(pat, id, id)).To(BeFalse()) - }) - - It("path prefix", func() { - id := credentials.ConsumerIdentity{ - identity.ID_HOSTNAME: "host", - identity.ID_PATHPREFIX: "a", - identity.ID_PORT: "4711", - } - Expect(identity.IdentityMatcher(pat, nil, id)).To(BeTrue()) - Expect(identity.IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - It("different prefix", func() { - id := credentials.ConsumerIdentity{ - identity.ID_HOSTNAME: "host", - identity.ID_PATHPREFIX: "b", - identity.ID_PORT: "4711", - } - Expect(identity.IdentityMatcher(pat, nil, id)).To(BeFalse()) - Expect(identity.IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - It("longer prefix", func() { - id := credentials.ConsumerIdentity{ - identity.ID_HOSTNAME: "host", - identity.ID_PATHPREFIX: "a/b/c", - identity.ID_PORT: "4711", - } - Expect(identity.IdentityMatcher(pat, nil, id)).To(BeFalse()) - Expect(identity.IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - It("missing path", func() { - id := credentials.ConsumerIdentity{ - identity.ID_HOSTNAME: "host", - identity.ID_PORT: "4711", - } - Expect(identity.IdentityMatcher(pat, nil, id)).To(BeTrue()) - Expect(identity.IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - It("missing port", func() { - id := credentials.ConsumerIdentity{ - identity.ID_HOSTNAME: "host", - identity.ID_PATHPREFIX: "a/b", - } - Expect(identity.IdentityMatcher(pat, nil, id)).To(BeTrue()) - Expect(identity.IdentityMatcher(pat, pat, id)).To(BeFalse()) - - Expect(identity.IdentityMatcher(id, nil, pat)).To(BeTrue()) // accept additional port as fallback - Expect(identity.IdentityMatcher(id, id, pat)).To(BeFalse()) // but not to replace more general match - }) - It("different port", func() { - id := credentials.ConsumerIdentity{ - identity.ID_HOSTNAME: "host", - identity.ID_PATHPREFIX: "a/b", - identity.ID_PORT: "0815", - } - Expect(identity.IdentityMatcher(pat, nil, id)).To(BeFalse()) - Expect(identity.IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - - It("different host", func() { - id := credentials.ConsumerIdentity{ - identity.ID_HOSTNAME: "other", - identity.ID_PATHPREFIX: "a/b", - identity.ID_PORT: "4711", - } - Expect(identity.IdentityMatcher(pat, nil, id)).To(BeFalse()) - Expect(identity.IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - It("no host", func() { - id := credentials.ConsumerIdentity{ - identity.ID_PATHPREFIX: "a/b", - identity.ID_PORT: "4711", - } - Expect(identity.IdentityMatcher(id, nil, pat)).To(BeTrue()) - Expect(identity.IdentityMatcher(pat, id, id)).To(BeFalse()) - Expect(identity.IdentityMatcher(pat, id, pat)).To(BeTrue()) - }) - }) - - Context("without path", func() { - pat := credentials.ConsumerIdentity{ - identity.ID_HOSTNAME: "host", - identity.ID_PORT: "4711", - } - - It("complete", func() { - id := credentials.ConsumerIdentity{ - identity.ID_HOSTNAME: "host", - identity.ID_PORT: "4711", - } - Expect(identity.IdentityMatcher(pat, nil, id)).To(BeTrue()) - Expect(identity.IdentityMatcher(pat, id, id)).To(BeFalse()) - }) - - It("different prefix", func() { - id := credentials.ConsumerIdentity{ - identity.ID_HOSTNAME: "host", - identity.ID_PORT: "4711", - identity.ID_PATHPREFIX: "b", - } - Expect(identity.IdentityMatcher(pat, nil, id)).To(BeFalse()) - Expect(identity.IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - It("missing port", func() { - id := credentials.ConsumerIdentity{ - identity.ID_HOSTNAME: "host", - } - Expect(identity.IdentityMatcher(pat, nil, id)).To(BeTrue()) - Expect(identity.IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - It("different port", func() { - id := credentials.ConsumerIdentity{ - identity.ID_HOSTNAME: "host", - identity.ID_PORT: "0815", - } - Expect(identity.IdentityMatcher(pat, nil, id)).To(BeFalse()) - Expect(identity.IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - }) -}) diff --git a/pkg/contexts/credentials/builtin/oci/identity/identity.go b/pkg/contexts/credentials/builtin/oci/identity/identity.go deleted file mode 100644 index 57917f4d0..000000000 --- a/pkg/contexts/credentials/builtin/oci/identity/identity.go +++ /dev/null @@ -1,48 +0,0 @@ -package identity - -import ( - "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/listformat" -) - -// CONSUMER_TYPE is the OCT registry type. -const CONSUMER_TYPE = "OCIRegistry" - -// used identity properties. -const ( - ID_TYPE = hostpath.ID_TYPE - ID_HOSTNAME = hostpath.ID_HOSTNAME - ID_PORT = hostpath.ID_PORT - ID_PATHPREFIX = hostpath.ID_PATHPREFIX - ID_SCHEME = hostpath.ID_SCHEME -) - -// used credential properties. -const ( - ATTR_USERNAME = cpi.ATTR_USERNAME - ATTR_PASSWORD = cpi.ATTR_PASSWORD - ATTR_IDENTITY_TOKEN = cpi.ATTR_IDENTITY_TOKEN - ATTR_CERTIFICATE_AUTHORITY = cpi.ATTR_CERTIFICATE_AUTHORITY -) - -func init() { - attrs := listformat.FormatListElements("", listformat.StringElementDescriptionList{ - ATTR_USERNAME, "the basic auth user name", - ATTR_PASSWORD, "the basic auth password", - ATTR_IDENTITY_TOKEN, "the bearer token used for non-basic auth authorization", - ATTR_CERTIFICATE_AUTHORITY, "the certificate authority certificate used to verify certificates", - }) - - cpi.RegisterStandardIdentity(CONSUMER_TYPE, IdentityMatcher, `OCI registry credential matcher - -It matches the `+CONSUMER_TYPE+` consumer type and additionally acts like -the `+hostpath.IDENTITY_TYPE+` type.`, - attrs) -} - -var identityMatcher = hostpath.IdentityMatcher(CONSUMER_TYPE) - -func IdentityMatcher(pattern, cur, id cpi.ConsumerIdentity) bool { - return identityMatcher(pattern, cur, id) -} diff --git a/pkg/contexts/credentials/builtin/wget/identity/identity.go b/pkg/contexts/credentials/builtin/wget/identity/identity.go deleted file mode 100644 index feac525e0..000000000 --- a/pkg/contexts/credentials/builtin/wget/identity/identity.go +++ /dev/null @@ -1,56 +0,0 @@ -package identity - -import ( - "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/listformat" -) - -// CONSUMER_TYPE is the wget access method type. -const CONSUMER_TYPE = "wget" - -// used identity properties. -const ( - ID_TYPE = hostpath.ID_TYPE - ID_HOSTNAME = hostpath.ID_HOSTNAME - ID_PORT = hostpath.ID_PORT - ID_PATHPREFIX = hostpath.ID_PATHPREFIX - ID_SCHEME = hostpath.ID_SCHEME -) - -// used credential properties. -const ( - ATTR_USERNAME = cpi.ATTR_USERNAME - ATTR_PASSWORD = cpi.ATTR_PASSWORD - ATTR_IDENTITY_TOKEN = cpi.ATTR_IDENTITY_TOKEN - ATTR_CERTIFICATE_AUTHORITY = cpi.ATTR_CERTIFICATE_AUTHORITY - ATTR_CERTIFICATE = cpi.ATTR_CERTIFICATE - ATTR_PRIVATE_KEY = cpi.ATTR_PRIVATE_KEY -) - -func init() { - attrs := listformat.FormatListElements("", listformat.StringElementDescriptionList{ - ATTR_USERNAME, "the basic auth user name", - ATTR_PASSWORD, "the basic auth password", - ATTR_IDENTITY_TOKEN, "the bearer token used for non-basic auth authorization", - ATTR_CERTIFICATE_AUTHORITY, "the certificate authority certificate used to verify certificates presented by the server", - ATTR_CERTIFICATE, "the certificate used to present to the server", - ATTR_PRIVATE_KEY, "the private key corresponding to the certificate", - }) - - cpi.RegisterStandardIdentity(CONSUMER_TYPE, IdentityMatcher, `wget credential matcher - -It matches the `+CONSUMER_TYPE+` consumer type and additionally acts like -the `+hostpath.IDENTITY_TYPE+` type.`, - attrs) -} - -var identityMatcher = hostpath.IdentityMatcher(CONSUMER_TYPE) - -func IdentityMatcher(pattern, cur, id cpi.ConsumerIdentity) bool { - return identityMatcher(pattern, cur, id) -} - -func GetConsumerId(url string) cpi.ConsumerIdentity { - return hostpath.GetConsumerIdentity(CONSUMER_TYPE, url) -} diff --git a/pkg/contexts/credentials/builtin/wget/identity/identity_test.go b/pkg/contexts/credentials/builtin/wget/identity/identity_test.go deleted file mode 100644 index 263548988..000000000 --- a/pkg/contexts/credentials/builtin/wget/identity/identity_test.go +++ /dev/null @@ -1,145 +0,0 @@ -package identity_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/wget/identity" - - "github.com/open-component-model/ocm/pkg/contexts/credentials" -) - -var _ = Describe("wget credential management", func() { - Context("with path", func() { - pat := credentials.ConsumerIdentity{ - ID_HOSTNAME: "host", - ID_PATHPREFIX: "a/b", - ID_PORT: "4711", - } - - It("complete", func() { - id := credentials.ConsumerIdentity{ - ID_HOSTNAME: "host", - ID_PATHPREFIX: "a/b", - ID_PORT: "4711", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) - Expect(IdentityMatcher(pat, id, id)).To(BeFalse()) - }) - - It("path prefix", func() { - id := credentials.ConsumerIdentity{ - ID_HOSTNAME: "host", - ID_PATHPREFIX: "a", - ID_PORT: "4711", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) - Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - It("different prefix", func() { - id := credentials.ConsumerIdentity{ - ID_HOSTNAME: "host", - ID_PATHPREFIX: "b", - ID_PORT: "4711", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) - Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - It("longer prefix", func() { - id := credentials.ConsumerIdentity{ - ID_HOSTNAME: "host", - ID_PATHPREFIX: "a/b/c", - ID_PORT: "4711", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) - Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - It("missing path", func() { - id := credentials.ConsumerIdentity{ - ID_HOSTNAME: "host", - ID_PORT: "4711", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) - Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - It("missing port", func() { - id := credentials.ConsumerIdentity{ - ID_HOSTNAME: "host", - ID_PATHPREFIX: "a/b", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) - Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) - - Expect(IdentityMatcher(id, nil, pat)).To(BeTrue()) // accept additional port as fallback - Expect(IdentityMatcher(id, id, pat)).To(BeFalse()) // but not to replace more general match - }) - It("different port", func() { - id := credentials.ConsumerIdentity{ - ID_HOSTNAME: "host", - ID_PATHPREFIX: "a/b", - ID_PORT: "0815", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) - Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - - It("different host", func() { - id := credentials.ConsumerIdentity{ - ID_HOSTNAME: "other", - ID_PATHPREFIX: "a/b", - ID_PORT: "4711", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) - Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - It("no host", func() { - id := credentials.ConsumerIdentity{ - ID_PATHPREFIX: "a/b", - ID_PORT: "4711", - } - Expect(IdentityMatcher(id, nil, pat)).To(BeTrue()) - Expect(IdentityMatcher(pat, id, id)).To(BeFalse()) - Expect(IdentityMatcher(pat, id, pat)).To(BeTrue()) - }) - }) - - Context("without path", func() { - pat := credentials.ConsumerIdentity{ - ID_HOSTNAME: "host", - ID_PORT: "4711", - } - - It("complete", func() { - id := credentials.ConsumerIdentity{ - ID_HOSTNAME: "host", - ID_PORT: "4711", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) - Expect(IdentityMatcher(pat, id, id)).To(BeFalse()) - }) - - It("different prefix", func() { - id := credentials.ConsumerIdentity{ - ID_HOSTNAME: "host", - ID_PORT: "4711", - ID_PATHPREFIX: "b", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) - Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - It("missing port", func() { - id := credentials.ConsumerIdentity{ - ID_HOSTNAME: "host", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) - Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - It("different port", func() { - id := credentials.ConsumerIdentity{ - ID_HOSTNAME: "host", - ID_PORT: "0815", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) - Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - }) -}) diff --git a/pkg/contexts/credentials/config/config_test.go b/pkg/contexts/credentials/config/config_test.go deleted file mode 100644 index e07492762..000000000 --- a/pkg/contexts/credentials/config/config_test.go +++ /dev/null @@ -1,209 +0,0 @@ -package config_test - -import ( - "encoding/json" - "reflect" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/mandelsoft/goutils/testutils" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/config" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - localconfig "github.com/open-component-model/ocm/pkg/contexts/credentials/config" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/aliases" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/directcreds" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/memory" - "github.com/open-component-model/ocm/pkg/runtime" -) - -var DefaultContext = credentials.New() - -var _ = Describe("generic credentials", func() { - props := common.Properties{ - "user": "USER", - "password": "PASSWORD", - } - - repospec := memory.NewRepositorySpec("test") - credspec := credentials.NewCredentialsSpec("cred", repospec) - direct := directcreds.NewRepositorySpec(props) - - cfgconsumerdata := "{\"type\":\"credentials.config.ocm.software\",\"consumers\":[{\"identity\":{\"type\":\"oci\",\"url\":\"https://acme.com\"},\"credentials\":[{\"credentialsName\":\"cred\",\"repoName\":\"test\",\"type\":\"Memory\"}]}]}" - cfgrepodata := "{\"type\":\"credentials.config.ocm.software\",\"repositories\":[{\"repository\":{\"repoName\":\"test\",\"type\":\"Memory\"},\"credentials\":[{\"properties\":{\"password\":\"PASSWORD\",\"user\":\"USER\"},\"type\":\"Credentials\"}]}]}" - cfgaliasdata := "{\"type\":\"credentials.config.ocm.software\",\"aliases\":{\"alias\":{\"repository\":{\"repoName\":\"test\",\"type\":\"Memory\"},\"credentials\":[{\"properties\":{\"password\":\"PASSWORD\",\"user\":\"USER\"},\"type\":\"Credentials\"}]}}}" - _ = props - - Context("serialize", func() { - It("serializes repository spec not in map", func() { - mapdata := "{\"repositories\":{\"repository\":{\"repoName\":\"test\",\"type\":\"Memory\"}}}" - type S struct { - Repositories localconfig.RepositorySpec `json:"repositories"` - } - - rspec, err := credentials.ToGenericRepositorySpec(repospec) - Expect(err).To(Succeed()) - s := &S{ - Repositories: localconfig.RepositorySpec{Repository: *rspec}, - } - data, err := json.Marshal(s) - - Expect(err).To(Succeed()) - Expect(data).To(Equal([]byte(mapdata))) - }) - - It("serializes repository spec map", func() { - mapdata := "{\"repositories\":{\"repo\":{\"repository\":{\"repoName\":\"test\",\"type\":\"Memory\"}}}}" - type S struct { - Repositories map[string]localconfig.RepositorySpec `json:"repositories"` - } - - rspec, err := credentials.ToGenericRepositorySpec(repospec) - Expect(err).To(Succeed()) - s := &S{ - Repositories: map[string]localconfig.RepositorySpec{ - "repo": {Repository: *rspec}, - }, - } - data, err := json.Marshal(s) - Expect(err).To(Succeed()) - Expect(data).To(Equal([]byte(mapdata))) - }) - }) - - Context("composition", func() { - It("composes a config for consumers", func() { - consumerid := credentials.ConsumerIdentity{ - "type": "oci", - "url": "https://acme.com", - } - - cfg := localconfig.New() - - cfg.AddConsumer(consumerid, credspec) - - data, err := json.Marshal(cfg) - Expect(err).To(Succeed()) - Expect(data).To(Equal([]byte(cfgconsumerdata))) - - cfg2 := &localconfig.Config{} - err = json.Unmarshal(data, cfg2) - Expect(err).To(Succeed()) - Expect(cfg2).To(Equal(cfg)) - }) - - It("composes a config for repositories", func() { - cfg := localconfig.New() - - cfg.AddRepository(repospec, direct) - - data, err := json.Marshal(cfg) - Expect(err).To(Succeed()) - Expect(data).To(Equal([]byte(cfgrepodata))) - - cfg2 := &localconfig.Config{} - err = json.Unmarshal(data, cfg2) - Expect(err).To(Succeed()) - Expect(cfg2).To(Equal(cfg)) - }) - - It("composes a config for aliases", func() { - cfg := localconfig.New() - - cfg.AddAlias("alias", repospec, direct) - - data, err := json.Marshal(cfg) - Expect(err).To(Succeed()) - Expect(data).To(Equal([]byte(cfgaliasdata))) - - cfg2 := &localconfig.Config{} - err = json.Unmarshal(data, cfg2) - Expect(err).To(Succeed()) - Expect(cfg2).To(Equal(cfg)) - }) - }) - - Context("apply", func() { - var ctx credentials.Context - - _ = ctx - - BeforeEach(func() { - ctx = credentials.WithConfigs(config.New()).New() - }) - - It("applies a config for aliases", func() { - cfg := localconfig.New() - cfg.AddAlias("alias", repospec, direct) - - ctx.ConfigContext().ApplyConfig(cfg, "testconfig") - - spec := aliases.NewRepositorySpec("alias") - - repo, err := ctx.RepositoryForSpec(spec) - Expect(err).To(Succeed()) - Expect(reflect.TypeOf(repo).String()).To(Equal("*memory.Repository")) - }) - - It("applies a config for consumers", func() { - cfg := localconfig.New() - - consumer := credentials.ConsumerIdentity{ - credentials.ID_TYPE: "mytype", - "host": "localhost", - } - props := common.Properties{"token": "mytoken"} - creds := directcreds.NewCredentials(props) - Expect(cfg.AddConsumer(consumer, creds)).To(Succeed()) - - data, err := runtime.DefaultYAMLEncoding.Marshal(cfg) - Expect(err).To(Succeed()) - Expect(string(data)).To(testutils.StringEqualTrimmedWithContext(` -consumers: -- credentials: - - credentialsName: Credentials - properties: - token: mytoken - type: Credentials - identity: - host: localhost - type: mytype -type: credentials.config.ocm.software -`)) - - ctx.ConfigContext().ApplyConfig(cfg, "testconfig") - - result, err := credentials.CredentialsForConsumer(ctx, consumer, credentials.CompleteMatch) - Expect(err).To(Succeed()) - - Expect(result.Properties()).To(Equal(props)) - }) - - It("applies a config for consumers", func() { - props := common.Properties{"token": "mytoken"} - consumer := credentials.ConsumerIdentity{ - credentials.ID_TYPE: "mytype", - "host": "localhost", - } - data := ` -type: credentials.config.ocm.software -consumers: -- credentials: - - type: Credentials - properties: - token: mytoken - identity: - host: localhost - type: mytype -` - ctx.ConfigContext().ApplyData([]byte(data), nil, "testconfig") - - result, err := credentials.CredentialsForConsumer(ctx, consumer, credentials.CompleteMatch) - Expect(err).To(Succeed()) - - Expect(result.Properties()).To(Equal(props)) - }) - }) -}) diff --git a/pkg/contexts/credentials/config/type.go b/pkg/contexts/credentials/config/type.go deleted file mode 100644 index 9f3fe9ca3..000000000 --- a/pkg/contexts/credentials/config/type.go +++ /dev/null @@ -1,186 +0,0 @@ -package config - -import ( - "fmt" - - "github.com/mandelsoft/goutils/errors" - - cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - ConfigType = "credentials" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX - ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" -) - -func init() { - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1, usage)) -} - -// Config describes a configuration for the config context. -type Config struct { - runtime.ObjectVersionedType `json:",inline"` - // Consumers describe predefine logical cosumer specs mapped to credentials - // These will (potentially) be evaluated if access objects requiring credentials - // are provided by other modules (e.g. oci repo access) without - // specifying crednentials. Then this module can request credentials here by passing - // an appropriate consumer spec. - Consumers []ConsumerSpec `json:"consumers,omitempty"` - // Repositories describe preloaded credential repositories with potential credential chain - Repositories []RepositorySpec `json:"repositories,omitempty"` - // Aliases describe logical credential repositories mapped to implementing repositories - Aliases map[string]RepositorySpec `json:"aliases,omitempty"` -} - -type ConsumerSpec struct { - Identity cpi.ConsumerIdentity `json:"identity"` - Credentials []cpi.GenericCredentialsSpec `json:"credentials"` -} - -type RepositorySpec struct { - Repository cpi.GenericRepositorySpec `json:"repository"` - Credentials []cpi.GenericCredentialsSpec `json:"credentials,omitempty"` -} - -// NewConfigSpec creates a new memory ConfigSpec. -func New() *Config { - return &Config{ - ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), - } -} - -func (a *Config) GetType() string { - return ConfigType -} - -func (a *Config) MapCredentialsChain(creds ...cpi.CredentialsSpec) ([]cpi.GenericCredentialsSpec, error) { - var cgens []cpi.GenericCredentialsSpec - for _, c := range creds { - cgen, err := cpi.ToGenericCredentialsSpec(c) - if err != nil { - return nil, err - } - cgens = append(cgens, *cgen) - } - return cgens, nil -} - -func (a *Config) AddConsumer(id cpi.ConsumerIdentity, creds ...cpi.CredentialsSpec) error { - cgens, err := a.MapCredentialsChain(creds...) - if err != nil { - return fmt.Errorf("failed to map credentials chain: %w", err) - } - - spec := &ConsumerSpec{ - Identity: id, - Credentials: cgens, - } - a.Consumers = append(a.Consumers, *spec) - return nil -} - -func (a *Config) MapRepository(repo cpi.RepositorySpec, creds ...cpi.CredentialsSpec) (*RepositorySpec, error) { - rgen, err := cpi.ToGenericRepositorySpec(repo) - if err != nil { - return nil, err - } - - cgens, err := a.MapCredentialsChain(creds...) - if err != nil { - return nil, err - } - - return &RepositorySpec{ - Repository: *rgen, - Credentials: cgens, - }, nil -} - -func (a *Config) AddRepository(repo cpi.RepositorySpec, creds ...cpi.CredentialsSpec) error { - spec, err := a.MapRepository(repo, creds...) - if err != nil { - return fmt.Errorf("failed to map repository: %w", err) - } - - a.Repositories = append(a.Repositories, *spec) - - return nil -} - -func (a *Config) AddAlias(name string, repo cpi.RepositorySpec, creds ...cpi.CredentialsSpec) error { - spec, err := a.MapRepository(repo, creds...) - if err != nil { - return fmt.Errorf("failed to map repository: %w", err) - } - - if a.Aliases == nil { - a.Aliases = map[string]RepositorySpec{} - } - a.Aliases[name] = *spec - return nil -} - -// --- begin apply --- - -func (a *Config) ApplyTo(ctx cfgcpi.Context, target interface{}) error { - list := errors.ErrListf("applying config") - t, ok := target.(cpi.Context) - if !ok { - return cfgcpi.ErrNoContext(ConfigType) - } - for _, e := range a.Consumers { - t.SetCredentialsForConsumer(e.Identity, CredentialsChain(e.Credentials...)) - } - // --- end apply --- - sub := errors.ErrListf("applying aliases") - for n, e := range a.Aliases { - sub.Add(t.SetAlias(n, &e.Repository, CredentialsChain(e.Credentials...))) - } - list.Add(sub.Result()) - sub = errors.ErrListf("applying repositories") - for i, e := range a.Repositories { - _, err := t.RepositoryForSpec(&e.Repository, CredentialsChain(e.Credentials...)) - sub.Add(errors.Wrapf(err, "repository entry %d", i)) - } - list.Add(sub.Result()) - - return list.Result() -} - -func CredentialsChain(creds ...cpi.GenericCredentialsSpec) cpi.CredentialsChain { - r := make([]cpi.CredentialsSource, len(creds)) - for i := range creds { - r[i] = &creds[i] - } - return r -} - -const usage = ` -The config type ` + ConfigType + ` can be used to define a list -of arbitrary configuration specifications: - -
-    type: ` + ConfigType + `
-    consumers:
-      - identity:
-          <name>: <value>
-          ...
-        credentials:
-          - <credential specification>
-          ... credential chain
-    repositories:
-       - repository: <repository specification>
-         credentials:
-          - <credential specification>
-          ... credential chain
-    aliases:
-       <name>: 
-         repository: <repository specification>
-         credentials:
-          - <credential specification>
-          ... credential chain
-
-` diff --git a/pkg/contexts/credentials/const.go b/pkg/contexts/credentials/const.go deleted file mode 100644 index 13dfe2455..000000000 --- a/pkg/contexts/credentials/const.go +++ /dev/null @@ -1,20 +0,0 @@ -package credentials - -import ( - "github.com/open-component-model/ocm/pkg/contexts/credentials/internal" -) - -const ( - ID_TYPE = internal.ID_TYPE - - ATTR_TYPE = internal.ATTR_TYPE - ATTR_USERNAME = internal.ATTR_USERNAME - ATTR_PASSWORD = internal.ATTR_PASSWORD - ATTR_CERTIFICATE_AUTHORITY = internal.ATTR_CERTIFICATE_AUTHORITY - ATTR_CERTIFICATE = internal.ATTR_CERTIFICATE // PEM encoded - ATTR_PRIVATE_KEY = internal.ATTR_PRIVATE_KEY // PEM encoded - ATTR_SERVER_ADDRESS = internal.ATTR_SERVER_ADDRESS - ATTR_IDENTITY_TOKEN = internal.ATTR_IDENTITY_TOKEN - ATTR_REGISTRY_TOKEN = internal.ATTR_REGISTRY_TOKEN - ATTR_TOKEN = internal.ATTR_TOKEN -) diff --git a/pkg/contexts/credentials/cpi/const.go b/pkg/contexts/credentials/cpi/const.go deleted file mode 100644 index 0c65870f1..000000000 --- a/pkg/contexts/credentials/cpi/const.go +++ /dev/null @@ -1,22 +0,0 @@ -package cpi - -import ( - "github.com/open-component-model/ocm/pkg/contexts/credentials/internal" -) - -const ( - ID_TYPE = internal.ID_TYPE - - ATTR_TYPE = internal.ATTR_TYPE - ATTR_USERNAME = internal.ATTR_USERNAME - ATTR_EMAIL = internal.ATTR_EMAIL - ATTR_PASSWORD = internal.ATTR_PASSWORD - ATTR_SERVER_ADDRESS = internal.ATTR_SERVER_ADDRESS - ATTR_TOKEN = internal.ATTR_TOKEN - ATTR_IDENTITY_TOKEN = internal.ATTR_IDENTITY_TOKEN - ATTR_REGISTRY_TOKEN = internal.ATTR_REGISTRY_TOKEN - ATTR_KEY = internal.ATTR_KEY - ATTR_CERTIFICATE_AUTHORITY = internal.ATTR_CERTIFICATE_AUTHORITY - ATTR_CERTIFICATE = internal.ATTR_CERTIFICATE - ATTR_PRIVATE_KEY = internal.ATTR_PRIVATE_KEY -) diff --git a/pkg/contexts/credentials/cpi/interface.go b/pkg/contexts/credentials/cpi/interface.go deleted file mode 100644 index 2499903c1..000000000 --- a/pkg/contexts/credentials/cpi/interface.go +++ /dev/null @@ -1,128 +0,0 @@ -package cpi - -// This is the Context Provider Interface for credential providers - -import ( - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials/internal" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" -) - -const ( - KIND_CREDENTIALS = internal.KIND_CREDENTIALS - KIND_REPOSITORY = internal.KIND_REPOSITORY -) - -const CONTEXT_TYPE = internal.CONTEXT_TYPE - -type ( - Context = internal.Context - ContextProvider = internal.ContextProvider - Repository = internal.Repository - RepositoryType = internal.RepositoryType - RepositoryTypeProvider = internal.RepositoryTypeProvider - RepositoryTypeScheme = internal.RepositoryTypeScheme - Credentials = internal.Credentials - CredentialsSource = internal.CredentialsSource - CredentialsChain = internal.CredentialsChain - CredentialsSpec = internal.CredentialsSpec - RepositorySpec = internal.RepositorySpec - GenericRepositorySpec = internal.GenericRepositorySpec - GenericCredentialsSpec = internal.GenericCredentialsSpec - DirectCredentials = internal.DirectCredentials - EvaluationContext = internal.EvaluationContext -) - -type ( - ConsumerIdentity = internal.ConsumerIdentity - ConsumerIdentityProvider = internal.ConsumerIdentityProvider - ProviderIdentity = internal.ProviderIdentity - ConsumerProvider = internal.ConsumerProvider - UsageContext = internal.UsageContext - StringUsageContext = internal.StringUsageContext - IdentityMatcher = internal.IdentityMatcher - IdentityMatcherInfo = internal.IdentityMatcherInfo - IdentityMatcherRegistry = internal.IdentityMatcherRegistry -) - -var DefaultContext = internal.DefaultContext - -func FromProvider(p ContextProvider) Context { - return internal.FromProvider(p) -} - -func New(m ...datacontext.BuilderMode) Context { - return internal.Builder{}.New(m...) -} - -func NewConsumerIdentity(typ string, attrs ...string) ConsumerIdentity { - return internal.NewConsumerIdentity(typ, attrs...) -} - -func NewGenericCredentialsSpec(name string, repospec *GenericRepositorySpec) *GenericCredentialsSpec { - return internal.NewGenericCredentialsSpec(name, repospec) -} - -func NewCredentialsSpec(name string, repospec RepositorySpec) CredentialsSpec { - return internal.NewCredentialsSpec(name, repospec) -} - -func ToGenericCredentialsSpec(spec CredentialsSpec) (*GenericCredentialsSpec, error) { - return internal.ToGenericCredentialsSpec(spec) -} - -func ToGenericRepositorySpec(spec RepositorySpec) (*GenericRepositorySpec, error) { - return internal.ToGenericRepositorySpec(spec) -} - -func RegisterStandardIdentityMatcher(typ string, matcher IdentityMatcher, desc string) { - internal.StandardIdentityMatchers.Register(typ, matcher, desc) -} - -func RegisterStandardIdentity(typ string, matcher IdentityMatcher, desc string, attrs string) { - internal.StandardIdentityMatchers.Register(typ, matcher, desc, attrs) -} - -func NewCredentials(props common.Properties) Credentials { - return internal.NewCredentials(props) -} - -func ErrUnknownCredentials(name string) error { - return internal.ErrUnknownCredentials(name) -} - -func ErrUnknownRepository(kind, name string) error { - return internal.ErrUnknownRepository(kind, name) -} - -func CredentialsForConsumer(ctx ContextProvider, id ConsumerIdentity, matchers ...IdentityMatcher) (Credentials, error) { - return internal.CredentialsForConsumer(ctx, id, false, matchers...) -} - -func RequiredCredentialsForConsumer(ctx ContextProvider, id ConsumerIdentity, matchers ...IdentityMatcher) (Credentials, error) { - return internal.CredentialsForConsumer(ctx, id, true, matchers...) -} - -func GetCredentialsForConsumer(ctx Context, ectx EvaluationContext, identity ConsumerIdentity, matchers ...IdentityMatcher) (CredentialsSource, error) { - return internal.GetCredentialsForConsumer(ctx, ectx, identity, matchers...) -} - -func GetEvaluationContextFor[T any](ectx EvaluationContext) T { - return internal.GetEvaluationContextFor[T](ectx) -} - -func SetEvaluationContextFor(ectx EvaluationContext, e any) { - internal.SetEvaluationContextFor(ectx, e) -} - -var ( - CompleteMatch = internal.CompleteMatch - NoMatch = internal.NoMatch - PartialMatch = internal.PartialMatch -) - -// provide context interface for other files to avoid diffs in imports. -var ( - newStrictRepositoryTypeScheme = internal.NewStrictRepositoryTypeScheme - defaultRepositoryTypeScheme = internal.DefaultRepositoryTypeScheme -) diff --git a/pkg/contexts/credentials/cpi/repotypes.go b/pkg/contexts/credentials/cpi/repotypes.go deleted file mode 100644 index d79212ce4..000000000 --- a/pkg/contexts/credentials/cpi/repotypes.go +++ /dev/null @@ -1,49 +0,0 @@ -package cpi - -// this file is identical for contexts oci and credentials and similar for -// ocm. - -import ( - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/runtime/descriptivetype" -) - -type RepositoryTypeVersionScheme = runtime.TypeVersionScheme[RepositorySpec, RepositoryType] - -func NewRepositoryTypeVersionScheme(kind string) RepositoryTypeVersionScheme { - return runtime.NewTypeVersionScheme[RepositorySpec, RepositoryType](kind, newStrictRepositoryTypeScheme()) -} - -func RegisterRepositoryType(rtype RepositoryType) { - defaultRepositoryTypeScheme.Register(rtype) -} - -func RegisterRepositoryTypeVersions(s RepositoryTypeVersionScheme) { - defaultRepositoryTypeScheme.AddKnownTypes(s) -} - -//////////////////////////////////////////////////////////////////////////////// - -func NewRepositoryType[I RepositorySpec](name string, opts ...RepositoryOption) RepositoryType { - return descriptivetype.NewTypedObjectTypeObject(runtime.NewVersionedTypedObjectType[RepositorySpec, I](name), opts...) -} - -func NewRepositoryTypeByConverter[I RepositorySpec, V runtime.TypedObject](name string, converter runtime.Converter[I, V], opts ...RepositoryOption) RepositoryType { - return descriptivetype.NewTypedObjectTypeObject(runtime.NewVersionedTypedObjectTypeByConverter[RepositorySpec, I](name, converter), opts...) -} - -func NewRepositoryTypeByFormatVersion(name string, fmt runtime.FormatVersion[RepositorySpec], opts ...RepositoryOption) RepositoryType { - return descriptivetype.NewTypedObjectTypeObject(runtime.NewVersionedTypedObjectTypeByFormatVersion[RepositorySpec](name, fmt), opts...) -} - -//////////////////////////////////////////////////////////////////////////////// - -type RepositoryOption = descriptivetype.Option - -func WithDescription(v string) RepositoryOption { - return descriptivetype.WithDescription(v) -} - -func WithFormatSpec(v string) RepositoryOption { - return descriptivetype.WithFormatSpec(v) -} diff --git a/pkg/contexts/credentials/gc_test.go b/pkg/contexts/credentials/gc_test.go deleted file mode 100644 index c96f94c41..000000000 --- a/pkg/contexts/credentials/gc_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package credentials_test - -import ( - "runtime" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - me "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/runtimefinalizer" -) - -var _ = Describe("area test", func() { - It("can be garbage collected", func() { - ctx := me.New() - - r := runtimefinalizer.GetRuntimeFinalizationRecorder(ctx) - Expect(r).NotTo(BeNil()) - - runtime.GC() - time.Sleep(time.Second) - ctx.GetType() - Expect(r.Get()).To(BeNil()) - - ctx = nil - for i := 0; i < 100; i++ { - runtime.GC() - time.Sleep(time.Millisecond) - } - - Expect(r.Get()).To(ContainElement(ContainSubstring(me.CONTEXT_TYPE))) - }) -}) diff --git a/pkg/contexts/credentials/identity/hostpath/id_test.go b/pkg/contexts/credentials/identity/hostpath/id_test.go deleted file mode 100644 index bbaaa2007..000000000 --- a/pkg/contexts/credentials/identity/hostpath/id_test.go +++ /dev/null @@ -1,204 +0,0 @@ -package hostpath_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/contexts/credentials/identity/hostpath" -) - -func IdentityMatcher(pattern, cur, id cpi.ConsumerIdentity) bool { - return hostpath.IdentityMatcher("OCIRegistry")(pattern, cur, id) -} - -var _ = Describe("ctf management", func() { - Context("with path", func() { - pat := credentials.ConsumerIdentity{ - hostpath.ID_HOSTNAME: "host", - hostpath.ID_PATHPREFIX: "a/b", - hostpath.ID_PORT: "4711", - hostpath.ID_SCHEME: "scheme://", - } - - It("complete", func() { - id := credentials.ConsumerIdentity{ - hostpath.ID_HOSTNAME: "host", - hostpath.ID_PATHPREFIX: "a/b", - hostpath.ID_PORT: "4711", - hostpath.ID_SCHEME: "scheme://", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) - Expect(IdentityMatcher(pat, id, id)).To(BeFalse()) - }) - - It("path prefix", func() { - id := credentials.ConsumerIdentity{ - hostpath.ID_HOSTNAME: "host", - hostpath.ID_PATHPREFIX: "a", - hostpath.ID_PORT: "4711", - hostpath.ID_SCHEME: "scheme://", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) - Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - It("different prefix", func() { - id := credentials.ConsumerIdentity{ - hostpath.ID_HOSTNAME: "host", - hostpath.ID_PATHPREFIX: "b", - hostpath.ID_PORT: "4711", - hostpath.ID_SCHEME: "scheme://", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) - Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - It("longer prefix", func() { - id := credentials.ConsumerIdentity{ - hostpath.ID_HOSTNAME: "host", - hostpath.ID_PATHPREFIX: "a/b/c", - hostpath.ID_PORT: "4711", - hostpath.ID_SCHEME: "scheme://", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) - Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - It("missing path", func() { - id := credentials.ConsumerIdentity{ - hostpath.ID_HOSTNAME: "host", - hostpath.ID_PORT: "4711", - hostpath.ID_SCHEME: "scheme://", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) - Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - It("missing port", func() { - id := credentials.ConsumerIdentity{ - hostpath.ID_HOSTNAME: "host", - hostpath.ID_PATHPREFIX: "a/b", - hostpath.ID_SCHEME: "scheme://", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) - Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) - - Expect(IdentityMatcher(id, nil, pat)).To(BeTrue()) // accept additional port as fallback - Expect(IdentityMatcher(id, id, pat)).To(BeFalse()) // but not to replace more general match - }) - It("different port", func() { - id := credentials.ConsumerIdentity{ - hostpath.ID_HOSTNAME: "host", - hostpath.ID_PATHPREFIX: "a/b", - hostpath.ID_PORT: "0815", - hostpath.ID_SCHEME: "scheme://", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) - Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - - It("different host", func() { - id := credentials.ConsumerIdentity{ - hostpath.ID_HOSTNAME: "other", - hostpath.ID_PATHPREFIX: "a/b", - hostpath.ID_PORT: "4711", - hostpath.ID_SCHEME: "scheme://", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) - Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - It("no host", func() { - id := credentials.ConsumerIdentity{ - hostpath.ID_PATHPREFIX: "a/b", - hostpath.ID_PORT: "4711", - hostpath.ID_SCHEME: "scheme://", - } - Expect(IdentityMatcher(id, nil, pat)).To(BeTrue()) - Expect(IdentityMatcher(pat, id, id)).To(BeFalse()) - Expect(IdentityMatcher(pat, id, pat)).To(BeTrue()) - }) - - It("different scheme", func() { - id := credentials.ConsumerIdentity{ - hostpath.ID_HOSTNAME: "host", - hostpath.ID_PATHPREFIX: "a/b", - hostpath.ID_PORT: "4711", - hostpath.ID_SCHEME: "otherscheme://", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) - Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - It("no scheme", func() { - id := credentials.ConsumerIdentity{ - hostpath.ID_HOSTNAME: "host", - hostpath.ID_PATHPREFIX: "a/b", - hostpath.ID_PORT: "4711", - } - Expect(IdentityMatcher(id, nil, pat)).To(BeTrue()) - Expect(IdentityMatcher(pat, id, id)).To(BeFalse()) - Expect(IdentityMatcher(pat, id, pat)).To(BeTrue()) - }) - }) - - Context("without path", func() { - pat := credentials.ConsumerIdentity{ - hostpath.ID_HOSTNAME: "host", - hostpath.ID_PORT: "4711", - hostpath.ID_SCHEME: "scheme://", - } - - It("complete", func() { - id := credentials.ConsumerIdentity{ - hostpath.ID_HOSTNAME: "host", - hostpath.ID_PORT: "4711", - hostpath.ID_SCHEME: "scheme://", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) - Expect(IdentityMatcher(pat, id, id)).To(BeFalse()) - }) - - It("different prefix", func() { - id := credentials.ConsumerIdentity{ - hostpath.ID_HOSTNAME: "host", - hostpath.ID_PORT: "4711", - hostpath.ID_PATHPREFIX: "b", - hostpath.ID_SCHEME: "scheme://", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) - Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - It("missing port", func() { - id := credentials.ConsumerIdentity{ - hostpath.ID_HOSTNAME: "host", - hostpath.ID_SCHEME: "scheme://", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) - Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - It("different port", func() { - id := credentials.ConsumerIdentity{ - hostpath.ID_HOSTNAME: "host", - hostpath.ID_PORT: "0815", - hostpath.ID_SCHEME: "scheme://", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) - Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - - It("different scheme", func() { - id := credentials.ConsumerIdentity{ - hostpath.ID_HOSTNAME: "host", - hostpath.ID_PORT: "4711", - hostpath.ID_SCHEME: "otherscheme://", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeFalse()) - Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - It("no scheme", func() { - id := credentials.ConsumerIdentity{ - hostpath.ID_HOSTNAME: "host", - hostpath.ID_PORT: "4711", - } - Expect(IdentityMatcher(pat, nil, id)).To(BeTrue()) - Expect(IdentityMatcher(pat, pat, id)).To(BeFalse()) - }) - }) -}) diff --git a/pkg/contexts/credentials/identity/hostpath/identity.go b/pkg/contexts/credentials/identity/hostpath/identity.go deleted file mode 100644 index 7cd128d78..000000000 --- a/pkg/contexts/credentials/identity/hostpath/identity.go +++ /dev/null @@ -1,148 +0,0 @@ -package hostpath - -import ( - "net/url" - "strings" - - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" -) - -// IDENTITY_TYPE is the identity of this matcher. -const IDENTITY_TYPE = "hostpath" - -// ID_TYPE is the type of the consumer. -const ID_TYPE = cpi.ID_TYPE - -// ID_HOSTNAME is a hostname. -const ID_HOSTNAME = "hostname" - -// ID_PORT is a port. -const ID_PORT = "port" - -// ID_PATHPREFIX is the path prefix below the host. -const ID_PATHPREFIX = "pathprefix" - -// ID_SCHEME is the scheme prefix. -const ID_SCHEME = "scheme" - -func init() { - cpi.RegisterStandardIdentityMatcher(IDENTITY_TYPE, Matcher, `Host and path based credential matcher - -This matcher works on the following properties: - -- *`+ID_TYPE+`* (required if set in pattern): the identity type -- *`+ID_HOSTNAME+`* (required if set in pattern): the hostname of a server -- *`+ID_SCHEME+`* (optional): the URL scheme of a server -- *`+ID_PORT+`* (optional): the port of a server -- *`+ID_PATHPREFIX+`* (optional): a path prefix to match. The - element with the most matching path components is selected (separator is /). -`) -} - -var Matcher = IdentityMatcher("") - -func Match(identityType string, request, cur, id cpi.ConsumerIdentity) (match bool, better bool) { - if request[ID_TYPE] != "" && request[ID_TYPE] != id[ID_TYPE] { - return false, false - } - - if identityType != "" && request[ID_TYPE] != "" && identityType != request[ID_TYPE] { - return false, false - } - - if request[ID_HOSTNAME] != "" && id[ID_HOSTNAME] != "" && request[ID_HOSTNAME] != id[ID_HOSTNAME] { - return false, false - } - - if request[ID_PORT] != "" { - if id[ID_PORT] != "" && id[ID_PORT] != request[ID_PORT] { - return false, false - } - } - - if request[ID_SCHEME] != "" { - if id[ID_SCHEME] != "" && id[ID_SCHEME] != request[ID_SCHEME] { - return false, false - } - } - - if request[ID_PATHPREFIX] != "" { - if id[ID_PATHPREFIX] != "" { - if len(id[ID_PATHPREFIX]) > len(request[ID_PATHPREFIX]) { - return false, false - } - pcomps := strings.Split(request[ID_PATHPREFIX], "/") - icomps := strings.Split(id[ID_PATHPREFIX], "/") - if len(icomps) > len(pcomps) { - return false, false - } - for i := range icomps { - if pcomps[i] != icomps[i] { - return false, false - } - } - } - } else { - if id[ID_PATHPREFIX] != "" { - return false, false - } - } - - // ok now it basically matches, check against current match - if len(cur) == 0 { - return true, true - } - - if cur[ID_HOSTNAME] == "" && id[ID_HOSTNAME] != "" { - return true, true - } - if cur[ID_PORT] == "" && (id[ID_PORT] != "" && request[ID_PORT] != "") { - return true, true - } - if cur[ID_SCHEME] == "" && (id[ID_SCHEME] != "" && request[ID_SCHEME] != "") { - return true, true - } - - if len(cur[ID_PATHPREFIX]) < len(id[ID_PATHPREFIX]) { - return true, true - } - return true, false -} - -func IdentityMatcher(identityType string) cpi.IdentityMatcher { - return func(request, cur, id cpi.ConsumerIdentity) bool { - _, better := Match(identityType, request, cur, id) - return better - } -} - -func GetConsumerIdentity(typ, _url string) cpi.ConsumerIdentity { - u, err := url.Parse(_url) - if err != nil { - return nil - } - - id := cpi.NewConsumerIdentity(typ) - if u.Host != "" { - parts := strings.Split(u.Host, ":") - if len(parts) > 1 { - id[ID_PORT] = parts[1] - } else { - switch u.Scheme { - case "https": - id[ID_PORT] = "443" - case "http": - id[ID_PORT] = "80" - } - } - id[ID_HOSTNAME] = parts[0] - } - if u.Scheme != "" { - id[ID_SCHEME] = u.Scheme - } - path := strings.Trim(u.Path, "/") - if path != "" { - id[ID_PATHPREFIX] = path - } - return id -} diff --git a/pkg/contexts/credentials/init.go b/pkg/contexts/credentials/init.go deleted file mode 100644 index 1ee7dbf31..000000000 --- a/pkg/contexts/credentials/init.go +++ /dev/null @@ -1,7 +0,0 @@ -package credentials - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin" - _ "github.com/open-component-model/ocm/pkg/contexts/credentials/config" - _ "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories" -) diff --git a/pkg/contexts/credentials/interface.go b/pkg/contexts/credentials/interface.go deleted file mode 100644 index 34f1e74a5..000000000 --- a/pkg/contexts/credentials/interface.go +++ /dev/null @@ -1,134 +0,0 @@ -package credentials - -import ( - "context" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials/internal" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/directcreds" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - KIND_CREDENTIALS = internal.KIND_CREDENTIALS - KIND_CONSUMER = internal.KIND_CONSUMER - KIND_REPOSITORY = internal.KIND_REPOSITORY -) - -const CONTEXT_TYPE = internal.CONTEXT_TYPE - -const AliasRepositoryType = internal.AliasRepositoryType - -type ( - Context = internal.Context - ContextProvider = internal.ContextProvider - RepositoryTypeScheme = internal.RepositoryTypeScheme - Repository = internal.Repository - Credentials = internal.Credentials - CredentialsSource = internal.CredentialsSource - CredentialsChain = internal.CredentialsChain - CredentialsSpec = internal.CredentialsSpec - RepositorySpec = internal.RepositorySpec -) - -type ( - ConsumerIdentity = internal.ConsumerIdentity - ConsumerIdentityProvider = internal.ConsumerIdentityProvider - ProviderIdentity = internal.ProviderIdentity - UsageContext = internal.UsageContext - StringUsageContext = internal.StringUsageContext - IdentityMatcher = internal.IdentityMatcher - IdentityMatcherInfo = internal.IdentityMatcherInfo - IdentityMatcherInfos = internal.IdentityMatcherInfos - IdentityMatcherRegistry = internal.IdentityMatcherRegistry -) - -type ( - GenericRepositorySpec = internal.GenericRepositorySpec - GenericCredentialsSpec = internal.GenericCredentialsSpec - DirectCredentials = internal.DirectCredentials -) - -func DefaultContext() internal.Context { - return internal.DefaultContext -} - -func FromContext(ctx context.Context) Context { - return internal.FromContext(ctx) -} - -func FromProvider(p ContextProvider) Context { - return internal.FromProvider(p) -} - -func DefinedForContext(ctx context.Context) (Context, bool) { - return internal.DefinedForContext(ctx) -} - -func NewCredentialsSpec(name string, repospec RepositorySpec) CredentialsSpec { - return internal.NewCredentialsSpec(name, repospec) -} - -func NewGenericCredentialsSpec(name string, repospec *GenericRepositorySpec) CredentialsSpec { - return internal.NewGenericCredentialsSpec(name, repospec) -} - -func NewGenericRepositorySpec(data []byte, unmarshaler runtime.Unmarshaler) (RepositorySpec, error) { - return internal.NewGenericRepositorySpec(data, unmarshaler) -} - -func NewCredentials(props common.Properties) Credentials { - return internal.NewCredentials(props) -} - -func CredentialsFromList(props ...string) Credentials { - creds := DirectCredentials{} - for i := 1; i < len(props); i += 2 { - creds[props[i-1]] = props[i] - } - return creds -} - -func CredentialsSpecFromList(props ...string) CredentialsSpec { - creds := DirectCredentials{} - for i := 1; i < len(props); i += 2 { - creds[props[i-1]] = props[i] - } - return directcreds.NewCredentials(creds.Properties()) -} - -func ToGenericCredentialsSpec(spec CredentialsSpec) (*GenericCredentialsSpec, error) { - return internal.ToGenericCredentialsSpec(spec) -} - -func ToGenericRepositorySpec(spec RepositorySpec) (*GenericRepositorySpec, error) { - return internal.ToGenericRepositorySpec(spec) -} - -func ErrUnknownCredentials(name string) error { - return internal.ErrUnknownCredentials(name) -} - -// CredentialsForConsumer determine effective credentials for a consumer. -// If no credentials are configured no error and nil is returned. -// It evaluates a found credentials source for the consumer to determine the -// final credential properties. -func CredentialsForConsumer(ctx ContextProvider, id ConsumerIdentity, matchers ...IdentityMatcher) (Credentials, error) { - return internal.CredentialsForConsumer(ctx, id, false, matchers...) -} - -// RequiredCredentialsForConsumer like CredentialsForConsumer, but an errors is returned -// if no credentials are found. -func RequiredCredentialsForConsumer(ctx ContextProvider, id ConsumerIdentity, matchers ...IdentityMatcher) (Credentials, error) { - return internal.CredentialsForConsumer(ctx, id, true, matchers...) -} - -var ( - CompleteMatch = internal.CompleteMatch - NoMatch = internal.NoMatch - PartialMatch = internal.PartialMatch -) - -func NewConsumerIdentity(typ string, attrs ...string) ConsumerIdentity { - return internal.NewConsumerIdentity(typ, attrs...) -} diff --git a/pkg/contexts/credentials/internal/builder.go b/pkg/contexts/credentials/internal/builder.go deleted file mode 100644 index 41cb04c55..000000000 --- a/pkg/contexts/credentials/internal/builder.go +++ /dev/null @@ -1,79 +0,0 @@ -package internal - -import ( - "context" - - "github.com/open-component-model/ocm/pkg/contexts/config" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" -) - -type Builder struct { - ctx context.Context - config config.Context - reposcheme RepositoryTypeScheme - matchers IdentityMatcherRegistry -} - -func (b *Builder) getContext() context.Context { - if b.ctx == nil { - return context.Background() - } - return b.ctx -} - -func (b Builder) WithContext(ctx context.Context) Builder { - b.ctx = ctx - return b -} - -func (b Builder) WithConfig(ctx config.Context) Builder { - b.config = ctx - return b -} - -func (b Builder) WithRepositoyTypeScheme(scheme RepositoryTypeScheme) Builder { - b.reposcheme = scheme - return b -} - -func (b Builder) WithStandardConumerMatchers(matchers IdentityMatcherRegistry) Builder { - b.matchers = matchers - return b -} - -func (b Builder) Bound() (Context, context.Context) { - c := b.New() - return c, context.WithValue(b.getContext(), key, c) -} - -func (b Builder) New(m ...datacontext.BuilderMode) Context { - mode := datacontext.Mode(m...) - ctx := b.getContext() - - if b.config == nil { - var ok bool - b.config, ok = config.DefinedForContext(ctx) - if !ok && mode != datacontext.MODE_SHARED { - b.config = config.New(mode) - } - } - if b.reposcheme == nil { - switch mode { - case datacontext.MODE_INITIAL: - b.reposcheme = NewRepositoryTypeScheme(nil) - case datacontext.MODE_CONFIGURED: - b.reposcheme = NewRepositoryTypeScheme(nil) - b.reposcheme.AddKnownTypes(DefaultRepositoryTypeScheme) - case datacontext.MODE_EXTENDED: - b.reposcheme = NewRepositoryTypeScheme(nil, DefaultRepositoryTypeScheme) - case datacontext.MODE_DEFAULTED: - fallthrough - case datacontext.MODE_SHARED: - b.reposcheme = DefaultRepositoryTypeScheme - } - } - if b.matchers == nil { - b.matchers = StandardIdentityMatchers - } - return datacontext.SetupContext(mode, newContext(b.config, b.reposcheme, b.matchers, b.config)) -} diff --git a/pkg/contexts/credentials/internal/builder_test.go b/pkg/contexts/credentials/internal/builder_test.go deleted file mode 100644 index d686a7d48..000000000 --- a/pkg/contexts/credentials/internal/builder_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package internal_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/config" - local "github.com/open-component-model/ocm/pkg/contexts/credentials/internal" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" -) - -var _ = Describe("builder test", func() { - It("creates local", func() { - ctx := local.Builder{}.New(datacontext.MODE_SHARED) - - Expect(ctx.AttributesContext()).To(BeIdenticalTo(datacontext.DefaultContext)) - Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) - Expect(ctx.RepositoryTypes()).To(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) - - Expect(ctx.ConfigContext().GetId()).To(BeIdenticalTo(config.DefaultContext().GetId())) - }) - - It("creates defaulted", func() { - ctx := local.Builder{}.New(datacontext.MODE_DEFAULTED) - - Expect(ctx.AttributesContext()).NotTo(BeIdenticalTo(datacontext.DefaultContext)) - Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) - Expect(ctx.RepositoryTypes()).To(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) - - Expect(ctx.ConfigContext().GetId()).NotTo(BeIdenticalTo(config.DefaultContext().GetType())) - Expect(ctx.ConfigContext().ConfigTypes()).To(BeIdenticalTo(config.DefaultContext().ConfigTypes())) - }) - - It("creates configured", func() { - ctx := local.Builder{}.New(datacontext.MODE_CONFIGURED) - - Expect(ctx.AttributesContext()).NotTo(BeIdenticalTo(datacontext.DefaultContext)) - Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) - Expect(ctx.RepositoryTypes()).NotTo(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) - Expect(ctx.RepositoryTypes().KnownTypeNames()).To(Equal(local.DefaultRepositoryTypeScheme.KnownTypeNames())) - - Expect(ctx.ConfigContext().GetId()).NotTo(BeIdenticalTo(config.DefaultContext().GetId())) - Expect(ctx.ConfigContext().ConfigTypes()).NotTo(BeIdenticalTo(config.DefaultContext().ConfigTypes())) - Expect(ctx.ConfigContext().ConfigTypes().KnownTypeNames()).To(Equal(config.DefaultContext().ConfigTypes().KnownTypeNames())) - }) - - It("creates iniial", func() { - ctx := local.Builder{}.New(datacontext.MODE_INITIAL) - - Expect(ctx.AttributesContext()).NotTo(BeIdenticalTo(datacontext.DefaultContext)) - Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) - Expect(ctx.RepositoryTypes()).NotTo(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) - Expect(len(ctx.RepositoryTypes().KnownTypeNames())).To(Equal(0)) - - Expect(ctx.ConfigContext()).NotTo(BeIdenticalTo(config.DefaultContext())) - Expect(len(ctx.ConfigContext().ConfigTypes().KnownTypeNames())).To(Equal(0)) - }) -}) diff --git a/pkg/contexts/credentials/internal/context.go b/pkg/contexts/credentials/internal/context.go deleted file mode 100644 index 09c6e297e..000000000 --- a/pkg/contexts/credentials/internal/context.go +++ /dev/null @@ -1,322 +0,0 @@ -package internal - -import ( - "context" - "fmt" - "reflect" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/generics" - "github.com/mandelsoft/goutils/maputils" - "golang.org/x/exp/maps" - - "github.com/open-component-model/ocm/pkg/contexts/config" - cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/runtimefinalizer" - "github.com/open-component-model/ocm/pkg/utils" -) - -// CONTEXT_TYPE is the global type for a credential context. -const CONTEXT_TYPE = "credentials" + datacontext.OCM_CONTEXT_SUFFIX - -// ProviderIdentity is used to uniquely identify a provider -// for a configured consumer id. If non-empty it -// must start with a DNSname identifying the origin of the -// provider followed by a slash and a local arbitrary identity. -type ProviderIdentity = runtimefinalizer.ObjectIdentity - -type ContextProvider interface { - CredentialsContext() Context -} - -type ConsumerProvider interface { - Unregister(id ProviderIdentity) - Get(id ConsumerIdentity) (CredentialsSource, bool) - Match(ectx EvaluationContext, id ConsumerIdentity, cur ConsumerIdentity, matcher IdentityMatcher) (CredentialsSource, ConsumerIdentity) -} - -type EvaluationContext *evaluationContext - -type evaluationContext struct { - data map[reflect.Type]interface{} -} - -func (e evaluationContext) String() string { - return fmt.Sprintf("%v", maputils.Transform(e.data, func(k reflect.Type, v interface{}) (string, string) { - return k.Name(), fmt.Sprintf("%v", v) - })) -} - -func GetEvaluationContextFor[T any](ectx EvaluationContext) T { - var _nil T - if ectx.data == nil { - return _nil - } - return generics.Cast[T](ectx.data[generics.TypeOf[T]()]) -} - -func SetEvaluationContextFor(ectx EvaluationContext, e any) EvaluationContext { - if ectx.data == nil { - ectx.data = map[reflect.Type]interface{}{} - } - n := &evaluationContext{maps.Clone(ectx.data)} - n.data[reflect.TypeOf(e)] = e - return n -} - -type Context interface { - datacontext.Context - ContextProvider - config.ContextProvider - - AttributesContext() datacontext.AttributesContext - RepositoryTypes() RepositoryTypeScheme - - RepositorySpecForConfig(data []byte, unmarshaler runtime.Unmarshaler) (RepositorySpec, error) - - RepositoryForSpec(spec RepositorySpec, creds ...CredentialsSource) (Repository, error) - RepositoryForConfig(data []byte, unmarshaler runtime.Unmarshaler, creds ...CredentialsSource) (Repository, error) - - CredentialsForSpec(spec CredentialsSpec, creds ...CredentialsSource) (Credentials, error) - CredentialsForConfig(data []byte, unmarshaler runtime.Unmarshaler, cred ...CredentialsSource) (Credentials, error) - - RegisterConsumerProvider(id ProviderIdentity, provider ConsumerProvider) - UnregisterConsumerProvider(id ProviderIdentity) - - GetCredentialsForConsumer(ConsumerIdentity, ...IdentityMatcher) (CredentialsSource, error) - getCredentialsForConsumer(EvaluationContext, ConsumerIdentity, ...IdentityMatcher) (CredentialsSource, error) - SetCredentialsForConsumer(identity ConsumerIdentity, creds CredentialsSource) - SetCredentialsForConsumerWithProvider(pid ProviderIdentity, identity ConsumerIdentity, creds CredentialsSource) - - SetAlias(name string, spec RepositorySpec, creds ...CredentialsSource) error - - ConsumerIdentityMatchers() IdentityMatcherRegistry -} - -var key = reflect.TypeOf(_context{}) - -// DefaultContext is the default context initialized by init functions. -var DefaultContext = Builder{}.New(datacontext.MODE_SHARED) - -// FromContext returns the Context to use for context.Context. -// This is either an explicit context or the default context. -func FromContext(ctx context.Context) Context { - c, _ := datacontext.ForContextByKey(ctx, key, DefaultContext) - return c.(Context) -} - -func FromProvider(p ContextProvider) Context { - if p == nil { - return nil - } - return p.CredentialsContext() -} - -func DefinedForContext(ctx context.Context) (Context, bool) { - c, ok := datacontext.ForContextByKey(ctx, key, DefaultContext) - if c != nil { - return c.(Context), ok - } - return nil, ok -} - -type _InternalContext = datacontext.InternalContext - -type _context struct { - _InternalContext - - sharedattributes datacontext.AttributesContext - updater cfgcpi.Updater - knownRepositoryTypes RepositoryTypeScheme - consumerIdentityMatchers IdentityMatcherRegistry - consumerProviders *consumerProviderRegistry -} - -var ( - _ Context = (*_context)(nil) - _ datacontext.ViewCreator[Context] = (*_context)(nil) -) - -// gcWrapper is used as garbage collectable -// wrapper for a context implementation -// to establish a runtime finalizer. -type gcWrapper struct { - datacontext.GCWrapper - *_context -} - -func newView(c *_context, ref ...bool) Context { - if utils.Optional(ref...) { - return datacontext.FinalizedContext[gcWrapper](c) - } - return c -} - -func (w *gcWrapper) SetContext(c *_context) { - w._context = c -} - -func newContext(configctx config.Context, reposcheme RepositoryTypeScheme, consumerMatchers IdentityMatcherRegistry, delegates datacontext.Delegates) Context { - c := &_context{ - sharedattributes: datacontext.PersistentContextRef(configctx.AttributesContext()), - knownRepositoryTypes: reposcheme, - consumerIdentityMatchers: consumerMatchers, - consumerProviders: newConsumerProviderRegistry(), - } - c._InternalContext = datacontext.NewContextBase(c, CONTEXT_TYPE, key, configctx.GetAttributes(), delegates) - c.updater = cfgcpi.NewUpdaterForFactory(datacontext.PersistentContextRef(configctx), c.CredentialsContext) - return newView(c, true) -} - -func (c *_context) CreateView() Context { - return newView(c, true) -} - -func (c *_context) CredentialsContext() Context { - return newView(c) -} - -func (c *_context) Update() error { - return c.updater.Update() -} - -func (c *_context) GetType() string { - return CONTEXT_TYPE -} - -func (c *_context) AttributesContext() datacontext.AttributesContext { - return c.sharedattributes -} - -func (c *_context) ConfigContext() config.Context { - return c.updater.GetContext() -} - -func (c *_context) RepositoryTypes() RepositoryTypeScheme { - return c.knownRepositoryTypes -} - -func (c *_context) RepositorySpecForConfig(data []byte, unmarshaler runtime.Unmarshaler) (RepositorySpec, error) { - return c.knownRepositoryTypes.Decode(data, unmarshaler) -} - -func (c *_context) RepositoryForSpec(spec RepositorySpec, creds ...CredentialsSource) (Repository, error) { - out := newView(c) - cred, err := CredentialsChain(creds).Credentials(out) - if err != nil { - return nil, err - } - c.Update() - return spec.Repository(out, cred) -} - -func (c *_context) RepositoryForConfig(data []byte, unmarshaler runtime.Unmarshaler, creds ...CredentialsSource) (Repository, error) { - spec, err := c.knownRepositoryTypes.Decode(data, unmarshaler) - if err != nil { - return nil, err - } - return c.RepositoryForSpec(spec, creds...) -} - -func (c *_context) CredentialsForSpec(spec CredentialsSpec, creds ...CredentialsSource) (Credentials, error) { - out := newView(c) - repospec := spec.GetRepositorySpec(out) - repo, err := c.RepositoryForSpec(repospec, creds...) - if err != nil { - return nil, err - } - return repo.LookupCredentials(spec.GetCredentialsName()) -} - -func (c *_context) CredentialsForConfig(data []byte, unmarshaler runtime.Unmarshaler, creds ...CredentialsSource) (Credentials, error) { - spec := &GenericCredentialsSpec{} - err := unmarshaler.Unmarshal(data, spec) - if err != nil { - return nil, err - } - return c.CredentialsForSpec(spec, creds...) -} - -var emptyIdentity = ConsumerIdentity{} - -func (c *_context) GetCredentialsForConsumer(identity ConsumerIdentity, matchers ...IdentityMatcher) (CredentialsSource, error) { - return c.getCredentialsForConsumer(nil, identity, matchers...) -} - -func (c *_context) getCredentialsForConsumer(ectx EvaluationContext, identity ConsumerIdentity, matchers ...IdentityMatcher) (CredentialsSource, error) { - err := c.Update() - if err != nil { - return nil, err - } - - if ectx == nil { - ectx = &evaluationContext{} - } - m := c.defaultMatcher(identity, matchers...) - var credsrc CredentialsSource - if m == nil { - credsrc, _ = c.consumerProviders.Get(identity) - } else { - credsrc, _ = c.consumerProviders.Match(ectx, identity, nil, m) - } - if credsrc == nil { - credsrc, _ = c.consumerProviders.Get(emptyIdentity) - } - if credsrc == nil { - return nil, ErrUnknownConsumer(identity.String()) - } - return credsrc, nil -} - -func (c *_context) defaultMatcher(id ConsumerIdentity, matchers ...IdentityMatcher) IdentityMatcher { - def := c.consumerIdentityMatchers.Get(id.Type()) - if def == nil { - def = PartialMatch - } - return mergeMatcher(def, andMatcher, matchers) -} - -func (c *_context) SetCredentialsForConsumer(identity ConsumerIdentity, creds CredentialsSource) { - c.Update() - c.consumerProviders.Set(identity, "", creds) -} - -func (c *_context) SetCredentialsForConsumerWithProvider(pid ProviderIdentity, identity ConsumerIdentity, creds CredentialsSource) { - c.Update() - c.consumerProviders.Set(identity, pid, creds) -} - -func (c *_context) ConsumerIdentityMatchers() IdentityMatcherRegistry { - return c.consumerIdentityMatchers -} - -func (c *_context) SetAlias(name string, spec RepositorySpec, creds ...CredentialsSource) error { - c.Update() - t := c.knownRepositoryTypes.GetType(AliasRepositoryType) - if t == nil { - return errors.ErrNotSupported("aliases") - } - if a, ok := t.(AliasRegistry); ok { - return a.SetAlias(c, name, spec, CredentialsChain(creds)) - } - return errors.ErrNotImplemented("interface", "AliasRegistry", reflect.TypeOf(t).String()) -} - -func (c *_context) RegisterConsumerProvider(id ProviderIdentity, provider ConsumerProvider) { - c.consumerProviders.Register(id, provider) -} - -func (c *_context) UnregisterConsumerProvider(id ProviderIdentity) { - c.consumerProviders.Unregister(id) -} - -/////////////////////////////////////// - -func GetCredentialsForConsumer(ctx Context, ectx EvaluationContext, identity ConsumerIdentity, matchers ...IdentityMatcher) (CredentialsSource, error) { - if ectx == nil { - ectx = &evaluationContext{} - } - return ctx.getCredentialsForConsumer(ectx, identity, matchers...) -} diff --git a/pkg/contexts/credentials/internal/cred_test.go b/pkg/contexts/credentials/internal/cred_test.go deleted file mode 100644 index 26204a994..000000000 --- a/pkg/contexts/credentials/internal/cred_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package internal_test - -import ( - "encoding/json" - "reflect" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/internal" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/memory" -) - -var DefaultContext = credentials.New() - -var _ = Describe("generic credentials", func() { - props := common.Properties{ - "user": "USER", - "password": "PASSWORD", - } - credmemdata := "{\"credentialsName\":\"cred\",\"repoName\":\"test\",\"type\":\"Memory\"}" - memdata := "{\"repoName\":\"test\",\"type\":\"Memory\"}" - - _ = props - - It("de/serializes credentials spec", func() { - repospec := memory.NewRepositorySpec("test") - credspec := credentials.NewCredentialsSpec("cred", repospec) - - data, err := json.Marshal(credspec) - Expect(err).To(Succeed()) - Expect(data).To(Equal([]byte(credmemdata))) - - credspec = &internal.DefaultCredentialsSpec{} - err = json.Unmarshal(data, credspec) - Expect(err).To(Succeed()) - s := credspec.(*internal.DefaultCredentialsSpec) - Expect(reflect.TypeOf(s.RepositorySpec).String()).To(Equal("*memory.RepositorySpec")) - Expect(s.CredentialsName).To(Equal("cred")) - Expect(s.RepositorySpec.(*memory.RepositorySpec).RepositoryName).To(Equal("test")) - }) - - It("de/serializes generic credentials spec", func() { - credspec := &internal.GenericCredentialsSpec{} - - err := json.Unmarshal([]byte(credmemdata), credspec) - Expect(err).To(Succeed()) - - data, err := json.Marshal(credspec) - Expect(err).To(Succeed()) - Expect(data).To(Equal([]byte(credmemdata))) - }) - - It("de/serializes generic repository spec", func() { - credspec := &internal.GenericRepositorySpec{} - - err := json.Unmarshal([]byte(memdata), credspec) - Expect(err).To(Succeed()) - - data, err := json.Marshal(credspec) - Expect(err).To(Succeed()) - Expect(data).To(Equal([]byte(memdata))) - }) - - It("converts credentials spec to generic ones", func() { - repospec := memory.NewRepositorySpec("test") - credspec := credentials.NewCredentialsSpec("cred", repospec) - data, err := json.Marshal(credspec) - Expect(err).To(Succeed()) - - gen, err := credentials.ToGenericCredentialsSpec(credspec) - Expect(err).To(Succeed()) - - Expect(reflect.TypeOf(gen).String()).To(Equal("*internal.GenericCredentialsSpec")) - Expect(reflect.TypeOf(gen.RepositorySpec).String()).To(Equal("*internal.GenericRepositorySpec")) - - gen2, err := credentials.ToGenericCredentialsSpec(gen) - Expect(err).To(Succeed()) - Expect(gen2).To(BeIdenticalTo(gen)) - - data3, err := json.Marshal(gen) - Expect(err).To(Succeed()) - Expect(data3).To(Equal(data)) - }) -}) diff --git a/pkg/contexts/credentials/internal/logging.go b/pkg/contexts/credentials/internal/logging.go deleted file mode 100644 index 6934cbe1e..000000000 --- a/pkg/contexts/credentials/internal/logging.go +++ /dev/null @@ -1,10 +0,0 @@ -package internal - -import ( - ocmlog "github.com/open-component-model/ocm/pkg/logging" -) - -var ( - REALM = ocmlog.DefineSubRealm("Credentials", "credentials") - log = ocmlog.DynamicLogger(REALM) -) diff --git a/pkg/contexts/credentials/internal/repository.go b/pkg/contexts/credentials/internal/repository.go deleted file mode 100644 index 171477cea..000000000 --- a/pkg/contexts/credentials/internal/repository.go +++ /dev/null @@ -1,63 +0,0 @@ -package internal - -import ( - "github.com/mandelsoft/goutils/set" - - "github.com/open-component-model/ocm/pkg/common" -) - -type Repository interface { - ExistsCredentials(name string) (bool, error) - LookupCredentials(name string) (Credentials, error) - WriteCredentials(name string, creds Credentials) (Credentials, error) -} - -type Credentials interface { - CredentialsSource - ExistsProperty(name string) bool - GetProperty(name string) string - PropertyNames() set.Set[string] - Properties() common.Properties -} - -type DirectCredentials common.Properties - -var _ Credentials = (*DirectCredentials)(nil) - -func NewCredentials(props common.Properties) DirectCredentials { - if props == nil { - props = common.Properties{} - } else { - props = props.Copy() - } - return DirectCredentials(props) -} - -func (c DirectCredentials) ExistsProperty(name string) bool { - _, ok := c[name] - return ok -} - -func (c DirectCredentials) GetProperty(name string) string { - return c[name] -} - -func (c DirectCredentials) PropertyNames() set.Set[string] { - return common.Properties(c).Names() -} - -func (c DirectCredentials) Properties() common.Properties { - return common.Properties(c).Copy() -} - -func (c DirectCredentials) Credentials(Context, ...CredentialsSource) (Credentials, error) { - return c, nil -} - -func (c DirectCredentials) Copy() DirectCredentials { - return DirectCredentials(common.Properties(c).Copy()) -} - -func (c DirectCredentials) String() string { - return common.Properties(c).String() -} diff --git a/pkg/contexts/credentials/internal/repotypes.go b/pkg/contexts/credentials/internal/repotypes.go deleted file mode 100644 index 37d3f94df..000000000 --- a/pkg/contexts/credentials/internal/repotypes.go +++ /dev/null @@ -1,138 +0,0 @@ -package internal - -import ( - "encoding/json" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/generics" - "github.com/modern-go/reflect2" - - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/runtime/descriptivetype" - "github.com/open-component-model/ocm/pkg/utils" -) - -type RepositoryType interface { - descriptivetype.TypedObjectType[RepositorySpec] -} - -type RepositorySpec interface { - runtime.VersionedTypedObject - - Repository(Context, Credentials) (Repository, error) -} - -type ( - RepositorySpecDecoder = runtime.TypedObjectDecoder[RepositorySpec] - RepositoryTypeProvider = runtime.KnownTypesProvider[RepositorySpec, RepositoryType] -) - -type RepositoryTypeScheme interface { - descriptivetype.TypeScheme[RepositorySpec, RepositoryType] -} - -type _Scheme = descriptivetype.TypeScheme[RepositorySpec, RepositoryType] - -type repositoryTypeScheme struct { - _Scheme -} - -func NewRepositoryTypeScheme(defaultDecoder RepositorySpecDecoder, base ...RepositoryTypeScheme) RepositoryTypeScheme { - scheme := descriptivetype.MustNewDefaultTypeScheme[RepositorySpec, RepositoryType, RepositoryTypeScheme]("Credential provider", nil, &UnknownRepositorySpec{}, true, defaultDecoder, utils.Optional(base...)) - return &repositoryTypeScheme{scheme} -} - -func NewStrictRepositoryTypeScheme(base ...RepositoryTypeScheme) runtime.VersionedTypeRegistry[RepositorySpec, RepositoryType] { - scheme := descriptivetype.MustNewDefaultTypeScheme[RepositorySpec, RepositoryType, RepositoryTypeScheme]("Credential provider", nil, nil, false, nil, utils.Optional(base...)) - return &repositoryTypeScheme{scheme} -} - -func (t *repositoryTypeScheme) KnownTypes() runtime.KnownTypes[RepositorySpec, RepositoryType] { - return t._Scheme.KnownTypes() -} - -// DefaultRepositoryTypeScheme contains all globally known access serializer. -var DefaultRepositoryTypeScheme = NewRepositoryTypeScheme(nil) - -func RegisterRepositoryType(atype RepositoryType) { - DefaultRepositoryTypeScheme.Register(atype) -} - -func CreateRepositorySpec(t runtime.TypedObject) (RepositorySpec, error) { - return DefaultRepositoryTypeScheme.Convert(t) -} - -//////////////////////////////////////////////////////////////////////////////// - -type UnknownRepositorySpec struct { - runtime.UnstructuredVersionedTypedObject `json:",inline"` -} - -var ( - _ RepositorySpec = &UnknownRepositorySpec{} - _ runtime.Unknown = &UnknownRepositorySpec{} -) - -func (r *UnknownRepositorySpec) IsUnknown() bool { - return true -} - -func (r *UnknownRepositorySpec) Repository(Context, Credentials) (Repository, error) { - return nil, errors.ErrUnknown("repository type", r.GetType()) -} - -//////////////////////////////////////////////////////////////////////////////// - -type GenericRepositorySpec struct { - runtime.UnstructuredVersionedTypedObject `json:",inline"` -} - -var _ RepositorySpec = &GenericRepositorySpec{} - -func ToGenericRepositorySpec(spec RepositorySpec) (*GenericRepositorySpec, error) { - if reflect2.IsNil(spec) { - return nil, nil - } - if g, ok := spec.(*GenericRepositorySpec); ok { - return g, nil - } - data, err := json.Marshal(spec) - if err != nil { - return nil, err - } - return newGenericRepositorySpec(data, runtime.DefaultJSONEncoding) -} - -func NewGenericRepositorySpec(data []byte, unmarshaler runtime.Unmarshaler) (RepositorySpec, error) { - return generics.CastPointerR[RepositorySpec](newGenericRepositorySpec(data, unmarshaler)) -} - -func newGenericRepositorySpec(data []byte, unmarshaler runtime.Unmarshaler) (*GenericRepositorySpec, error) { - unstr := &runtime.UnstructuredVersionedTypedObject{} - if unmarshaler == nil { - unmarshaler = runtime.DefaultYAMLEncoding - } - err := unmarshaler.Unmarshal(data, unstr) - if err != nil { - return nil, err - } - return &GenericRepositorySpec{*unstr}, nil -} - -func (s *GenericRepositorySpec) Evaluate(ctx Context) (RepositorySpec, error) { - raw, err := s.GetRaw() - if err != nil { - return nil, err - } - return ctx.RepositoryTypes().Decode(raw, runtime.DefaultJSONEncoding) -} - -func (s *GenericRepositorySpec) Repository(ctx Context, creds Credentials) (Repository, error) { - spec, err := s.Evaluate(ctx) - if err != nil { - return nil, err - } - return spec.Repository(ctx, creds) -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/pkg/contexts/credentials/repositories/aliases/cache.go b/pkg/contexts/credentials/repositories/aliases/cache.go deleted file mode 100644 index 8a9db2441..000000000 --- a/pkg/contexts/credentials/repositories/aliases/cache.go +++ /dev/null @@ -1,37 +0,0 @@ -package aliases - -import ( - "sync" - - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" -) - -const ATTR_REPOS = "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/aliases" - -type Repositories struct { - sync.RWMutex - repos map[string]*Repository -} - -func newRepositories(datacontext.Context) interface{} { - return &Repositories{ - repos: map[string]*Repository{}, - } -} - -func (c *Repositories) GetRepository(name string) *Repository { - c.RLock() - defer c.RUnlock() - return c.repos[name] -} - -func (c *Repositories) Set(name string, spec cpi.RepositorySpec, creds cpi.CredentialsSource) { - c.Lock() - defer c.Unlock() - c.repos[name] = &Repository{ - name: name, - spec: spec, - creds: creds, - } -} diff --git a/pkg/contexts/credentials/repositories/aliases/repo_test.go b/pkg/contexts/credentials/repositories/aliases/repo_test.go deleted file mode 100644 index 1e98ba8ce..000000000 --- a/pkg/contexts/credentials/repositories/aliases/repo_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package aliases_test - -import ( - "encoding/json" - "reflect" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - local "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/aliases" -) - -var DefaultContext = credentials.New() - -var _ = Describe("alias credentials", func() { - props := common.Properties{ - "user": "USER", - "password": "PASSWORD", - } - - memorydata := "{\"type\":\"Memory\",\"repoName\":\"myrepo\"}" - specdata := "{\"type\":\"Alias\",\"alias\":\"test\"}" - - It("serializes repo spec", func() { - spec := local.NewRepositorySpec("test") - data, err := json.Marshal(spec) - Expect(err).To(Succeed()) - Expect(data).To(Equal([]byte(specdata))) - }) - It("deserializes repo spec", func() { - spec, err := DefaultContext.RepositorySpecForConfig([]byte(specdata), nil) - Expect(err).To(Succeed()) - Expect(reflect.TypeOf(spec).String()).To(Equal("*aliases.RepositorySpec")) - Expect(spec.(*local.RepositorySpec).Alias).To(Equal("test")) - }) - - It("resolves repository", func() { - memoryspec, err := credentials.NewGenericRepositorySpec([]byte(memorydata), nil) - Expect(err).To(Succeed()) - - err = DefaultContext.SetAlias("test", memoryspec) - Expect(err).To(Succeed()) - - repo, err := DefaultContext.RepositoryForConfig([]byte(specdata), nil) - Expect(err).To(Succeed()) - Expect(reflect.TypeOf(repo).String()).To(Equal("*memory.Repository")) - }) - - It("sets and retrieves credentials", func() { - memoryspec, err := credentials.NewGenericRepositorySpec([]byte(memorydata), nil) - Expect(err).To(Succeed()) - - err = DefaultContext.SetAlias("test", memoryspec) - Expect(err).To(Succeed()) - - repo, err := DefaultContext.RepositoryForConfig([]byte(memorydata), nil) - Expect(err).To(Succeed()) - - _, err = repo.WriteCredentials("bibo", credentials.NewCredentials(props)) - Expect(err).To(Succeed()) - - credspec := credentials.NewCredentialsSpec("bibo", local.NewRepositorySpec("test")) - - creds, err := DefaultContext.CredentialsForSpec(credspec) - Expect(err).To(Succeed()) - Expect(creds.Properties()).To(Equal(props)) - }) -}) diff --git a/pkg/contexts/credentials/repositories/aliases/repository.go b/pkg/contexts/credentials/repositories/aliases/repository.go deleted file mode 100644 index 3ed4bb259..000000000 --- a/pkg/contexts/credentials/repositories/aliases/repository.go +++ /dev/null @@ -1,45 +0,0 @@ -package aliases - -import ( - "sync" - - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" -) - -type Repository struct { - sync.Mutex - name string - spec cpi.RepositorySpec - creds cpi.CredentialsSource - repo cpi.Repository -} - -func (a *Repository) GetRepository(ctx cpi.Context, creds cpi.Credentials) (cpi.Repository, error) { - a.Lock() - defer a.Unlock() - if a.repo != nil { - return a.repo, nil - } - - src := cpi.CredentialsChain{} - if a.creds != nil { - src = append(src, a.creds) - } - if creds != nil { - src = append(src, creds) - } - repo, err := ctx.RepositoryForSpec(a.spec, src...) - if err != nil { - return nil, err - } - a.repo = repo - return repo, nil -} - -func NewRepository(name string, spec cpi.RepositorySpec, creds cpi.Credentials) *Repository { - return &Repository{ - name: name, - spec: spec, - creds: creds, - } -} diff --git a/pkg/contexts/credentials/repositories/aliases/type.go b/pkg/contexts/credentials/repositories/aliases/type.go deleted file mode 100644 index 7b8a90b98..000000000 --- a/pkg/contexts/credentials/repositories/aliases/type.go +++ /dev/null @@ -1,59 +0,0 @@ -package aliases - -import ( - "fmt" - - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - Type = cpi.AliasRepositoryType - TypeV1 = Type + runtime.VersionSeparator + "v1" -) - -func init() { - cpi.RegisterRepositoryType(cpi.NewAliasRegistry(cpi.NewRepositoryType[*RepositorySpec](Type), setAlias)) - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1)) -} - -func setAlias(ctx cpi.Context, name string, spec cpi.RepositorySpec, creds cpi.CredentialsSource) error { - r := ctx.GetAttributes().GetOrCreateAttribute(ATTR_REPOS, newRepositories) - repos, ok := r.(*Repositories) - if !ok { - return fmt.Errorf("failed to assert type %T to Repositories", r) - } - repos.Set(name, spec, creds) - return nil -} - -// RepositorySpec describes a memory based repository interface. -type RepositorySpec struct { - runtime.ObjectVersionedType `json:",inline"` - Alias string `json:"alias"` -} - -// NewRepositorySpec creates a new memory RepositorySpec. -func NewRepositorySpec(name string) *RepositorySpec { - return &RepositorySpec{ - ObjectVersionedType: runtime.NewVersionedTypedObject(Type), - Alias: name, - } -} - -func (a *RepositorySpec) GetType() string { - return Type -} - -func (a *RepositorySpec) Repository(ctx cpi.Context, creds cpi.Credentials) (cpi.Repository, error) { - r := ctx.GetAttributes().GetOrCreateAttribute(ATTR_REPOS, newRepositories) - repos, ok := r.(*Repositories) - if !ok { - return nil, fmt.Errorf("failed to assert type %T to Repositories", r) - } - alias := repos.GetRepository(a.Alias) - if alias == nil { - return nil, cpi.ErrUnknownRepository(Type, a.Alias) - } - return alias.GetRepository(ctx, creds) -} diff --git a/pkg/contexts/credentials/repositories/directcreds/a_usage.go b/pkg/contexts/credentials/repositories/directcreds/a_usage.go deleted file mode 100644 index b808e7d9f..000000000 --- a/pkg/contexts/credentials/repositories/directcreds/a_usage.go +++ /dev/null @@ -1,14 +0,0 @@ -package directcreds - -import ( - "github.com/open-component-model/ocm/pkg/listformat" -) - -var usage = ` -This repository type can be used to specify a single inline credential -set. The default name is the empty string or ` + Type + `.` - -var format = `The repository specification supports the following fields: -` + listformat.FormatListElements("", listformat.StringElementDescriptionList{ - "properties", "*map[string]string*: direct credential fields", -}) diff --git a/pkg/contexts/credentials/repositories/directcreds/credentials.go b/pkg/contexts/credentials/repositories/directcreds/credentials.go deleted file mode 100644 index 48c29b528..000000000 --- a/pkg/contexts/credentials/repositories/directcreds/credentials.go +++ /dev/null @@ -1,10 +0,0 @@ -package directcreds - -import ( - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" -) - -func NewCredentials(props common.Properties) cpi.CredentialsSpec { - return cpi.NewCredentialsSpec(Type, NewRepositorySpec(props)) -} diff --git a/pkg/contexts/credentials/repositories/directcreds/repo_test.go b/pkg/contexts/credentials/repositories/directcreds/repo_test.go deleted file mode 100644 index 3694363e8..000000000 --- a/pkg/contexts/credentials/repositories/directcreds/repo_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package directcreds_test - -import ( - "encoding/json" - "reflect" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/directcreds" -) - -var DefaultContext = credentials.New() - -var _ = Describe("direct credentials", func() { - props := common.Properties{ - "user": "USER", - "password": "PASSWORD", - } - propsdata := "{\"type\":\"Credentials\",\"properties\":{\"password\":\"PASSWORD\",\"user\":\"USER\"}}" - - It("serializes credentials spec", func() { - spec := directcreds.NewRepositorySpec(props) - data, err := json.Marshal(spec) - Expect(err).To(Succeed()) - Expect(data).To(Equal([]byte(propsdata))) - }) - It("deserializes credentials spec", func() { - spec, err := DefaultContext.RepositoryForConfig([]byte(propsdata), nil) - Expect(err).To(Succeed()) - Expect(reflect.TypeOf(spec).String()).To(Equal("*directcreds.Repository")) - }) - - It("resolved direct credentials", func() { - spec := directcreds.NewCredentials(props) - - creds, err := DefaultContext.CredentialsForSpec(spec) - Expect(err).To(Succeed()) - Expect(creds.Properties()).To(Equal(props)) - }) -}) diff --git a/pkg/contexts/credentials/repositories/directcreds/repository.go b/pkg/contexts/credentials/repositories/directcreds/repository.go deleted file mode 100644 index 6a58b16e3..000000000 --- a/pkg/contexts/credentials/repositories/directcreds/repository.go +++ /dev/null @@ -1,34 +0,0 @@ -package directcreds - -import ( - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" -) - -type Repository struct { - Credentials cpi.Credentials -} - -func NewRepository(creds cpi.Credentials) cpi.Repository { - return &Repository{ - Credentials: creds, - } -} - -func (r *Repository) ExistsCredentials(name string) (bool, error) { - return name == Type, nil -} - -func (r *Repository) LookupCredentials(name string) (cpi.Credentials, error) { - if name != Type && name != "" { - return nil, cpi.ErrUnknownCredentials(name) - } - return r.Credentials, nil -} - -func (r *Repository) WriteCredentials(name string, creds cpi.Credentials) (cpi.Credentials, error) { - return nil, errors.ErrNotSupported(cpi.KIND_CREDENTIALS, "write", "constant credential") -} - -var _ cpi.Repository = &Repository{} diff --git a/pkg/contexts/credentials/repositories/directcreds/type.go b/pkg/contexts/credentials/repositories/directcreds/type.go deleted file mode 100644 index bb81cec7b..000000000 --- a/pkg/contexts/credentials/repositories/directcreds/type.go +++ /dev/null @@ -1,56 +0,0 @@ -package directcreds - -import ( - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - Type = "Credentials" - TypeV1 = Type + runtime.VersionSeparator + "v1" -) - -func init() { - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](Type)) - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1, cpi.WithDescription(usage), cpi.WithFormatSpec(format))) -} - -// RepositorySpec describes a repository interface for single direct credentials. -type RepositorySpec struct { - runtime.ObjectVersionedType `json:",inline"` - Properties common.Properties `json:"properties"` -} - -var ( - _ cpi.RepositorySpec = &RepositorySpec{} - _ cpi.CredentialsSpec = &RepositorySpec{} -) - -// NewRepositorySpec creates a new RepositorySpec. -func NewRepositorySpec(credentials common.Properties) *RepositorySpec { - return &RepositorySpec{ - ObjectVersionedType: runtime.NewVersionedTypedObject(Type), - Properties: credentials, - } -} - -func (a *RepositorySpec) GetType() string { - return Type -} - -func (a *RepositorySpec) Repository(ctx cpi.Context, creds cpi.Credentials) (cpi.Repository, error) { - return NewRepository(cpi.NewCredentials(a.Properties)), nil -} - -func (a *RepositorySpec) Credentials(context cpi.Context, source ...cpi.CredentialsSource) (cpi.Credentials, error) { - return cpi.NewCredentials(a.Properties), nil -} - -func (a *RepositorySpec) GetCredentialsName() string { - return "" -} - -func (a *RepositorySpec) GetRepositorySpec(context cpi.Context) cpi.RepositorySpec { - return a -} diff --git a/pkg/contexts/credentials/repositories/dockerconfig/a_usage.go b/pkg/contexts/credentials/repositories/dockerconfig/a_usage.go deleted file mode 100644 index 1df556666..000000000 --- a/pkg/contexts/credentials/repositories/dockerconfig/a_usage.go +++ /dev/null @@ -1,19 +0,0 @@ -package dockerconfig - -import ( - "github.com/open-component-model/ocm/pkg/listformat" -) - -var usage = ` -This repository type can be used to access credentials stored in a file -following the docker config json format. It take into account the -credentials helper section, also. If enabled, the described -credentials will be automatically assigned to appropriate consumer ids. -` - -var format = `The repository specification supports the following fields: -` + listformat.FormatListElements("", listformat.StringElementDescriptionList{ - "dockerConfigFile", "*string*: the file path to a docker config file", - "dockerConfig", "*json*: an embedded docker config json", - "propagateConsumerIdentity", "*bool*(optional): enable consumer id propagation", -}) diff --git a/pkg/contexts/credentials/repositories/dockerconfig/cache.go b/pkg/contexts/credentials/repositories/dockerconfig/cache.go deleted file mode 100644 index 8b8290242..000000000 --- a/pkg/contexts/credentials/repositories/dockerconfig/cache.go +++ /dev/null @@ -1,40 +0,0 @@ -package dockerconfig - -import ( - "sync" - - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" -) - -const ATTR_REPOS = "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/dockerconfig" - -type Repositories struct { - lock sync.Mutex - repos map[string]*Repository -} - -func newRepositories(datacontext.Context) interface{} { - return &Repositories{ - repos: map[string]*Repository{}, - } -} - -func (r *Repositories) GetRepository(ctx cpi.Context, name string, data []byte, propagate bool) (*Repository, error) { - r.lock.Lock() - defer r.lock.Unlock() - var ( - err error = nil - repo *Repository - ) - if name != "" { - repo = r.repos[name] - } - if repo == nil { - repo, err = NewRepository(ctx, name, data, propagate) - if err == nil { - r.repos[name] = repo - } - } - return repo, err -} diff --git a/pkg/contexts/credentials/repositories/dockerconfig/credentials.go b/pkg/contexts/credentials/repositories/dockerconfig/credentials.go deleted file mode 100644 index a6a38f829..000000000 --- a/pkg/contexts/credentials/repositories/dockerconfig/credentials.go +++ /dev/null @@ -1,67 +0,0 @@ -package dockerconfig - -import ( - "github.com/docker/cli/cli/config/configfile" - dockercred "github.com/docker/cli/cli/config/credentials" - "github.com/docker/cli/cli/config/types" - "github.com/mandelsoft/goutils/set" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" -) - -type Credentials struct { - config *configfile.ConfigFile - name string - store dockercred.Store -} - -var _ cpi.Credentials = (*Credentials)(nil) - -// NewCredentials describes a default getter method for a authentication method. -func NewCredentials(cfg *configfile.ConfigFile, name string, store dockercred.Store) cpi.Credentials { - return &Credentials{ - config: cfg, - name: name, - store: store, - } -} - -func (c *Credentials) get() common.Properties { - auth, err := c.config.GetAuthConfig(c.name) - if err != nil { - return common.Properties{} - } - return newCredentials(auth).Properties() -} - -func (c *Credentials) Credentials(context cpi.Context, source ...cpi.CredentialsSource) (cpi.Credentials, error) { - var auth types.AuthConfig - var err error - if c.store == nil { - auth, err = c.config.GetAuthConfig(c.name) - } else { - auth, err = c.store.Get(c.name) - } - if err != nil { - return nil, err - } - return newCredentials(auth), nil -} - -func (c *Credentials) ExistsProperty(name string) bool { - _, ok := c.get()[name] - return ok -} - -func (c *Credentials) GetProperty(name string) string { - return c.get()[name] -} - -func (c *Credentials) PropertyNames() set.Set[string] { - return c.get().Names() -} - -func (c *Credentials) Properties() common.Properties { - return c.get() -} diff --git a/pkg/contexts/credentials/repositories/dockerconfig/default.go b/pkg/contexts/credentials/repositories/dockerconfig/default.go deleted file mode 100644 index f29813bbc..000000000 --- a/pkg/contexts/credentials/repositories/dockerconfig/default.go +++ /dev/null @@ -1,32 +0,0 @@ -package dockerconfig - -import ( - dockercli "github.com/docker/cli/cli/config" - "github.com/mandelsoft/filepath/pkg/filepath" - "github.com/mandelsoft/vfs/pkg/osfs" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/contexts/config" - credcfg "github.com/open-component-model/ocm/pkg/contexts/credentials/config" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils/defaultconfigregistry" -) - -func init() { - defaultconfigregistry.RegisterDefaultConfigHandler(DefaultConfigHandler, desc) -} - -func DefaultConfigHandler(cfg config.Context) (string, config.Config, error) { - // use docker config as default config for ocm cli - d := filepath.Join(dockercli.Dir(), dockercli.ConfigFileName) - if ok, err := vfs.FileExists(osfs.New(), d); ok && err == nil { - ccfg := credcfg.New() - ccfg.AddRepository(NewRepositorySpec(d, true)) - return d, ccfg, nil - } - return "", nil, nil -} - -var desc = ` -The docker configuration file at ~/.docker/config.json is -read to feed in the configured credentials for OCI registries. -` diff --git a/pkg/contexts/credentials/repositories/dockerconfig/logging.go b/pkg/contexts/credentials/repositories/dockerconfig/logging.go deleted file mode 100644 index 1902d87bc..000000000 --- a/pkg/contexts/credentials/repositories/dockerconfig/logging.go +++ /dev/null @@ -1,7 +0,0 @@ -package dockerconfig - -import ( - ocmlog "github.com/open-component-model/ocm/pkg/logging" -) - -var REALM = ocmlog.DefineSubRealm("docker config handling as credential repository", "credentials/dockerconfig") diff --git a/pkg/contexts/credentials/repositories/dockerconfig/provider.go b/pkg/contexts/credentials/repositories/dockerconfig/provider.go deleted file mode 100644 index 1ac017391..000000000 --- a/pkg/contexts/credentials/repositories/dockerconfig/provider.go +++ /dev/null @@ -1,87 +0,0 @@ -package dockerconfig - -import ( - "github.com/docker/cli/cli/config/configfile" - dockercred "github.com/docker/cli/cli/config/credentials" - - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/utils" -) - -const PROVIDER = "ocm.software/credentialprovider/" + Type - -type ConsumerProvider struct { - cfg *configfile.ConfigFile -} - -var _ cpi.ConsumerProvider = (*ConsumerProvider)(nil) - -func (p *ConsumerProvider) Unregister(id cpi.ProviderIdentity) { -} - -func (p *ConsumerProvider) Match(ectx cpi.EvaluationContext, req cpi.ConsumerIdentity, cur cpi.ConsumerIdentity, m cpi.IdentityMatcher) (cpi.CredentialsSource, cpi.ConsumerIdentity) { - return p.get(req, cur, m) -} - -func (p *ConsumerProvider) Get(req cpi.ConsumerIdentity) (cpi.CredentialsSource, bool) { - creds, _ := p.get(req, nil, cpi.CompleteMatch) - return creds, creds != nil -} - -func (p *ConsumerProvider) get(req cpi.ConsumerIdentity, cur cpi.ConsumerIdentity, m cpi.IdentityMatcher) (cpi.CredentialsSource, cpi.ConsumerIdentity) { - cfg := p.cfg - all := cfg.GetAuthConfigs() - defaultStore := dockercred.DetectDefaultStore(cfg.CredentialsStore) - var store dockercred.Store - if defaultStore != "" { - store = dockercred.NewNativeStore(cfg, defaultStore) - } - - var creds cpi.CredentialsSource - - for h, a := range all { - hostname, port, _ := utils.SplitLocator(dockercred.ConvertToHostname(h)) - if hostname == "index.docker.io" { - hostname = "docker.io" - } - attrs := []string{identity.ID_HOSTNAME, hostname} - if port != "" { - attrs = append(attrs, identity.ID_PORT, port) - } - id := cpi.NewConsumerIdentity(identity.CONSUMER_TYPE, attrs...) - if m(req, cur, id) { - if IsEmptyAuthConfig(a) { - store := store - for hh, helper := range cfg.CredentialHelpers { - if hh == h { - store = dockercred.NewNativeStore(cfg, helper) - break - } - } - if store == nil { - continue - } - creds = NewCredentials(cfg, h, store) - } else { - creds = newCredentials(a) - } - cur = id - } - } - for h, helper := range cfg.CredentialHelpers { - hostname := dockercred.ConvertToHostname(h) - if hostname == "index.docker.io" { - hostname = "docker.io" - } - id := cpi.ConsumerIdentity{ - cpi.ATTR_TYPE: identity.CONSUMER_TYPE, - identity.ID_HOSTNAME: hostname, - } - if m(req, cur, id) { - creds = NewCredentials(cfg, h, dockercred.NewNativeStore(cfg, helper)) - cur = id - } - } - return creds, cur -} diff --git a/pkg/contexts/credentials/repositories/dockerconfig/repo_test.go b/pkg/contexts/credentials/repositories/dockerconfig/repo_test.go deleted file mode 100644 index e3e2e8c7b..000000000 --- a/pkg/contexts/credentials/repositories/dockerconfig/repo_test.go +++ /dev/null @@ -1,172 +0,0 @@ -package dockerconfig_test - -import ( - "encoding/json" - "os" - "reflect" - "runtime" - "time" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - local "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/dockerconfig" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/runtimefinalizer" -) - -var _ = Describe("docker config", func() { - props := common.Properties{ - "username": "mandelsoft", - "password": "password", - "serverAddress": "https://index.docker.io/v1/", - } - - props2 := common.Properties{ - "username": "mandelsoft", - "password": "token", - "serverAddress": "https://ghcr.io", - } - - var DefaultContext credentials.Context - - BeforeEach(func() { - DefaultContext = credentials.New() - }) - - Context("file based", func() { - specdata := "{\"type\":\"DockerConfig\",\"dockerConfigFile\":\"testdata/dockerconfig.json\"}" - specdata2 := "{\"type\":\"DockerConfig\",\"dockerConfigFile\":\"testdata/dockerconfig.json\",\"propagateConsumerIdentity\":true}" - - It("serializes repo spec", func() { - spec := local.NewRepositorySpec("testdata/dockerconfig.json") - data := Must(json.Marshal(spec)) - Expect(data).To(Equal([]byte(specdata))) - - spec = local.NewRepositorySpec("testdata/dockerconfig.json").WithConsumerPropagation(true) - data = Must(json.Marshal(spec)) - Expect(data).To(Equal([]byte(specdata2))) - }) - - It("deserializes repo spec", func() { - spec := Must(DefaultContext.RepositorySpecForConfig([]byte(specdata), nil)) - Expect(reflect.TypeOf(spec).String()).To(Equal("*dockerconfig.RepositorySpec")) - Expect(spec.(*local.RepositorySpec).DockerConfigFile).To(Equal("testdata/dockerconfig.json")) - }) - - It("resolves repository", func() { - repo := Must(DefaultContext.RepositoryForConfig([]byte(specdata), nil)) - Expect(reflect.TypeOf(repo).String()).To(Equal("*dockerconfig.Repository")) - }) - - It("retrieves credentials", func() { - repo := Must(DefaultContext.RepositoryForConfig([]byte(specdata), nil)) - - creds := Must(repo.LookupCredentials("index.docker.io")) - Expect(creds.Properties()).To(Equal(props)) - - creds = Must(repo.LookupCredentials("ghcr.io")) - Expect(creds.Properties()).To(Equal(props2)) - }) - - It("propagates credentials to consumer identity", func() { - Must(DefaultContext.RepositoryForConfig([]byte(specdata2), nil)) - - creds := Must(credentials.CredentialsForConsumer(DefaultContext, credentials.ConsumerIdentity{ - cpi.ATTR_TYPE: identity.CONSUMER_TYPE, - identity.ID_HOSTNAME: "ghcr.io", - })) - Expect(creds.Properties()).To(Equal(props2)) - }) - }) - - Context("inline data", func() { - specdata := "{\"type\":\"DockerConfig\",\"dockerConfig\":{\"auths\":{\"https://index.docker.io/v1/\":{\"auth\":\"bWFuZGVsc29mdDpwYXNzd29yZA==\"},\"https://ghcr.io\":{\"auth\":\"bWFuZGVsc29mdDp0b2tlbg==\"}},\"HttpHeaders\":{\"User-Agent\":\"Docker-Client/18.06.1-ce (linux)\"}},\"propagateConsumerIdentity\":true}" - - It("serializes repo spec", func() { - configdata := Must(os.ReadFile("testdata/dockerconfig.json")) - spec := local.NewRepositorySpecForConfig(configdata).WithConsumerPropagation(true) - data := Must(json.Marshal(spec)) - - var ( - datajson map[string]interface{} - specjson map[string]interface{} - ) - // Comparing the bytes might be problematic as the order of the JSON objects within the config file might change - // during Marshaling - MustBeSuccessful(json.Unmarshal([]byte(specdata), &specjson)) - MustBeSuccessful(json.Unmarshal(data, &datajson)) - Expect(datajson).To(Equal(specjson)) - }) - - It("deserializes repo spec", func() { - spec := Must(DefaultContext.RepositorySpecForConfig([]byte(specdata), nil)) - Expect(reflect.TypeOf(spec).String()).To(Equal("*dockerconfig.RepositorySpec")) - configdata := Must(os.ReadFile("testdata/dockerconfig.json")) - var ( - configdatajson map[string]interface{} - dockerconfigjson map[string]interface{} - ) - // Comparing the bytes might be problematic as the order of the JSON objects within the config file might change - // during Marshaling - MustBeSuccessful(json.Unmarshal(configdata, &configdatajson)) - MustBeSuccessful(json.Unmarshal(spec.(*local.RepositorySpec).DockerConfig, &dockerconfigjson)) - Expect(dockerconfigjson).To(Equal(configdatajson)) - }) - - It("resolves repository", func() { - repo := Must(DefaultContext.RepositoryForConfig([]byte(specdata), nil)) - Expect(reflect.TypeOf(repo).String()).To(Equal("*dockerconfig.Repository")) - }) - - It("retrieves credentials", func() { - repo := Must(DefaultContext.RepositoryForConfig([]byte(specdata), nil)) - - creds := Must(repo.LookupCredentials("index.docker.io")) - Expect(creds.Properties()).To(Equal(props)) - - creds = Must(repo.LookupCredentials("ghcr.io")) - Expect(creds.Properties()).To(Equal(props2)) - }) - - It("propagates credentials to consumer identity", func() { - Must(DefaultContext.RepositoryForConfig([]byte(specdata), nil)) - - creds := Must(credentials.CredentialsForConsumer(DefaultContext, credentials.ConsumerIdentity{ - cpi.ATTR_TYPE: identity.CONSUMER_TYPE, - identity.ID_HOSTNAME: "ghcr.io", - })) - Expect(creds.Properties()).To(Equal(props2)) - }) - }) - - Context("ref handling", func() { - specdata := "{\"type\":\"DockerConfig\",\"dockerConfigFile\":\"testdata/dockerconfig.json\",\"propagateConsumerIdentity\":true}" - - It("can access the default context", func() { - ctx := credentials.New() - - r := runtimefinalizer.GetRuntimeFinalizationRecorder(ctx) - Expect(r).NotTo(BeNil()) - - Must(ctx.RepositoryForConfig([]byte(specdata), nil)) - - runtime.GC() - time.Sleep(time.Second) - ctx.GetType() - Expect(r.Get()).To(BeNil()) - - Expect(datacontext.GetContextRefCount(ctx)).To(Equal(1)) - ctx = nil - runtime.GC() - time.Sleep(time.Second) - - Expect(r.Get()).To(ContainElement(ContainSubstring(credentials.CONTEXT_TYPE))) - }) - }) -}) diff --git a/pkg/contexts/credentials/repositories/dockerconfig/repository.go b/pkg/contexts/credentials/repositories/dockerconfig/repository.go deleted file mode 100644 index 108494fea..000000000 --- a/pkg/contexts/credentials/repositories/dockerconfig/repository.go +++ /dev/null @@ -1,143 +0,0 @@ -package dockerconfig - -import ( - "bytes" - "fmt" - "os" - "strings" - "sync" - - "github.com/docker/cli/cli/config" - "github.com/docker/cli/cli/config/configfile" - "github.com/docker/cli/cli/config/types" - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/runtimefinalizer" - "github.com/open-component-model/ocm/pkg/utils" -) - -type Repository struct { - lock sync.RWMutex - ctx cpi.Context - propagate bool - path string - data []byte - config *configfile.ConfigFile -} - -func NewRepository(ctx cpi.Context, path string, data []byte, propagate bool) (*Repository, error) { - r := &Repository{ - ctx: datacontext.InternalContextRef(ctx), - propagate: propagate, - path: path, - data: data, - } - if path != "" && len(data) > 0 { - return nil, fmt.Errorf("only config file or config data possible") - } - err := r.Read(true) - return r, err -} - -var _ cpi.Repository = &Repository{} - -func (r *Repository) ExistsCredentials(name string) (bool, error) { - err := r.Read(false) - if err != nil { - return false, err - } - r.lock.RLock() - defer r.lock.RUnlock() - - _, err = r.config.GetAuthConfig(name) - return err != nil, err -} - -func (r *Repository) LookupCredentials(name string) (cpi.Credentials, error) { - err := r.Read(false) - if err != nil { - return nil, err - } - r.lock.RLock() - defer r.lock.RUnlock() - - auth, err := r.config.GetAuthConfig(name) - if err != nil { - return nil, err - } - return newCredentials(auth), nil -} - -func (r *Repository) WriteCredentials(name string, creds cpi.Credentials) (cpi.Credentials, error) { - return nil, errors.ErrNotSupported("write", "credentials", Type) -} - -func (r *Repository) Read(force bool) error { - r.lock.Lock() - defer r.lock.Unlock() - if !force && r.config != nil { - return nil - } - var ( - data []byte - err error - id runtimefinalizer.ObjectIdentity - ) - if r.path != "" { - path, err := utils.ResolvePath(r.path) - if err != nil { - return errors.Wrapf(err, "cannot resolve path %q", r.path) - } - data, err = os.ReadFile(path) - if err != nil { - return fmt.Errorf("failed to read file '%s': %w", path, err) - } - id = cpi.ProviderIdentity(PROVIDER + "/" + path) - } else if len(r.data) > 0 { - data = r.data - id = runtimefinalizer.NewObjectIdentity(PROVIDER) - } - - cfg, err := config.LoadFromReader(bytes.NewBuffer(data)) - if err != nil { - return fmt.Errorf("failed to load config: %w", err) - } - if r.propagate { - r.ctx.RegisterConsumerProvider(id, &ConsumerProvider{cfg}) - } - r.config = cfg - return nil -} - -func newCredentials(auth types.AuthConfig) cpi.Credentials { - props := common.Properties{ - cpi.ATTR_USERNAME: norm(auth.Username), - cpi.ATTR_PASSWORD: norm(auth.Password), - } - props.SetNonEmptyValue("auth", auth.Auth) - props.SetNonEmptyValue(cpi.ATTR_SERVER_ADDRESS, norm(auth.ServerAddress)) - props.SetNonEmptyValue(cpi.ATTR_IDENTITY_TOKEN, norm(auth.IdentityToken)) - props.SetNonEmptyValue(cpi.ATTR_REGISTRY_TOKEN, norm(auth.RegistryToken)) - return cpi.NewCredentials(props) -} - -func norm(s string) string { - for strings.HasSuffix(s, "\n") { - s = s[:len(s)-1] - } - return s -} - -// IsEmptyAuthConfig validates if the resulting auth config contains credentials. -func IsEmptyAuthConfig(auth types.AuthConfig) bool { - if len(auth.Auth) != 0 { - return false - } - if len(auth.Username) != 0 { - return false - } - return true -} diff --git a/pkg/contexts/credentials/repositories/dockerconfig/type.go b/pkg/contexts/credentials/repositories/dockerconfig/type.go deleted file mode 100644 index d18cd7f26..000000000 --- a/pkg/contexts/credentials/repositories/dockerconfig/type.go +++ /dev/null @@ -1,76 +0,0 @@ -package dockerconfig - -import ( - "encoding/json" - "fmt" - - "github.com/mandelsoft/goutils/generics" - - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -const ( - Type = "DockerConfig" - TypeV1 = Type + runtime.VersionSeparator + "v1" -) - -func init() { - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](Type)) - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1, cpi.WithDescription(usage), cpi.WithFormatSpec(format))) -} - -// RepositorySpec describes a docker config based credential repository interface. -type RepositorySpec struct { - runtime.ObjectVersionedType `json:",inline"` - DockerConfigFile string `json:"dockerConfigFile,omitempty"` - DockerConfig json.RawMessage `json:"dockerConfig,omitempty"` - PropgateConsumerIdentity *bool `json:"propagateConsumerIdentity,omitempty"` -} - -func (s RepositorySpec) WithConsumerPropagation(propagate bool) *RepositorySpec { - s.PropgateConsumerIdentity = &propagate - return &s -} - -// NewRepositorySpec creates a new memory RepositorySpec. -func NewRepositorySpec(path string, prop ...bool) *RepositorySpec { - var p *bool - if len(prop) > 0 { - p = generics.Pointer(utils.Optional(prop...)) - } - if path == "" { - path = "~/.docker/config.json" - } - return &RepositorySpec{ - ObjectVersionedType: runtime.NewVersionedTypedObject(Type), - DockerConfigFile: path, - PropgateConsumerIdentity: p, - } -} - -func NewRepositorySpecForConfig(data []byte, prop ...bool) *RepositorySpec { - var p *bool - if len(prop) > 0 { - p = generics.Pointer(utils.Optional(prop...)) - } - return &RepositorySpec{ - ObjectVersionedType: runtime.NewVersionedTypedObject(Type), - DockerConfig: data, - PropgateConsumerIdentity: p, - } -} - -func (a *RepositorySpec) GetType() string { - return Type -} - -func (a *RepositorySpec) Repository(ctx cpi.Context, creds cpi.Credentials) (cpi.Repository, error) { - r := ctx.GetAttributes().GetOrCreateAttribute(ATTR_REPOS, newRepositories) - repos, ok := r.(*Repositories) - if !ok { - return nil, fmt.Errorf("failed to assert type %T to Repositories", r) - } - return repos.GetRepository(ctx, a.DockerConfigFile, a.DockerConfig, utils.AsBool(a.PropgateConsumerIdentity, true)) -} diff --git a/pkg/contexts/credentials/repositories/gardenerconfig/cache.go b/pkg/contexts/credentials/repositories/gardenerconfig/cache.go deleted file mode 100644 index 43f62e0d2..000000000 --- a/pkg/contexts/credentials/repositories/gardenerconfig/cache.go +++ /dev/null @@ -1,36 +0,0 @@ -package gardenerconfig - -import ( - "fmt" - "sync" - - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - gardenercfgcpi "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/gardenerconfig/cpi" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" -) - -const ATTR_REPOS = "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/gardenerconfig" - -type Repositories struct { - lock sync.Mutex - repos map[string]*Repository -} - -func newRepositories(datacontext.Context) interface{} { - return &Repositories{ - repos: map[string]*Repository{}, - } -} - -func (r *Repositories) GetRepository(ctx cpi.Context, url string, configType gardenercfgcpi.ConfigType, cipher Cipher, key []byte, propagateConsumerIdentity bool) (*Repository, error) { - r.lock.Lock() - defer r.lock.Unlock() - if _, ok := r.repos[url]; !ok { - repo, err := NewRepository(ctx, url, configType, cipher, key, propagateConsumerIdentity) - if err != nil { - return nil, fmt.Errorf("unable to create repository: %w", err) - } - r.repos[url] = repo - } - return r.repos[url], nil -} diff --git a/pkg/contexts/credentials/repositories/gardenerconfig/cpi/interface.go b/pkg/contexts/credentials/repositories/gardenerconfig/cpi/interface.go deleted file mode 100644 index 1ee6b7ed4..000000000 --- a/pkg/contexts/credentials/repositories/gardenerconfig/cpi/interface.go +++ /dev/null @@ -1,53 +0,0 @@ -package cpi - -import ( - "io" - "sync" - - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" -) - -type ConfigType string - -const ( - ContainerRegistry ConfigType = "container_registry" -) - -type Credential interface { - Name() string - ConsumerIdentity() cpi.ConsumerIdentity - Properties() cpi.Credentials -} - -type Handler interface { - ConfigType() ConfigType - ParseConfig(io.Reader) ([]Credential, error) -} - -var ( - handlers = map[ConfigType]Handler{} - lock sync.RWMutex -) - -func RegisterHandler(h Handler) { - lock.Lock() - defer lock.Unlock() - handlers[h.ConfigType()] = h -} - -func GetHandler(configType ConfigType) Handler { - lock.RLock() - defer lock.RUnlock() - return handlers[configType] -} - -func GetHandlers() map[ConfigType]Handler { - lock.RLock() - defer lock.RUnlock() - - m := map[ConfigType]Handler{} - for k, v := range handlers { - m[k] = v - } - return m -} diff --git a/pkg/contexts/credentials/repositories/gardenerconfig/credentials.go b/pkg/contexts/credentials/repositories/gardenerconfig/credentials.go deleted file mode 100644 index c633b9120..000000000 --- a/pkg/contexts/credentials/repositories/gardenerconfig/credentials.go +++ /dev/null @@ -1,15 +0,0 @@ -package gardenerconfig - -import ( - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" -) - -type credentialGetter struct { - getCredentials func() (cpi.Credentials, error) -} - -var _ cpi.CredentialsSource = credentialGetter{} - -func (c credentialGetter) Credentials(ctx cpi.Context, cs ...cpi.CredentialsSource) (cpi.Credentials, error) { - return c.getCredentials() -} diff --git a/pkg/contexts/credentials/repositories/gardenerconfig/handler/container_registry/credentials.go b/pkg/contexts/credentials/repositories/gardenerconfig/handler/container_registry/credentials.go deleted file mode 100644 index 7ff669591..000000000 --- a/pkg/contexts/credentials/repositories/gardenerconfig/handler/container_registry/credentials.go +++ /dev/null @@ -1,26 +0,0 @@ -package container_registry - -import ( - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - gardenercfgcpi "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/gardenerconfig/cpi" -) - -type credentials struct { - name string - consumerIdentity cpi.ConsumerIdentity - properties cpi.Credentials -} - -func (c credentials) Name() string { - return c.name -} - -func (c credentials) ConsumerIdentity() cpi.ConsumerIdentity { - return c.consumerIdentity -} - -func (c credentials) Properties() cpi.Credentials { - return c.properties -} - -var _ gardenercfgcpi.Credential = credentials{} diff --git a/pkg/contexts/credentials/repositories/gardenerconfig/handler/container_registry/handler.go b/pkg/contexts/credentials/repositories/gardenerconfig/handler/container_registry/handler.go deleted file mode 100644 index a06583804..000000000 --- a/pkg/contexts/credentials/repositories/gardenerconfig/handler/container_registry/handler.go +++ /dev/null @@ -1,99 +0,0 @@ -package container_registry - -import ( - "encoding/json" - "fmt" - "io" - "strings" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/contexts/credentials/identity/hostpath" - gardenercfgcpi "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/gardenerconfig/cpi" - "github.com/open-component-model/ocm/pkg/utils" -) - -func init() { - gardenercfgcpi.RegisterHandler(Handler{}) -} - -// config is the struct that describes the gardener config data structure. -type config struct { - ContainerRegistry map[string]*containerRegistryCredentials `json:"container_registry"` -} - -// containerRegistryCredentials describes the container registry credentials struct as defined by the gardener config. -type containerRegistryCredentials struct { - Username string `json:"username"` - Password string `json:"password"` - Privileges string `json:"privileges"` - Host string `json:"host,omitempty"` - ImageReferencePrefixes []string `json:"image_reference_prefixes,omitempty"` -} - -type Handler struct{} - -func (h Handler) ConfigType() gardenercfgcpi.ConfigType { - return gardenercfgcpi.ContainerRegistry -} - -func (h Handler) ParseConfig(configReader io.Reader) ([]gardenercfgcpi.Credential, error) { - config := &config{} - if err := json.NewDecoder(configReader).Decode(&config); err != nil { - return nil, fmt.Errorf("unable to unmarshal config: %w", err) - } - - creds := []gardenercfgcpi.Credential{} - for credentialName, credential := range config.ContainerRegistry { - var ( - scheme string - port string - ) - if credential.Host != "" { - parsedHost, err := utils.ParseURL(credential.Host) - if err != nil { - return nil, fmt.Errorf("unable to parse host: %w", err) - } - scheme = parsedHost.Scheme - port = parsedHost.Port() - } - - for _, imgRef := range credential.ImageReferencePrefixes { - parsedImgPrefix, err := utils.ParseURL(imgRef) - if err != nil { - return nil, fmt.Errorf("unable to parse image prefix: %w", err) - } - if parsedImgPrefix.Host == "index.docker.io" { - parsedImgPrefix.Host = "docker.io" - } - - consumerIdentity := cpi.ConsumerIdentity{ - cpi.ID_TYPE: identity.CONSUMER_TYPE, - hostpath.ID_HOSTNAME: parsedImgPrefix.Host, - hostpath.ID_PATHPREFIX: strings.Trim(parsedImgPrefix.Path, "/"), - } - consumerIdentity.SetNonEmptyValue(hostpath.ID_SCHEME, scheme) - consumerIdentity.SetNonEmptyValue(hostpath.ID_PORT, port) - - c := credentials{ - name: credentialName, - consumerIdentity: consumerIdentity, - properties: newCredentialsFromContainerRegistryCredentials(credential), - } - - creds = append(creds, c) - } - } - - return creds, nil -} - -func newCredentialsFromContainerRegistryCredentials(auth *containerRegistryCredentials) cpi.Credentials { - props := common.Properties{ - cpi.ATTR_USERNAME: auth.Username, - cpi.ATTR_PASSWORD: auth.Password, - } - props.SetNonEmptyValue(cpi.ATTR_SERVER_ADDRESS, auth.Host) - return cpi.NewCredentials(props) -} diff --git a/pkg/contexts/credentials/repositories/gardenerconfig/identity/identity.go b/pkg/contexts/credentials/repositories/gardenerconfig/identity/identity.go deleted file mode 100644 index 39ff6d172..000000000 --- a/pkg/contexts/credentials/repositories/gardenerconfig/identity/identity.go +++ /dev/null @@ -1,60 +0,0 @@ -package identity - -import ( - "fmt" - "strings" - - "github.com/open-component-model/ocm/pkg/common" - "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/listformat" - "github.com/open-component-model/ocm/pkg/utils" -) - -const CONSUMER_TYPE = "Buildcredentials" + common.OCM_TYPE_GROUP_SUFFIX - -// used identity attributes. -const ( - ID_SCHEME = hostpath.ID_SCHEME - ID_HOSTNAME = hostpath.ID_HOSTNAME - ID_PORT = hostpath.ID_PORT - ID_PATHPREFIX = hostpath.ID_PATHPREFIX -) - -// used credential properties. -const ( - ATTR_KEY = cpi.ATTR_KEY -) - -var identityMatcher = hostpath.IdentityMatcher(CONSUMER_TYPE) - -func IdentityMatcher(pattern, cur, id cpi.ConsumerIdentity) bool { - return identityMatcher(pattern, cur, id) -} - -func init() { - attrs := listformat.FormatListElements("", listformat.StringElementDescriptionList{ - ATTR_KEY, "secret key use to access the credential server", - }) - - cpi.RegisterStandardIdentity(CONSUMER_TYPE, IdentityMatcher, `Gardener config credential matcher - -It matches the `+CONSUMER_TYPE+` consumer type and additionally acts like -the `+hostpath.IDENTITY_TYPE+` type.`, - attrs) -} - -func GetConsumerId(configURL string) (cpi.ConsumerIdentity, error) { - parsedURL, err := utils.ParseURL(configURL) - if err != nil { - return nil, fmt.Errorf("unable to parse url: %w", err) - } - - id := cpi.NewConsumerIdentity(CONSUMER_TYPE) - id.SetNonEmptyValue(ID_HOSTNAME, parsedURL.Host) - id.SetNonEmptyValue(ID_SCHEME, parsedURL.Scheme) - id.SetNonEmptyValue(ID_PATHPREFIX, strings.Trim(parsedURL.Path, "/")) - id.SetNonEmptyValue(ID_PORT, parsedURL.Port()) - - return id, nil -} diff --git a/pkg/contexts/credentials/repositories/gardenerconfig/init.go b/pkg/contexts/credentials/repositories/gardenerconfig/init.go deleted file mode 100644 index 9b69c3a3a..000000000 --- a/pkg/contexts/credentials/repositories/gardenerconfig/init.go +++ /dev/null @@ -1,5 +0,0 @@ -package gardenerconfig - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/gardenerconfig/handler/container_registry" -) diff --git a/pkg/contexts/credentials/repositories/gardenerconfig/repo_test.go b/pkg/contexts/credentials/repositories/gardenerconfig/repo_test.go deleted file mode 100644 index beb5a3241..000000000 --- a/pkg/contexts/credentials/repositories/gardenerconfig/repo_test.go +++ /dev/null @@ -1,200 +0,0 @@ -package gardenerconfig_test - -import ( - "encoding/base64" - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "reflect" - "strings" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/mandelsoft/vfs/pkg/memoryfs" - - "github.com/open-component-model/ocm/pkg/contexts/credentials" - ociidentity "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - local "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/gardenerconfig" - gardenercfgcpi "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/gardenerconfig/cpi" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/gardenerconfig/identity" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/vfsattr" - "github.com/open-component-model/ocm/pkg/utils" -) - -var _ = Describe("gardener config", func() { - containerRegistryCfg := `{ - "container_registry": { - "test-credentials": { - "username": "abc", - "password": "123", - "image_reference_prefixes": [ - "eu.gcr.io/test-project" - ] - } - } -}` - encryptionKey := "abcdefghijklmnop" - encryptedContainerRegistryCfg := "Uz4mfePXFOUbjUEZnRrnG8zP2T7lRH6bR2rFHYgWDwZUXfW7D5wArwY4dsBACPVFNapF7kcM9z79+LvJXd2kNoIfvUyMOhrSDAyv4LtUqYSKBOoRH/aJMnXjmN9GQBCXSRSJs/Fu21AoDNo8fA9zYvvc7WxTldkYC/vHxLVNJu5j176e1QiaS9hwDjgNhgyUT3XUjHUyQ19PcRgwDglRLfiL4Cs/fYPPxdg4YZQdCnc=" - expectedCreds := cpi.DirectCredentials{ - cpi.ATTR_USERNAME: "abc", - cpi.ATTR_PASSWORD: "123", - } - - repoSpecTemplate := `{"type":"GardenerConfig","url":"%s","configType":"container_registry","cipher":"%s","propagateConsumerIdentity":true}` - - var defaultContext credentials.Context - - BeforeEach(func() { - defaultContext = credentials.New() - }) - - It("serializes repo spec", func() { - const ( - url = "http://localhost:8080/container_registry" - cipher = local.Plaintext - ) - expectedSpec := fmt.Sprintf(repoSpecTemplate, url, cipher) - - spec := local.NewRepositorySpec("http://localhost:8080/container_registry", "container_registry", local.Plaintext, true) - data, err := json.Marshal(spec) - Expect(err).ToNot(HaveOccurred()) - Expect(data).To(Equal([]byte(expectedSpec))) - }) - - It("deserializes repo spec", func() { - const ( - url = "http://localhost:8080/container_registry" - cipher = local.Plaintext - ) - specdata := fmt.Sprintf(repoSpecTemplate, url, cipher) - - spec, err := defaultContext.RepositorySpecForConfig([]byte(specdata), nil) - Expect(err).ToNot(HaveOccurred()) - Expect(reflect.TypeOf(spec).String()).To(Equal("*gardenerconfig.RepositorySpec")) - - parsedSpec := spec.(*local.RepositorySpec) - Expect(parsedSpec.URL).To(Equal(url)) - Expect(parsedSpec.ConfigType).To(Equal(gardenercfgcpi.ContainerRegistry)) - Expect(parsedSpec.Cipher).To(Equal(cipher)) - }) - - It("resolves repository", func() { - const ( - url = "http://localhost:8080/container_registry" - cipher = local.Plaintext - ) - specdata := fmt.Sprintf(repoSpecTemplate, url, cipher) - - repo, err := defaultContext.RepositoryForConfig([]byte(specdata), nil) - Expect(err).ToNot(HaveOccurred()) - Expect(repo).ToNot(BeNil()) - Expect(reflect.TypeOf(repo).String()).To(Equal("*gardenerconfig.Repository")) - }) - - It("retrieves credentials from unencrypted server", func() { - svr := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { - writer.WriteHeader(200) - _, err := writer.Write([]byte(containerRegistryCfg)) - Expect(err).ToNot(HaveOccurred()) - })) - defer svr.Close() - - spec := fmt.Sprintf(repoSpecTemplate, svr.URL, local.Plaintext) - - repo, err := defaultContext.RepositoryForConfig([]byte(spec), nil) - Expect(err).ToNot(HaveOccurred()) - Expect(repo).ToNot(BeNil()) - - credentialsFromRepo, err := repo.LookupCredentials("test-credentials") - Expect(err).ToNot(HaveOccurred()) - Expect(credentialsFromRepo).To(Equal(expectedCreds)) - }) - - It("propagates credentials with consumer ids in the context", func() { - expectedConsumerId := cpi.ConsumerIdentity{ - cpi.ID_TYPE: ociidentity.CONSUMER_TYPE, - ociidentity.ID_HOSTNAME: "eu.gcr.io", - ociidentity.ID_PATHPREFIX: "test-project", - } - - svr := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { - writer.WriteHeader(200) - _, err := writer.Write([]byte(containerRegistryCfg)) - Expect(err).ToNot(HaveOccurred()) - })) - defer svr.Close() - - spec := fmt.Sprintf(repoSpecTemplate, svr.URL, local.Plaintext) - - repo, err := defaultContext.RepositoryForConfig([]byte(spec), nil) - Expect(err).ToNot(HaveOccurred()) - Expect(repo).ToNot(BeNil()) - - credentialsFromCtx, err := credentials.CredentialsForConsumer(defaultContext, expectedConsumerId) - Expect(err).ToNot(HaveOccurred()) - Expect(credentialsFromCtx).To(Equal(expectedCreds)) - }) - - It("retrieves credentials from encrypted server", func() { - svr := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { - writer.WriteHeader(200) - data, err := base64.StdEncoding.DecodeString(encryptedContainerRegistryCfg) - Expect(err).ToNot(HaveOccurred()) - _, err = writer.Write(data) - Expect(err).ToNot(HaveOccurred()) - })) - defer svr.Close() - - parsedURL, err := utils.ParseURL(svr.URL) - Expect(err).ToNot(HaveOccurred()) - - id := cpi.NewConsumerIdentity(identity.CONSUMER_TYPE) - id.SetNonEmptyValue(identity.ID_HOSTNAME, parsedURL.Host) - id.SetNonEmptyValue(identity.ID_SCHEME, parsedURL.Scheme) - id.SetNonEmptyValue(identity.ID_PATHPREFIX, strings.Trim(parsedURL.Path, "/")) - id.SetNonEmptyValue(identity.ID_PORT, parsedURL.Port()) - - creds := credentials.DirectCredentials{ - cpi.ATTR_KEY: encryptionKey, - } - defaultContext.SetCredentialsForConsumer(id, creds) - - spec := fmt.Sprintf(repoSpecTemplate, svr.URL, local.AESECB) - - repo, err := defaultContext.RepositoryForConfig([]byte(spec), nil) - Expect(err).ToNot(HaveOccurred()) - Expect(repo).ToNot(BeNil()) - - credentialsFromRepo, err := repo.LookupCredentials("test-credentials") - Expect(err).ToNot(HaveOccurred()) - Expect(credentialsFromRepo).To(Equal(expectedCreds)) - }) - - It("retrieves credentials from file", func() { - filename := "/container_registry" - fs := memoryfs.New() - vfsattr.Set(defaultContext, fs) - - file, err := fs.Create(filename) - Expect(err).ToNot(HaveOccurred()) - - _, err = file.Write([]byte(containerRegistryCfg)) - Expect(err).ToNot(HaveOccurred()) - - err = file.Close() - Expect(err).ToNot(HaveOccurred()) - - spec := fmt.Sprintf(repoSpecTemplate, "file://"+filename, local.Plaintext) - - repo, err := defaultContext.RepositoryForConfig([]byte(spec), nil) - Expect(err).ToNot(HaveOccurred()) - Expect(repo).ToNot(BeNil()) - - credentialsFromRepo, err := repo.LookupCredentials("test-credentials") - Expect(err).ToNot(HaveOccurred()) - Expect(credentialsFromRepo).To(Equal(expectedCreds)) - }) -}) diff --git a/pkg/contexts/credentials/repositories/gardenerconfig/repository.go b/pkg/contexts/credentials/repositories/gardenerconfig/repository.go deleted file mode 100644 index 70fc7606b..000000000 --- a/pkg/contexts/credentials/repositories/gardenerconfig/repository.go +++ /dev/null @@ -1,223 +0,0 @@ -package gardenerconfig - -import ( - "bytes" - "context" - "crypto/aes" - "crypto/cipher" - "fmt" - "io" - "net/http" - "net/url" - "sync" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/contexts/credentials/internal" - gardenercfgcpi "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/gardenerconfig/cpi" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/gardenerconfig/identity" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/vfsattr" - "github.com/open-component-model/ocm/pkg/errkind" -) - -type Cipher string - -const ( - Plaintext Cipher = "PLAINTEXT" - AESECB Cipher = "AES.ECB" -) - -type Repository struct { - ctx cpi.Context - lock sync.RWMutex - url string - configType gardenercfgcpi.ConfigType - cipher Cipher - key []byte - propagateConsumerIdentity bool - creds map[string]cpi.Credentials - fs vfs.FileSystem -} - -var _ cpi.ConsumerIdentityProvider = (*Repository)(nil) - -func NewRepository(ctx cpi.Context, url string, configType gardenercfgcpi.ConfigType, cipher Cipher, key []byte, propagateConsumerIdentity bool) (*Repository, error) { - r := &Repository{ - ctx: ctx, - url: url, - configType: configType, - cipher: cipher, - key: key, - propagateConsumerIdentity: propagateConsumerIdentity, - fs: vfsattr.Get(ctx), - } - if err := r.read(true); err != nil { - return nil, fmt.Errorf("unable to read repository content: %w", err) - } - return r, nil -} - -var _ cpi.Repository = &Repository{} - -func (r *Repository) GetConsumerId(uctx ...internal.UsageContext) internal.ConsumerIdentity { - id, err := identity.GetConsumerId(r.url) - if err != nil { - return nil - } - return id -} - -func (r *Repository) GetIdentityMatcher() string { - return identity.CONSUMER_TYPE -} - -func (r *Repository) ExistsCredentials(name string) (bool, error) { - r.lock.RLock() - defer r.lock.RUnlock() - - if err := r.read(false); err != nil { - return false, fmt.Errorf("unable to read repository content: %w", err) - } - - return r.creds[name] != nil, nil -} - -func (r *Repository) LookupCredentials(name string) (cpi.Credentials, error) { - r.lock.RLock() - defer r.lock.RUnlock() - - if err := r.read(false); err != nil { - return nil, fmt.Errorf("unable to read repository content: %w", err) - } - - auth, ok := r.creds[name] - if !ok { - return nil, cpi.ErrUnknownCredentials(name) - } - - return auth, nil -} - -func (r *Repository) WriteCredentials(name string, creds cpi.Credentials) (cpi.Credentials, error) { - return nil, errors.ErrNotSupported("write", "credentials", Type) -} - -func (r *Repository) read(force bool) error { - if !force && r.creds != nil { - return nil - } - - configReader, err := r.getRawConfig() - if err != nil { - return fmt.Errorf("unable to get config: %w", err) - } - if configReader == nil { - return nil - } - defer configReader.Close() - - handler := gardenercfgcpi.GetHandler(r.configType) - if handler == nil { - return errors.Newf("unable to get handler for config type %s", string(r.configType)) - } - - creds, err := handler.ParseConfig(configReader) - if err != nil { - return fmt.Errorf("unable to parse config: %w", err) - } - - r.creds = map[string]cpi.Credentials{} - for _, cred := range creds { - credName := cred.Name() - if _, ok := r.creds[credName]; !ok { - r.creds[credName] = cred.Properties() - } - if r.propagateConsumerIdentity { - getCredentials := func() (cpi.Credentials, error) { - return r.LookupCredentials(credName) - } - cg := credentialGetter{ - getCredentials: getCredentials, - } - r.ctx.SetCredentialsForConsumer(cred.ConsumerIdentity(), cg) - } - } - - return nil -} - -func (r *Repository) getRawConfig() (io.ReadCloser, error) { - u, err := url.Parse(r.url) - if err != nil { - return nil, fmt.Errorf("unable to parse url: %w", err) - } - - var reader io.ReadCloser - if u.Scheme == "file" { - f, err := r.fs.Open(u.Path) - if err != nil { - return nil, fmt.Errorf("unable to open file: %w", err) - } - reader = f - } else { - req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, u.String(), nil) - if err != nil { - return nil, fmt.Errorf("request to secret server failed: %w", err) - } - res, err := http.DefaultClient.Do(req) - if err != nil { - // the secret server might be temporarily not available. - // for these situations we should allow a retry at a later point in time - // while keeping the old data for the moment. - if errkind.IsRetryable(err) { - // TODO: log error - return nil, nil - } - return nil, fmt.Errorf("request to secret server failed: %w", err) - } - reader = res.Body - } - - switch r.cipher { - case AESECB: - var srcBuf bytes.Buffer - if _, err := io.Copy(&srcBuf, reader); err != nil { - return nil, fmt.Errorf("unable to read: %w", err) - } - if err := reader.Close(); err != nil { - return nil, fmt.Errorf("unable to close reader: %w", err) - } - block, err := aes.NewCipher(r.key) - if err != nil { - return nil, fmt.Errorf("unable to create cipher: %w", err) - } - dst := make([]byte, srcBuf.Len()) - if err := ecbDecrypt(block, dst, srcBuf.Bytes()); err != nil { - return nil, fmt.Errorf("unable to decrypt: %w", err) - } - - return io.NopCloser(bytes.NewBuffer(dst)), nil - case Plaintext: - return reader, nil - default: - return nil, errors.ErrNotImplemented("cipher algorithm", string(r.cipher), Type) - } -} - -func ecbDecrypt(block cipher.Block, dst, src []byte) error { - blockSize := block.BlockSize() - if len(src)%blockSize != 0 { - return fmt.Errorf("input must contain only full blocks (blocksize: %d; input length: %d)", blockSize, len(src)) - } - if len(dst) < len(src) { - return errors.New("destination is smaller than source") - } - for len(src) > 0 { - block.Decrypt(dst, src[:blockSize]) - src = src[blockSize:] - dst = dst[blockSize:] - } - return nil -} diff --git a/pkg/contexts/credentials/repositories/gardenerconfig/type.go b/pkg/contexts/credentials/repositories/gardenerconfig/type.go deleted file mode 100644 index 9824dcab6..000000000 --- a/pkg/contexts/credentials/repositories/gardenerconfig/type.go +++ /dev/null @@ -1,96 +0,0 @@ -package gardenerconfig - -import ( - "fmt" - - "github.com/mandelsoft/goutils/generics" - - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/contexts/credentials/internal" - gardenercfgcpi "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/gardenerconfig/cpi" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/gardenerconfig/identity" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -const ( - Type = "GardenerConfig" - TypeV1 = Type + runtime.VersionSeparator + "v1" -) - -func init() { - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](Type)) - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1)) -} - -// RepositorySpec describes a secret server based credential repository interface. -type RepositorySpec struct { - runtime.ObjectVersionedType `json:",inline"` - URL string `json:"url"` - ConfigType gardenercfgcpi.ConfigType `json:"configType"` - Cipher Cipher `json:"cipher"` - PropagateConsumerIdentity *bool `json:"propagateConsumerIdentity,omitempty"` -} - -var _ cpi.ConsumerIdentityProvider = (*RepositorySpec)(nil) - -// NewRepositorySpec creates a new memory RepositorySpec. -func NewRepositorySpec(url string, configType gardenercfgcpi.ConfigType, cipher Cipher, propagateConsumerIdentity ...bool) *RepositorySpec { - return &RepositorySpec{ - ObjectVersionedType: runtime.NewVersionedTypedObject(Type), - URL: url, - ConfigType: configType, - Cipher: cipher, - PropagateConsumerIdentity: generics.Pointer(utils.OptionalDefaultedBool(true, propagateConsumerIdentity...)), - } -} - -func (a *RepositorySpec) GetType() string { - return Type -} - -func (a *RepositorySpec) Repository(ctx cpi.Context, creds cpi.Credentials) (cpi.Repository, error) { - r := ctx.GetAttributes().GetOrCreateAttribute(ATTR_REPOS, newRepositories) - repos, ok := r.(*Repositories) - if !ok { - return nil, fmt.Errorf("failed to assert type %T to Responsitories", r) - } - - key, err := getKey(ctx, a.URL) - if err != nil { - return nil, fmt.Errorf("unable to get key from context: %w", err) - } - - return repos.GetRepository(ctx, a.URL, a.ConfigType, a.Cipher, key, utils.AsBool(a.PropagateConsumerIdentity, true)) -} - -func (a *RepositorySpec) GetConsumerId(uctx ...internal.UsageContext) internal.ConsumerIdentity { - id, err := identity.GetConsumerId(a.URL) - if err != nil { - return nil - } - return id -} - -func (a *RepositorySpec) GetIdentityMatcher() string { - return identity.CONSUMER_TYPE -} - -func getKey(cctx cpi.Context, configURL string) ([]byte, error) { - id, err := identity.GetConsumerId(configURL) - if err != nil { - return nil, err - } - - creds, err := cpi.CredentialsForConsumer(cctx, id) - if err != nil { - return nil, err - } - - var key string - if creds != nil { - key = creds.GetProperty(identity.ATTR_KEY) - } - - return []byte(key), nil -} diff --git a/pkg/contexts/credentials/repositories/init.go b/pkg/contexts/credentials/repositories/init.go deleted file mode 100644 index fbdbb91bc..000000000 --- a/pkg/contexts/credentials/repositories/init.go +++ /dev/null @@ -1,12 +0,0 @@ -package repositories - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/aliases" - _ "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/directcreds" - _ "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/dockerconfig" - _ "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/gardenerconfig" - _ "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/memory" - _ "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/memory/config" - _ "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/npm" - _ "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/vault" -) diff --git a/pkg/contexts/credentials/repositories/memory/cache.go b/pkg/contexts/credentials/repositories/memory/cache.go deleted file mode 100644 index aec6fd2b0..000000000 --- a/pkg/contexts/credentials/repositories/memory/cache.go +++ /dev/null @@ -1,31 +0,0 @@ -package memory - -import ( - "sync" - - "github.com/open-component-model/ocm/pkg/contexts/datacontext" -) - -const ATTR_REPOS = "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/memory" - -type Repositories struct { - lock sync.Mutex - repos map[string]*Repository -} - -func newRepositories(datacontext.Context) interface{} { - return &Repositories{ - repos: map[string]*Repository{}, - } -} - -func (r *Repositories) GetRepository(name string) *Repository { - r.lock.Lock() - defer r.lock.Unlock() - repo := r.repos[name] - if repo == nil { - repo = NewRepository(name) - r.repos[name] = repo - } - return repo -} diff --git a/pkg/contexts/credentials/repositories/memory/config/config_test.go b/pkg/contexts/credentials/repositories/memory/config/config_test.go deleted file mode 100644 index 139c1770f..000000000 --- a/pkg/contexts/credentials/repositories/memory/config/config_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package config_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/env" - - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/config" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/memory" -) - -var _ = Describe("configure credentials", func() { - var env *Environment - var ctx credentials.Context - var cfg config.Context - - BeforeEach(func() { - env = NewEnvironment(TestData()) - cfg = config.New() - ctx = credentials.WithConfigs(cfg).New() - }) - - AfterEach(func() { - env.Cleanup() - }) - - It("reads config with ref", func() { - data, err := vfs.ReadFile(env, "/testdata/creds.yaml") - Expect(err).To(Succeed()) - _, err = cfg.ApplyData(data, nil, "creds.yaml") - Expect(err).To(Succeed()) - - spec := memory.NewRepositorySpec("default") - repo, err := ctx.RepositoryForSpec(spec) - Expect(err).To(Succeed()) - mem := repo.(*memory.Repository) - Expect(mem.ExistsCredentials("ref")).To(BeTrue()) - creds, err := mem.LookupCredentials("ref") - Expect(err).To(Succeed()) - Expect(creds.Properties()).To(Equal(common.Properties{"username": "mandelsoft", "password": "specialsecret"})) - }) - - It("reads config with direct", func() { - data, err := vfs.ReadFile(env, "/testdata/creds.yaml") - Expect(err).To(Succeed()) - _, err = cfg.ApplyData(data, nil, "creds.yaml") - Expect(err).To(Succeed()) - - spec := memory.NewRepositorySpec("default") - repo, err := ctx.RepositoryForSpec(spec) - Expect(err).To(Succeed()) - mem := repo.(*memory.Repository) - Expect(mem.ExistsCredentials("direct")).To(BeTrue()) - creds, err := mem.LookupCredentials("direct") - Expect(err).To(Succeed()) - Expect(creds.Properties()).To(Equal(common.Properties{"username": "mandelsoft2", "password": "specialsecret2"})) - }) -}) diff --git a/pkg/contexts/credentials/repositories/memory/config/type.go b/pkg/contexts/credentials/repositories/memory/config/type.go deleted file mode 100644 index 3e1b5aecd..000000000 --- a/pkg/contexts/credentials/repositories/memory/config/type.go +++ /dev/null @@ -1,131 +0,0 @@ -package config - -import ( - "fmt" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/common" - cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/memory" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - ConfigType = "memory.credentials" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX - ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" -) - -func init() { - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1, usage)) -} - -// Config describes a configuration for the config context. -type Config struct { - runtime.ObjectVersionedType `json:",inline"` - RepoName string `json:"repoName"` - Credentials []CredentialsSpec `json:"credentials,omitempty"` -} - -type CredentialsSpec struct { - CredentialsName string `json:"credentialsName"` - // Reference refers to credentials store in some othe repo - Reference *cpi.GenericCredentialsSpec `json:"reference,omitempty"` - // Credentials are direct credentials (one of Reference or Credentials must be set) - Credentials common.Properties `json:"credentials"` -} - -// New creates a new memory ConfigSpec. -func New(repo string, credentials ...CredentialsSpec) *Config { - return &Config{ - ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), - RepoName: repo, - Credentials: credentials, - } -} - -func (a *Config) GetType() string { - return ConfigType -} - -func (a *Config) AddCredentials(name string, props common.Properties) error { - a.Credentials = append(a.Credentials, CredentialsSpec{CredentialsName: name, Credentials: props}) - return nil -} - -func (a *Config) AddCredentialsRef(name string, refname string, spec cpi.RepositorySpec) error { - repo, err := cpi.ToGenericRepositorySpec(spec) - if err != nil { - return fmt.Errorf("unable to convert cpi repository spec to generic: %w", err) - } - - ref := cpi.NewGenericCredentialsSpec(refname, repo) - a.Credentials = append(a.Credentials, CredentialsSpec{CredentialsName: name, Reference: ref}) - - return nil -} - -func (a *Config) ApplyTo(ctx cfgcpi.Context, target interface{}) error { - list := errors.ErrListf("applying config") - - t, ok := target.(cpi.Context) - if !ok { - return cfgcpi.ErrNoContext(ConfigType) - } - - repo, err := t.RepositoryForSpec(memory.NewRepositorySpec(a.RepoName)) - if err != nil { - return fmt.Errorf("unable to get repository for spec: %w", err) - } - - mem, ok := repo.(*memory.Repository) - if !ok { - return fmt.Errorf("invalid type assertion of type %T to memory.Repository", repo) - } - - for i, e := range a.Credentials { - var creds cpi.Credentials - if e.Reference != nil { - if len(e.Credentials) != 0 { - err = fmt.Errorf("credentials and reference set") - } else { - creds, err = e.Reference.Credentials(t) - } - } else { - creds = cpi.NewCredentials(e.Credentials) - } - if err != nil { - list.Add(errors.Wrapf(err, "config entry %d[%s]", i, e.CredentialsName)) - } - if creds != nil { - _, err = mem.WriteCredentials(e.CredentialsName, creds) - if err != nil { - list.Add(errors.Wrapf(err, "config entry %d", i)) - } - } - } - return list.Result() -} - -const usage = ` -The config type ` + ConfigType + ` can be used to define a list -of arbitrary credentials stored in a memory based credentials repository: - -
-    type: ` + ConfigType + `
-    repoName: default
-    credentials:
-      - credentialsName: ref
-        reference:  # refer to a credential set stored in some other credential repository
-          type: Credentials # this is a repo providing just one explicit credential set
-          properties:
-            username: mandelsoft
-            password: specialsecret
-      - credentialsName: direct
-        credentials: # direct credential specification
-            username: mandelsoft2
-            password: specialsecret2
-
-` diff --git a/pkg/contexts/credentials/repositories/memory/repo_test.go b/pkg/contexts/credentials/repositories/memory/repo_test.go deleted file mode 100644 index 0e7ca7b71..000000000 --- a/pkg/contexts/credentials/repositories/memory/repo_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package memory_test - -import ( - "encoding/json" - "reflect" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - local "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/memory" -) - -var DefaultContext = credentials.New() - -var _ = Describe("direct credentials", func() { - props := common.Properties{ - "user": "USER", - "password": "PASSWORD", - } - - props2 := common.Properties{ - "user": "OTHER", - "password": "OTHERPASSWORD", - } - - specdata := "{\"type\":\"Memory\",\"repoName\":\"test\"}" - - _ = props - - It("serializes repo spec", func() { - spec := local.NewRepositorySpec("test") - data, err := json.Marshal(spec) - Expect(err).To(Succeed()) - Expect(data).To(Equal([]byte(specdata))) - }) - It("deserializes repo spec", func() { - spec, err := DefaultContext.RepositorySpecForConfig([]byte(specdata), nil) - Expect(err).To(Succeed()) - Expect(reflect.TypeOf(spec).String()).To(Equal("*memory.RepositorySpec")) - Expect(spec.(*local.RepositorySpec).RepositoryName).To(Equal("test")) - }) - - It("resolves repository", func() { - repo, err := DefaultContext.RepositoryForConfig([]byte(specdata), nil) - Expect(err).To(Succeed()) - Expect(reflect.TypeOf(repo).String()).To(Equal("*memory.Repository")) - }) - - It("sets and retrieves credentials", func() { - repo, err := DefaultContext.RepositoryForConfig([]byte(specdata), nil) - Expect(err).To(Succeed()) - - _, err = repo.WriteCredentials("bibo", credentials.NewCredentials(props)) - Expect(err).To(Succeed()) - - creds, err := repo.LookupCredentials("bibo") - Expect(err).To(Succeed()) - Expect(creds.Properties()).To(Equal(props)) - - creds, err = repo.LookupCredentials("other") - Expect(err).NotTo(Succeed()) - Expect(creds).To(BeNil()) - }) - - It("caches repo", func() { - repo, err := DefaultContext.RepositoryForConfig([]byte(specdata), nil) - Expect(err).To(Succeed()) - - _, err = repo.WriteCredentials("bibo", credentials.NewCredentials(props)) - Expect(err).To(Succeed()) - - // re-request repo by spec - repo, err = DefaultContext.RepositoryForConfig([]byte(specdata), nil) - Expect(err).To(Succeed()) - - creds, err := repo.LookupCredentials("bibo") - Expect(err).To(Succeed()) - Expect(creds.Properties()).To(Equal(props)) - - creds, err = repo.LookupCredentials("other") - Expect(err).NotTo(Succeed()) - Expect(creds).To(BeNil()) - }) - - It("caches repo in two contexts", func() { - ctx1 := DefaultContext - ctx2 := credentials.New() - - // write to first context - repo1, err := ctx1.RepositoryForConfig([]byte(specdata), nil) - Expect(err).To(Succeed()) - - _, err = repo1.WriteCredentials("bibo", credentials.NewCredentials(props)) - Expect(err).To(Succeed()) - - // request repo in second context - repo2, err := ctx2.RepositoryForConfig([]byte(specdata), nil) - Expect(err).To(Succeed()) - - creds, err := repo2.LookupCredentials("bibo") - Expect(err).NotTo(Succeed()) - Expect(creds).To(BeNil()) - - // write to second context - _, err = repo2.WriteCredentials("bibo", credentials.NewCredentials(props2)) - Expect(err).To(Succeed()) - - creds, err = repo2.LookupCredentials("bibo") - Expect(err).To(Succeed()) - Expect(creds.Properties()).To(Equal(props2)) - - // check first context - creds, err = repo1.LookupCredentials("bibo") - Expect(err).To(Succeed()) - Expect(creds.Properties()).To(Equal(props)) - }) -}) diff --git a/pkg/contexts/credentials/repositories/memory/repository.go b/pkg/contexts/credentials/repositories/memory/repository.go deleted file mode 100644 index 5149f7992..000000000 --- a/pkg/contexts/credentials/repositories/memory/repository.go +++ /dev/null @@ -1,47 +0,0 @@ -package memory - -import ( - "sync" - - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" -) - -type Repository struct { - lock sync.RWMutex - name string - credentials map[string]cpi.Credentials -} - -func NewRepository(name string) *Repository { - return &Repository{ - name: name, - credentials: map[string]cpi.Credentials{}, - } -} - -var _ cpi.Repository = &Repository{} - -func (r *Repository) ExistsCredentials(name string) (bool, error) { - r.lock.RLock() - defer r.lock.RUnlock() - _, ok := r.credentials[name] - return ok, nil -} - -func (r *Repository) LookupCredentials(name string) (cpi.Credentials, error) { - r.lock.RLock() - defer r.lock.RUnlock() - c, ok := r.credentials[name] - if ok { - return cpi.NewCredentials(c.Properties()), nil - } - return nil, cpi.ErrUnknownCredentials(name) -} - -func (r *Repository) WriteCredentials(name string, creds cpi.Credentials) (cpi.Credentials, error) { - c := cpi.NewCredentials(creds.Properties()) - r.lock.Lock() - defer r.lock.Unlock() - r.credentials[name] = c - return c, nil -} diff --git a/pkg/contexts/credentials/repositories/memory/type.go b/pkg/contexts/credentials/repositories/memory/type.go deleted file mode 100644 index fd2812d76..000000000 --- a/pkg/contexts/credentials/repositories/memory/type.go +++ /dev/null @@ -1,45 +0,0 @@ -package memory - -import ( - "fmt" - - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - Type = "Memory" - TypeV1 = Type + runtime.VersionSeparator + "v1" -) - -func init() { - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](Type)) - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1)) -} - -// RepositorySpec describes a memory based repository interface. -type RepositorySpec struct { - runtime.ObjectVersionedType `json:",inline"` - RepositoryName string `json:"repoName"` -} - -// NewRepositorySpec creates a new memory RepositorySpec. -func NewRepositorySpec(name string) *RepositorySpec { - return &RepositorySpec{ - ObjectVersionedType: runtime.NewVersionedTypedObject(Type), - RepositoryName: name, - } -} - -func (a *RepositorySpec) GetType() string { - return Type -} - -func (a *RepositorySpec) Repository(ctx cpi.Context, creds cpi.Credentials) (cpi.Repository, error) { - r := ctx.GetAttributes().GetOrCreateAttribute(ATTR_REPOS, newRepositories) - repos, ok := r.(*Repositories) - if !ok { - return nil, fmt.Errorf("failed to assert type %T to Repositories", r) - } - return repos.GetRepository(a.RepositoryName), nil -} diff --git a/pkg/contexts/credentials/repositories/npm/a_usage.go b/pkg/contexts/credentials/repositories/npm/a_usage.go deleted file mode 100644 index a8b77fb9d..000000000 --- a/pkg/contexts/credentials/repositories/npm/a_usage.go +++ /dev/null @@ -1,18 +0,0 @@ -package npm - -import ( - "github.com/open-component-model/ocm/pkg/listformat" -) - -var usage = ` -This repository type can be used to access credentials stored in a file -following the NPM npmrc format (~/.npmrc). It take into account the -credentials helper section, also. If enabled, the described -credentials will be automatically assigned to appropriate consumer ids. -` - -var format = `The repository specification supports the following fields: -` + listformat.FormatListElements("", listformat.StringElementDescriptionList{ - "npmrcFile", "*string*: the file path to a NPM npmrc file", - "propagateConsumerIdentity", "*bool*(optional): enable consumer id propagation", -}) diff --git a/pkg/contexts/credentials/repositories/npm/cache.go b/pkg/contexts/credentials/repositories/npm/cache.go deleted file mode 100644 index 15b2f0e74..000000000 --- a/pkg/contexts/credentials/repositories/npm/cache.go +++ /dev/null @@ -1,33 +0,0 @@ -package npm - -import ( - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" -) - -type Cache struct { - repos map[string]*Repository -} - -func createCache(_ datacontext.Context) interface{} { - return &Cache{ - repos: map[string]*Repository{}, - } -} - -func (r *Cache) GetRepository(ctx cpi.Context, name string, prop bool) (*Repository, error) { - var ( - err error = nil - repo *Repository - ) - if name != "" { - repo = r.repos[name] - } - if repo == nil { - repo, err = NewRepository(ctx, name, prop) - if err == nil { - r.repos[name] = repo - } - } - return repo, err -} diff --git a/pkg/contexts/credentials/repositories/npm/config.go b/pkg/contexts/credentials/repositories/npm/config.go deleted file mode 100644 index d61cb00d4..000000000 --- a/pkg/contexts/credentials/repositories/npm/config.go +++ /dev/null @@ -1,56 +0,0 @@ -package npm - -import ( - "bufio" - "os" - "strings" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/utils" -) - -type npmConfig map[string]string - -// readNpmConfigFile reads "~/.npmrc" file line by line, parse it and return the result as a npmConfig. -func readNpmConfigFile(path string) (npmConfig, string, error) { - path, err := utils.ResolvePath(path) - if err != nil { - return nil, path, errors.Wrapf(err, "cannot resolve path %q", path) - } - - // Open the file - file, err := os.Open(path) - if err != nil { - return nil, path, err - } - defer file.Close() - - // Create a new scanner and read the file line by line - scanner := bufio.NewScanner(file) - cfg := make(map[string]string) - for scanner.Scan() { - line := scanner.Text() - line, authFound := strings.CutPrefix(line, "//") - if !authFound { - // e.g. 'global=false' - continue - } - // Split the line into key and value - parts := strings.SplitN(line, ":_authToken=", 2) - if len(parts) == 2 { - if strings.HasSuffix(parts[0], "/") { - cfg[parts[0][:len(parts[0])-1]] = parts[1] - } else { - cfg[parts[0]] = parts[1] - } - } - } - - // Check for errors - if err = scanner.Err(); err != nil { - return nil, path, err - } - - return cfg, path, nil -} diff --git a/pkg/contexts/credentials/repositories/npm/config_test.go b/pkg/contexts/credentials/repositories/npm/config_test.go deleted file mode 100644 index 177c60778..000000000 --- a/pkg/contexts/credentials/repositories/npm/config_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package npm_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/npm/identity" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/npm" -) - -var _ = Describe("Config deserialization Test Environment", func() { - It("read .npmrc", func() { - ctx := credentials.New() - repo := Must(npm.NewRepository(ctx, "testdata/.npmrc")) - Expect(Must(repo.LookupCredentials("registry.npmjs.org")).Properties()).To(Equal(common.Properties{identity.ATTR_TOKEN: "npm_TOKEN"})) - Expect(Must(repo.LookupCredentials("npm.registry.acme.com/api/npm")).Properties()).To(Equal(common.Properties{identity.ATTR_TOKEN: "bearer_TOKEN"})) - }) - - It("propagates credentials", func() { - ctx := credentials.New() - spec := npm.NewRepositorySpec("testdata/.npmrc") - _ = Must(ctx.RepositoryForSpec(spec)) - id := Must(identity.GetConsumerId("registry.npmjs.org", "pkg")) - creds := Must(credentials.CredentialsForConsumer(ctx, id)) - Expect(creds).NotTo(BeNil()) - Expect(creds.GetProperty(identity.ATTR_TOKEN)).To(Equal("npm_TOKEN")) - }) - - It("has description", func() { - ctx := credentials.New() - t := ctx.RepositoryTypes().GetType(npm.TypeV1) - Expect(t).NotTo(BeNil()) - Expect(t.Description()).NotTo(Equal("")) - }) -}) diff --git a/pkg/contexts/credentials/repositories/npm/default.go b/pkg/contexts/credentials/repositories/npm/default.go deleted file mode 100644 index 1c11e20cd..000000000 --- a/pkg/contexts/credentials/repositories/npm/default.go +++ /dev/null @@ -1,49 +0,0 @@ -package npm - -import ( - "fmt" - "os" - - "github.com/mandelsoft/filepath/pkg/filepath" - "github.com/mandelsoft/vfs/pkg/osfs" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/contexts/config" - credcfg "github.com/open-component-model/ocm/pkg/contexts/credentials/config" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils/defaultconfigregistry" -) - -const ( - ConfigFileName = ".npmrc" -) - -func init() { - defaultconfigregistry.RegisterDefaultConfigHandler(DefaultConfigHandler, desc) -} - -func DefaultConfig() (string, error) { - d, err := os.UserHomeDir() - if err != nil { - return "", err - } - return filepath.Join(d, ConfigFileName), nil -} - -func DefaultConfigHandler(cfg config.Context) (string, config.Config, error) { - // use docker config as default config for ocm cli - d, err := DefaultConfig() - if err != nil { - return "", nil, nil - } - if ok, err := vfs.FileExists(osfs.OsFs, d); ok && err == nil { - ccfg := credcfg.New() - ccfg.AddRepository(NewRepositorySpec(d, true)) - return d, ccfg, nil - } - return "", nil, nil -} - -var desc = fmt.Sprintf(` -The npm configuration file at ~/%s is -read to feed in the configured credentials for NPM registries. -`, ConfigFileName) diff --git a/pkg/contexts/credentials/repositories/npm/provider.go b/pkg/contexts/credentials/repositories/npm/provider.go deleted file mode 100644 index a60520437..000000000 --- a/pkg/contexts/credentials/repositories/npm/provider.go +++ /dev/null @@ -1,51 +0,0 @@ -package npm - -import ( - npm "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/npm/identity" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/logging" -) - -type ConsumerProvider struct { - npmrcPath string -} - -var _ cpi.ConsumerProvider = (*ConsumerProvider)(nil) - -func (p *ConsumerProvider) Unregister(_ cpi.ProviderIdentity) { -} - -func (p *ConsumerProvider) Match(ectx cpi.EvaluationContext, req cpi.ConsumerIdentity, cur cpi.ConsumerIdentity, m cpi.IdentityMatcher) (cpi.CredentialsSource, cpi.ConsumerIdentity) { - return p.get(req, cur, m) -} - -func (p *ConsumerProvider) Get(req cpi.ConsumerIdentity) (cpi.CredentialsSource, bool) { - creds, _ := p.get(req, nil, cpi.CompleteMatch) - return creds, creds != nil -} - -func (p *ConsumerProvider) get(requested cpi.ConsumerIdentity, currentFound cpi.ConsumerIdentity, m cpi.IdentityMatcher) (cpi.CredentialsSource, cpi.ConsumerIdentity) { - all, path, err := readNpmConfigFile(p.npmrcPath) - if err != nil { - log := logging.Context().Logger(npm.REALM) - log.LogError(err, "Failed to read npmrc file", "path", path) - return nil, nil - } - - var creds cpi.CredentialsSource - - for key, value := range all { - id, err := npm.GetConsumerId("https://"+key, "") - if err != nil { - log := logging.Context().Logger(npm.REALM) - log.LogError(err, "Failed to get consumer id", "key", key, "value", value) - return nil, nil - } - if m(requested, currentFound, id) { - creds = newCredentials(value) - currentFound = id - } - } - - return creds, currentFound -} diff --git a/pkg/contexts/credentials/repositories/npm/repository.go b/pkg/contexts/credentials/repositories/npm/repository.go deleted file mode 100644 index 0cd48ffa4..000000000 --- a/pkg/contexts/credentials/repositories/npm/repository.go +++ /dev/null @@ -1,88 +0,0 @@ -package npm - -import ( - "fmt" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/common" - npmCredentials "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/npm/identity" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/utils" -) - -const PROVIDER = "ocm.software/credentialprovider/" + Type - -type Repository struct { - ctx cpi.Context - path string - propagate bool - npmrc npmConfig -} - -func NewRepository(ctx cpi.Context, path string, prop ...bool) (*Repository, error) { - return newRepository(ctx, path, utils.OptionalDefaultedBool(true, prop...)) -} - -func newRepository(ctx cpi.Context, path string, prop bool) (*Repository, error) { - r := &Repository{ - ctx: ctx, - path: path, - propagate: prop, - } - err := r.Read(true) - return r, err -} - -var _ cpi.Repository = &Repository{} - -func (r *Repository) ExistsCredentials(name string) (bool, error) { - err := r.Read(false) - if err != nil { - return false, err - } - return r.npmrc[name] != "", nil -} - -func (r *Repository) LookupCredentials(name string) (cpi.Credentials, error) { - exists, err := r.ExistsCredentials(name) - if err != nil { - return nil, err - } - if !exists { - return nil, errors.ErrNotFound("credentials", name, Type) - } - return newCredentials(r.npmrc[name]), nil -} - -func (r *Repository) WriteCredentials(_ string, _ cpi.Credentials) (cpi.Credentials, error) { - return nil, errors.ErrNotSupported("write", "credentials", Type) -} - -func (r *Repository) Read(force bool) error { - if !force && r.npmrc != nil { - return nil - } - - if r.path == "" { - return errors.New("npmrc path not provided") - } - cfg, path, err := readNpmConfigFile(r.path) - if err != nil { - return fmt.Errorf("failed to load npmrc: %w", err) - } - id := cpi.ProviderIdentity(PROVIDER + "/" + path) - - if r.propagate { - r.ctx.RegisterConsumerProvider(id, &ConsumerProvider{r.path}) - } - r.npmrc = cfg - return nil -} - -func newCredentials(token string) cpi.Credentials { - props := common.Properties{ - npmCredentials.ATTR_TOKEN: token, - } - return cpi.NewCredentials(props) -} diff --git a/pkg/contexts/credentials/repositories/npm/repository_test.go b/pkg/contexts/credentials/repositories/npm/repository_test.go deleted file mode 100644 index 2e4442a50..000000000 --- a/pkg/contexts/credentials/repositories/npm/repository_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package npm_test - -import ( - "encoding/json" - "reflect" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - 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() { - props := common.Properties{ - npmCredentials.ATTR_TOKEN: "npm_TOKEN", - } - - props2 := common.Properties{ - npmCredentials.ATTR_TOKEN: "bearer_TOKEN", - } - - var DefaultContext credentials.Context - - BeforeEach(func() { - DefaultContext = credentials.New() - }) - - specdata := "{\"type\":\"NPMConfig\",\"npmrcFile\":\"testdata/.npmrc\"}" - - It("serializes repo spec", func() { - spec := local.NewRepositorySpec("testdata/.npmrc") - data := Must(json.Marshal(spec)) - Expect(data).To(Equal([]byte(specdata))) - }) - - It("deserializes repo spec", func() { - spec := Must(DefaultContext.RepositorySpecForConfig([]byte(specdata), nil)) - Expect(reflect.TypeOf(spec).String()).To(Equal("*npm.RepositorySpec")) - Expect(spec.(*local.RepositorySpec).NpmrcFile).To(Equal("testdata/.npmrc")) - }) - - It("resolves repository", func() { - repo := Must(DefaultContext.RepositoryForConfig([]byte(specdata), nil)) - Expect(reflect.TypeOf(repo).String()).To(Equal("*npm.Repository")) - }) - - It("retrieves credentials", func() { - repo := Must(DefaultContext.RepositoryForConfig([]byte(specdata), nil)) - - creds := Must(repo.LookupCredentials("registry.npmjs.org")) - Expect(creds.Properties()).To(Equal(props)) - - creds = Must(repo.LookupCredentials("npm.registry.acme.com/api/npm")) - Expect(creds.Properties()).To(Equal(props2)) - }) - - It("can access the default context", func() { - ctx := credentials.New() - - r := runtimefinalizer.GetRuntimeFinalizationRecorder(ctx) - Expect(r).NotTo(BeNil()) - - Must(ctx.RepositoryForConfig([]byte(specdata), nil)) - - ci := cpi.NewConsumerIdentity(npmCredentials.CONSUMER_TYPE) - Expect(ci).NotTo(BeNil()) - credentials := Must(cpi.CredentialsForConsumer(ctx.CredentialsContext(), ci)) - Expect(credentials).NotTo(BeNil()) - Expect(credentials.Properties()).To(Equal(props)) - }) -}) diff --git a/pkg/contexts/credentials/repositories/npm/type.go b/pkg/contexts/credentials/repositories/npm/type.go deleted file mode 100644 index d2b695851..000000000 --- a/pkg/contexts/credentials/repositories/npm/type.go +++ /dev/null @@ -1,62 +0,0 @@ -package npm - -import ( - "fmt" - - "github.com/mandelsoft/goutils/generics" - - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -const ( - // Type is the type of the NPMConfig. - Type = "NPMConfig" - TypeV1 = Type + runtime.VersionSeparator + "v1" -) - -func init() { - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](Type)) - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1, cpi.WithDescription(usage), cpi.WithFormatSpec(format))) -} - -// RepositorySpec describes a docker npmrc based credential repository interface. -type RepositorySpec struct { - runtime.ObjectVersionedType `json:",inline"` - NpmrcFile string `json:"npmrcFile,omitempty"` - PropgateConsumerIdentity *bool `json:"propagateConsumerIdentity,omitempty"` -} - -// NewRepositorySpec creates a new memory RepositorySpec. -func NewRepositorySpec(path string, propagate ...bool) *RepositorySpec { - var p *bool - if path == "" { - d, err := DefaultConfig() - if err == nil { - path = d - } - } - if len(propagate) > 0 { - p = generics.Pointer(utils.OptionalDefaultedBool(true, propagate...)) - } - - return &RepositorySpec{ - ObjectVersionedType: runtime.NewVersionedTypedObject(Type), - NpmrcFile: path, - PropgateConsumerIdentity: p, - } -} - -func (rs *RepositorySpec) GetType() string { - return Type -} - -func (rs *RepositorySpec) Repository(ctx cpi.Context, _ cpi.Credentials) (cpi.Repository, error) { - r := ctx.GetAttributes().GetOrCreateAttribute(".npmrc", createCache) - cache, ok := r.(*Cache) - if !ok { - return nil, fmt.Errorf("failed to assert type %T to Cache", r) - } - return cache.GetRepository(ctx, rs.NpmrcFile, utils.AsBool(rs.PropgateConsumerIdentity, true)) -} diff --git a/pkg/contexts/credentials/repositories/vault/a_usage.go b/pkg/contexts/credentials/repositories/vault/a_usage.go deleted file mode 100644 index 5c165c063..000000000 --- a/pkg/contexts/credentials/repositories/vault/a_usage.go +++ /dev/null @@ -1,56 +0,0 @@ -package vault - -import ( - "strings" - - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/vault/identity" - "github.com/open-component-model/ocm/pkg/listformat" -) - -func init() { - info := cpi.DefaultContext.ConsumerIdentityMatchers().GetInfo(identity.CONSUMER_TYPE) - idx := strings.Index(info.Description, "\n") - desc := ` -This repository type can be used to access credentials stored in a HashiCorp -Vault. - -It provides access to list of secrets stored under a dedicated path in -a vault namespace. This list can either explicitly be specified, or -it is taken from the metadata of a specified secret. - -The following custom metadata attributes are evaluated: -- ` + CUSTOM_SECRETS + ` this attribute may contain a comma separated list of - vault secrets, which should be exposed by this repository instance. - The names are evaluated under the path prefix used for the repository. -- ` + CUSTOM_CONSUMERID + ` this attribute may contain a JSON encoded - consumer id , this secret should be assigned to. -- type if no special attribute is defined this attribute - indicated to use the complete custom metadata as consumer id. - -It uses the ` + identity.CONSUMER_TYPE + ` identity matcher and consumer type -to requests credentials for the access. -` + info.Description[idx:] + ` - -It requires the following credential attributes: - -` + info.CredentialAttributes - - usage = desc -} - -var usage string - -var format = ` -The repository specification supports the following fields: -` + listformat.FormatListElements("", listformat.StringElementDescriptionList{ - "serverURL", "*string* (required): the URL of the vault instance", - "namespace", "*string* (optional): the namespace used to evaluate secrets", - "mountPath", "*string* (optional): the mount path to use (default: secrets)", - "path", "*string* (optional): the path prefix used to lookup secrets", - "secrets", "*[]string* (optional): list of secrets", - "propagateConsumerIdentity", "*bool*(optional): evaluate metadata for consumer id propagation", -}) + ` -If the secrets list is empty, all secret entries found in the given path -is read. -` diff --git a/pkg/contexts/credentials/repositories/vault/cache.go b/pkg/contexts/credentials/repositories/vault/cache.go deleted file mode 100644 index 12638d4d8..000000000 --- a/pkg/contexts/credentials/repositories/vault/cache.go +++ /dev/null @@ -1,44 +0,0 @@ -package vault - -import ( - "sync" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" -) - -const ATTR_REPOS = "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/vault" - -type Repositories struct { - lock sync.Mutex - repos map[cpi.ProviderIdentity]*Repository -} - -func newRepositories(datacontext.Context) interface{} { - return &Repositories{ - repos: map[cpi.ProviderIdentity]*Repository{}, - } -} - -func (r *Repositories) GetRepository(ctx cpi.Context, spec *RepositorySpec) (*Repository, error) { - var repo *Repository - - if spec.ServerURL == "" { - return nil, errors.ErrInvalid("server url") - } - r.lock.Lock() - defer r.lock.Unlock() - - var err error - key := spec.GetKey() - repo = r.repos[key] - if repo == nil { - repo, err = NewRepository(ctx, spec) - if err == nil { - r.repos[key] = repo - } - } - return repo, err -} diff --git a/pkg/contexts/credentials/repositories/vault/identity/identity.go b/pkg/contexts/credentials/repositories/vault/identity/identity.go deleted file mode 100644 index 4cae7c00f..000000000 --- a/pkg/contexts/credentials/repositories/vault/identity/identity.go +++ /dev/null @@ -1,124 +0,0 @@ -package identity - -import ( - "net" - "net/url" - "strings" - - "github.com/mandelsoft/goutils/errors" - - "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/listformat" -) - -const CONSUMER_TYPE = "HashiCorpVault" - -// identity properties. -const ( - ID_HOSTNAME = hostpath.ID_HOSTNAME - ID_SCHEMA = hostpath.ID_SCHEME - ID_PORT = hostpath.ID_PORT - ID_PATHPREFIX = hostpath.ID_PATHPREFIX - ID_MOUNTPATH = "mountPath" - ID_NAMESPACE = "namespace" -) - -// credential properties. -const ( - ATTR_AUTHMETH = "authmeth" - ATTR_TOKEN = cpi.ATTR_TOKEN - ATTR_ROLEID = "roleid" - ATTR_SECRETID = "secretid" -) - -const ( - AUTH_APPROLE = "approle" - AUTH_TOKEN = "token" -) - -var identityMatcher = hostpath.IdentityMatcher(CONSUMER_TYPE) - -func IdentityMatcher(request, cur, id cpi.ConsumerIdentity) bool { - if id[ID_NAMESPACE] != request[ID_NAMESPACE] { - return false - } - if id[ID_MOUNTPATH] != "" && id[ID_MOUNTPATH] != request[ID_MOUNTPATH] { - return false - } - return identityMatcher(request, cur, id) -} - -func init() { - attrs := listformat.FormatListElements("", listformat.StringElementDescriptionList{ - ATTR_AUTHMETH, "auth method", - ATTR_TOKEN, "vault token", - ATTR_ROLEID, "applrole role id", - ATTR_SECRETID, "applrole secret id", - }) - ids := listformat.FormatListElements("", listformat.StringElementDescriptionList{ - ID_HOSTNAME, "vault server host", - ID_SCHEMA, "(optional) URL scheme", - ID_PORT, "(optional) server port", - ID_NAMESPACE, "vault namespace", - ID_MOUNTPATH, "mount path", - ID_PATHPREFIX, "path prefix for secret", - }) - cpi.RegisterStandardIdentity(CONSUMER_TYPE, identityMatcher, - `HashiCorp Vault credential matcher - -This matcher matches credentials for a HashiCorp vault instance. -It uses the following identity attributes: -`+ids, - attrs+` -The only supported auth methods, so far, are token and approle. -`) -} - -func GetConsumerId(serverurl string, namespace string, mountpath string, secretpath string) (cpi.ConsumerIdentity, error) { - if serverurl == "" { - return nil, errors.Newf("server address must be given") - } - u, err := url.Parse(serverurl) - if err != nil { - return nil, errors.ErrInvalidWrap(err, "server url", serverurl) - } - - host, port, err := net.SplitHostPort(u.Host) - if err != nil { - if strings.LastIndex(host, ":") >= 0 { - return nil, errors.ErrInvalidWrap(err, "server url", serverurl) - } - host = u.Host - } - - id := cpi.ConsumerIdentity{ - cpi.ID_TYPE: CONSUMER_TYPE, - ID_HOSTNAME: host, - } - if u.Scheme != "" { - id[ID_SCHEMA] = u.Scheme - } - if port != "" { - id[ID_PORT] = port - } - if namespace != "" { - id[ID_NAMESPACE] = namespace - } - if mountpath != "" { - id[ID_MOUNTPATH] = mountpath - } - - if secretpath != "" { - id[ID_PATHPREFIX] = secretpath - } - return id, nil -} - -func GetCredentials(ctx cpi.ContextProvider, serverurl, namespace string, mountpath, secretpath string) (cpi.Credentials, error) { - id, err := GetConsumerId(serverurl, namespace, mountpath, secretpath) - if err != nil { - return nil, err - } - return cpi.CredentialsForConsumer(ctx.CredentialsContext(), id, IdentityMatcher) -} diff --git a/pkg/contexts/credentials/repositories/vault/logging.go b/pkg/contexts/credentials/repositories/vault/logging.go deleted file mode 100644 index e5ccfdaeb..000000000 --- a/pkg/contexts/credentials/repositories/vault/logging.go +++ /dev/null @@ -1,10 +0,0 @@ -package vault - -import ( - ocmlog "github.com/open-component-model/ocm/pkg/logging" -) - -var ( - REALM = ocmlog.DefineSubRealm("HashiCorp Vault Access", "credentials", "vault") - log = ocmlog.DynamicLogger(REALM) -) diff --git a/pkg/contexts/credentials/repositories/vault/options.go b/pkg/contexts/credentials/repositories/vault/options.go deleted file mode 100644 index 59d292583..000000000 --- a/pkg/contexts/credentials/repositories/vault/options.go +++ /dev/null @@ -1,96 +0,0 @@ -package vault - -import ( - "github.com/mandelsoft/goutils/optionutils" - "golang.org/x/exp/slices" - - "github.com/open-component-model/ocm/pkg/utils" -) - -type Option = optionutils.Option[*Options] - -type Options struct { - Namespace string `json:"namespace,omitempty"` - MountPath string `json:"mountPath,omitempty"` - Path string `json:"path,omitempty"` - Secrets []string `json:"secrets,omitempty"` - PropgateConsumerIdentity bool `json:"propagateConsumerIdentity,omitempty"` -} - -var _ Option = (*Options)(nil) - -func (o *Options) ApplyTo(opts *Options) { - if o.Namespace != "" { - opts.Namespace = o.Namespace - } - if o.MountPath != "" { - opts.MountPath = o.MountPath - } - if o.Path != "" { - opts.Path = o.Path - } - if o.Secrets != nil { - opts.Secrets = slices.Clone(o.Secrets) - } - opts.PropgateConsumerIdentity = o.PropgateConsumerIdentity -} - -//////////////////////////////////////////////////////////////////////////////// - -type ns string - -func (o ns) ApplyTo(opts *Options) { - opts.Namespace = string(o) -} - -func WithNamespace(s string) Option { - return ns(s) -} - -//////////////////////////////////////////////////////////////////////////////// - -type m string - -func (o m) ApplyTo(opts *Options) { - opts.MountPath = string(o) -} - -func WithMountPath(s string) Option { - return m(s) -} - -//////////////////////////////////////////////////////////////////////////////// - -type p string - -func (o p) ApplyTo(opts *Options) { - opts.Path = string(o) -} - -func WithPath(s string) Option { - return p(s) -} - -//////////////////////////////////////////////////////////////////////////////// - -type sec []string - -func (o sec) ApplyTo(opts *Options) { - opts.Secrets = append(opts.Secrets, []string(o)...) -} - -func WithSecrets(s ...string) Option { - return sec(slices.Clone(s)) -} - -//////////////////////////////////////////////////////////////////////////////// - -type pr bool - -func (o pr) ApplyTo(opts *Options) { - opts.PropgateConsumerIdentity = bool(o) -} - -func WithPropagation(b ...bool) Option { - return pr(utils.OptionalDefaultedBool(true, b...)) -} diff --git a/pkg/contexts/credentials/repositories/vault/provider.go b/pkg/contexts/credentials/repositories/vault/provider.go deleted file mode 100644 index 12d1a13d1..000000000 --- a/pkg/contexts/credentials/repositories/vault/provider.go +++ /dev/null @@ -1,320 +0,0 @@ -package vault - -import ( - "context" - "encoding/json" - "net/http" - "path" - "strings" - "sync" - "time" - - "github.com/hashicorp/vault-client-go" - "github.com/mandelsoft/goutils/errors" - "golang.org/x/exp/slices" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/contexts/credentials/internal" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/vault/identity" -) - -const PROVIDER = "ocm.software/credentialprovider/" + Type - -const ( - CUSTOM_SECRETS = "secrets" - CUSTOM_CONSUMERID = "consumerId" -) - -type mapping struct { - Id cpi.ConsumerIdentity - Name string -} - -type credentialCache struct { - creds cpi.CredentialsSource - credentials map[string]cpi.DirectCredentials - consumer []*mapping -} - -func newCredentialCache(creds cpi.CredentialsSource) *credentialCache { - return &credentialCache{ - creds: creds, - credentials: map[string]cpi.DirectCredentials{}, - } -} - -type ConsumerProvider struct { - lock sync.Mutex - repository *Repository - cache *credentialCache - - updated bool -} - -var ( - _ cpi.ConsumerProvider = (*ConsumerProvider)(nil) - _ cpi.ConsumerIdentityProvider = (*ConsumerProvider)(nil) -) - -func NewConsumerProvider(repo *Repository) (*ConsumerProvider, error) { - src, err := repo.ctx.GetCredentialsForConsumer(repo.id) - if err != nil { - return nil, err - } - return &ConsumerProvider{ - cache: newCredentialCache(src), - repository: repo, - }, nil -} - -func (p *ConsumerProvider) String() string { - return p.repository.id.String() -} - -func (p *ConsumerProvider) GetConsumerId(uctx ...internal.UsageContext) internal.ConsumerIdentity { - return p.repository.GetConsumerId() -} - -func (p *ConsumerProvider) GetIdentityMatcher() string { - return p.repository.GetIdentityMatcher() -} - -func (p *ConsumerProvider) update(ectx cpi.EvaluationContext) error { - if p.updated { - return nil - } - credsrc, err := cpi.GetCredentialsForConsumer(p.repository.ctx, ectx, p.repository.id, identity.IdentityMatcher) - if err != nil { - return err - } - creds, err := credsrc.Credentials(p.repository.ctx) - if err != nil { - return err - } - err = p.validateCreds(creds) - if err != nil { - return err - } - - ctx := context.Background() - - client, err := vault.New( - vault.WithAddress(p.repository.spec.ServerURL), - vault.WithRequestTimeout(30*time.Second), - ) - if err != nil { - return err - } - - // vault.WithMountPath("piper/PIPELINE-GROUP-4953/PIPELINE-25042/appRoleCredentials"), - token, err := p.getToken(ctx, client, creds) - if err != nil { - return err - } - - if err := client.SetToken(token); err != nil { - return err - } - if err := client.SetNamespace(p.repository.spec.Namespace); err != nil { - return err - } - - cache := newCredentialCache(credsrc) - - // TODO: support for pure path based access for other secret engine types - secrets := slices.Clone(p.repository.spec.Secrets) - if len(secrets) == 0 { - s, err := client.Secrets.KvV2List(ctx, p.repository.spec.Path, - vault.WithMountPath(p.repository.spec.MountPath)) - if err != nil { - p.error(err, "error listing secrets", "") - return err - } - for _, k := range s.Data.Keys { - if !strings.HasSuffix(k, "/") { - secrets = append(secrets, k) - } - } - } - for i := 0; i < len(secrets); i++ { - n := secrets[i] - creds, id, list, err := p.read(ctx, client, n) - p.error(err, "error reading vault secret", n) - if err == nil { - for _, a := range list { - if !slices.Contains(secrets, a) { - secrets = append(secrets, a) - } - } - if len(id) > 0 { - cache.consumer = append(cache.consumer, &mapping{ - Id: cpi.ConsumerIdentity(id), - Name: n, - }) - } - if len(creds) > 0 { - cache.credentials[n] = cpi.DirectCredentials(creds) - } - } - } - p.cache = cache - p.updated = true - return nil -} - -func (p *ConsumerProvider) validateCreds(creds cpi.Credentials) error { - m := creds.GetProperty(identity.ATTR_AUTHMETH) - if m == "" { - return errors.ErrRequired(identity.ATTR_AUTHMETH) - } - meth := methods.Get(m) - if meth == nil { - return errors.ErrInvalid(identity.ATTR_AUTHMETH, m) - } - return meth.Validate(creds) -} - -func (p *ConsumerProvider) getToken(ctx context.Context, client *vault.Client, creds cpi.Credentials) (string, error) { - m := creds.GetProperty(identity.ATTR_AUTHMETH) - return methods.Get(m).GetToken(ctx, client, p.repository.spec.Namespace, creds) -} - -func (p *ConsumerProvider) error(err error, msg string, secret string, keypairs ...interface{}) { - if err == nil { - return - } - f := log.Info - var v *vault.ResponseError - if errors.As(err, &v) && v.StatusCode != http.StatusNotFound { - f = log.Error - } - f(msg, append(keypairs, - "server", p.repository.spec.ServerURL, - "namespace", p.repository.spec.Namespace, - "engine", p.repository.spec.MountPath, - "path", path.Join(p.repository.spec.Path, secret), - "error", err.Error(), - )..., - ) -} - -func (p *ConsumerProvider) read(ctx context.Context, client *vault.Client, secret string) (common.Properties, common.Properties, []string, error) { - // read the secret - - secret = path.Join(p.repository.spec.Path, secret) - s, err := client.Secrets.KvV2Read(ctx, secret, - vault.WithMountPath(p.repository.spec.MountPath)) - if err != nil { - return nil, nil, nil, err - } - - var id common.Properties - var list []string - props := getProps(s.Data.Data) - - if meta, ok := s.Data.Metadata["custom_metadata"].(map[string]interface{}); ok { - sub := false - if cid := meta[CUSTOM_CONSUMERID]; cid != nil { - id = common.Properties{} - if err := json.Unmarshal([]byte(cid.(string)), &id); err != nil { - id = nil - } - sub = true - } - if cid := meta[CUSTOM_SECRETS]; cid != nil { - if s, ok := meta[CUSTOM_SECRETS].(string); ok { - for _, e := range strings.Split(s, ",") { - e = strings.TrimSpace(e) - if e != "" { - list = append(list, e) - } - } - } - sub = true - } - if _, ok := meta[cpi.ID_TYPE]; !sub && ok { - id = getProps(meta) - } - } - return props, id, list, nil -} - -func getProps(data map[string]interface{}) common.Properties { - props := common.Properties{} - for k, v := range data { - if s, ok := v.(string); ok { - props[k] = s - } - } - return props -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ConsumerProvider interface - -func (p *ConsumerProvider) Unregister(id cpi.ProviderIdentity) { -} - -func (p *ConsumerProvider) Match(ectx cpi.EvaluationContext, req cpi.ConsumerIdentity, cur cpi.ConsumerIdentity, m cpi.IdentityMatcher) (cpi.CredentialsSource, cpi.ConsumerIdentity) { - return p.get(ectx, req, cur, m) -} - -func (p *ConsumerProvider) Get(req cpi.ConsumerIdentity) (cpi.CredentialsSource, bool) { - creds, _ := p.get(nil, req, nil, cpi.CompleteMatch) - return creds, creds != nil -} - -func (p *ConsumerProvider) get(ectx cpi.EvaluationContext, req cpi.ConsumerIdentity, cur cpi.ConsumerIdentity, m cpi.IdentityMatcher) (cpi.CredentialsSource, cpi.ConsumerIdentity) { - if req.Equals(p.repository.id) { - return nil, cur - } - - p.lock.Lock() - defer p.lock.Unlock() - - err := p.update(ectx) - if err != nil { - log.Info("error accessing credentials provider", "error", err) - } - - var creds cpi.CredentialsSource - - for _, a := range p.cache.consumer { - if m(req, cur, a.Id) { - cur = a.Id - creds = p.cache.credentials[a.Name] - } - } - return creds, cur -} - -//////////////////////////////////////////////////////////////////////////////// -// lookup - -func (c *ConsumerProvider) ExistsCredentials(name string) (bool, error) { - c.lock.Lock() - defer c.lock.Unlock() - - err := c.update(nil) - if err != nil { - return false, err - } - _, ok := c.cache.credentials[name] - return ok, nil -} - -func (c *ConsumerProvider) LookupCredentials(name string) (cpi.Credentials, error) { - c.lock.Lock() - defer c.lock.Unlock() - - err := c.update(nil) - if err != nil { - return nil, err - } - src, ok := c.cache.credentials[name] - if ok { - return src, nil - } - return nil, nil -} diff --git a/pkg/contexts/credentials/repositories/vault/repo_test.go b/pkg/contexts/credentials/repositories/vault/repo_test.go deleted file mode 100644 index 581390405..000000000 --- a/pkg/contexts/credentials/repositories/vault/repo_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package vault_test - -import ( - "encoding/json" - "fmt" - "reflect" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "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" -) - -const ( - VAULT_ADDRESS = "127.0.0.1:8200" - VAULT_HTTP_URL = "http://" + VAULT_ADDRESS - VAULT_NAMESPACE = "test-namespace" - VAULT_MOUNT_PATH = "secret" - VAULT_PATH_REPO1 = "mysecrets/repo1" - VAULT_PATH_REPO2 = "mysecrets/repo2" -) - -var _ = Describe("", func() { - Context("serialization and deserialization", func() { - DefaultContext := credentials.New() - - specdata := fmt.Sprintf("{\"type\": %q, \"serverURL\": %q, \"namespace\": %q, \"mountPath\": %q, \"path\": %q, \"secrets\": [\"secret1\", \"secret2\", \"secret3\"], \"propagateConsumerIdentity\": true }", me.Type, "http://"+VAULT_ADDRESS, VAULT_NAMESPACE, VAULT_MOUNT_PATH, VAULT_PATH_REPO1) - spec := me.NewRepositorySpec("http://"+VAULT_ADDRESS, me.WithNamespace(VAULT_NAMESPACE), me.WithMountPath(VAULT_MOUNT_PATH), me.WithPath(VAULT_PATH_REPO1), me.WithSecrets("secret1", "secret2", "secret3"), me.WithPropagation()) - - specdata2 := fmt.Sprintf("{\"type\": %q, \"serverURL\": %q }", me.Type, "http://"+VAULT_ADDRESS) - spec2 := me.NewRepositorySpec("http://" + VAULT_ADDRESS) - - It("serializes repo spec", func() { - data := Must(json.Marshal(spec)) - Expect(data).To(YAMLEqual([]byte(specdata))) - - data = Must(json.Marshal(spec2)) - Expect(data).To(YAMLEqual([]byte(specdata2))) - }) - - It("deserializes repo spec", func() { - localspec := Must(DefaultContext.RepositorySpecForConfig([]byte(specdata), nil)) - Expect(reflect.TypeOf(localspec).String()).To(Equal("*vault.RepositorySpec")) - Expect(localspec).To(Equal(spec)) - - localspec = Must(DefaultContext.RepositorySpecForConfig([]byte(specdata2), nil)) - Expect(reflect.TypeOf(localspec).String()).To(Equal("*vault.RepositorySpec")) - Expect(localspec).To(Equal(spec2)) - }) - - It("resolves repository", func() { - // Since vault always requires credentials to be accessed, RepositoryForConfig checks whether credentials - // for a corresponding consumer exist. Thus, creating such credentials is required to test the method even - // though they are not used - consumerId := Must(identity.GetConsumerId(VAULT_HTTP_URL, VAULT_NAMESPACE, VAULT_MOUNT_PATH, VAULT_PATH_REPO1)) - creds := credentials.NewCredentials(common.Properties{ - identity.ATTR_AUTHMETH: identity.AUTH_TOKEN, - identity.ATTR_TOKEN: "token", - }) - DefaultContext.SetCredentialsForConsumer(consumerId, creds) - - repo := Must(DefaultContext.RepositoryForConfig([]byte(specdata), nil)) - Expect(repo).ToNot(BeNil()) - }) - }) -}) diff --git a/pkg/contexts/credentials/repositories/vault/repository.go b/pkg/contexts/credentials/repositories/vault/repository.go deleted file mode 100644 index 449db5667..000000000 --- a/pkg/contexts/credentials/repositories/vault/repository.go +++ /dev/null @@ -1,66 +0,0 @@ -package vault - -import ( - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/contexts/credentials/internal" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/vault/identity" -) - -type Repository struct { - ctx cpi.Context - spec *RepositorySpec - id cpi.ConsumerIdentity - provider *ConsumerProvider -} - -var ( - _ cpi.Repository = (*Repository)(nil) - _ cpi.ConsumerIdentityProvider = (*Repository)(nil) -) - -func NewRepository(ctx cpi.Context, spec *RepositorySpec) (*Repository, error) { - id, err := identity.GetConsumerId(spec.ServerURL, spec.Namespace, spec.MountPath, spec.Path) - if err != nil { - return nil, err - } - r := &Repository{ - ctx: ctx, - spec: spec, - id: id, - } - if spec.ServerURL == "" { - return nil, errors.ErrInvalid("server url") - } - r.provider, err = NewConsumerProvider(r) - if err != nil { - return nil, err - } - if spec.PropgateConsumerIdentity { - ctx.RegisterConsumerProvider(spec.GetKey(), r.provider) - } - return r, err -} - -var _ cpi.Repository = &Repository{} - -func (r *Repository) ExistsCredentials(name string) (bool, error) { - return r.provider.ExistsCredentials(name) -} - -func (r *Repository) LookupCredentials(name string) (cpi.Credentials, error) { - return r.provider.LookupCredentials(name) -} - -func (r *Repository) WriteCredentials(name string, creds cpi.Credentials) (cpi.Credentials, error) { - return nil, errors.ErrNotSupported("write", "credentials", Type) -} - -func (r *Repository) GetConsumerId(uctx ...internal.UsageContext) internal.ConsumerIdentity { - return r.id -} - -func (r *Repository) GetIdentityMatcher() string { - return identity.CONSUMER_TYPE -} diff --git a/pkg/contexts/credentials/repositories/vault/type.go b/pkg/contexts/credentials/repositories/vault/type.go deleted file mode 100644 index aa78f2b93..000000000 --- a/pkg/contexts/credentials/repositories/vault/type.go +++ /dev/null @@ -1,82 +0,0 @@ -package vault - -import ( - "encoding/json" - "fmt" - - "github.com/mandelsoft/goutils/optionutils" - "golang.org/x/exp/slices" - - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/contexts/credentials/internal" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/vault/identity" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - Type = "HashiCorpVault" - TypeV1 = Type + runtime.VersionSeparator + "v1" -) - -func init() { - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](Type)) - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1, cpi.WithDescription(usage), cpi.WithFormatSpec(format))) -} - -// RepositorySpec describes a docker config based credential repository interface. -type RepositorySpec struct { - runtime.ObjectVersionedType `json:",inline"` - ServerURL string `json:"serverURL"` - Options `json:",inline"` -} - -var _ cpi.ConsumerIdentityProvider = (*RepositorySpec)(nil) - -// NewRepositorySpec creates a new memory RepositorySpec. -func NewRepositorySpec(url string, opts ...Option) *RepositorySpec { - return &RepositorySpec{ - ObjectVersionedType: runtime.NewVersionedTypedObject(Type), - ServerURL: url, - Options: *optionutils.EvalOptions(opts...), - } -} - -func (a *RepositorySpec) GetType() string { - return Type -} - -func (a *RepositorySpec) Repository(ctx cpi.Context, creds cpi.Credentials) (cpi.Repository, error) { - r := ctx.GetAttributes().GetOrCreateAttribute(ATTR_REPOS, newRepositories) - repos, ok := r.(*Repositories) - if !ok { - return nil, fmt.Errorf("failed to assert type %T to Repositories", r) - } - spec := *a - spec.Secrets = slices.Clone(a.Secrets) - if spec.MountPath == "" { - spec.MountPath = "secret" - } - return repos.GetRepository(ctx, &spec) -} - -func (a *RepositorySpec) GetKey() cpi.ProviderIdentity { - spec := *a - spec.PropgateConsumerIdentity = false - data, err := json.Marshal(&spec) - if err == nil { - return cpi.ProviderIdentity(data) - } - return cpi.ProviderIdentity(spec.ServerURL) -} - -func (a *RepositorySpec) GetConsumerId(uctx ...internal.UsageContext) internal.ConsumerIdentity { - id, err := identity.GetConsumerId(a.ServerURL, a.Namespace, a.MountPath, a.Path) - if err != nil { - return nil - } - return id -} - -func (a *RepositorySpec) GetIdentityMatcher() string { - return identity.CONSUMER_TYPE -} diff --git a/pkg/contexts/credentials/utils.go b/pkg/contexts/credentials/utils.go deleted file mode 100644 index e33477a36..000000000 --- a/pkg/contexts/credentials/utils.go +++ /dev/null @@ -1,139 +0,0 @@ -package credentials - -import ( - "crypto/tls" - "crypto/x509" - "strings" - - "github.com/mandelsoft/goutils/errors" - "github.com/texttheater/golang-levenshtein/levenshtein" - - "github.com/open-component-model/ocm/pkg/contexts/credentials/internal" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/rootcertsattr" - "github.com/open-component-model/ocm/pkg/utils" -) - -func GetProvidedConsumerId(obj interface{}, uctx ...UsageContext) ConsumerIdentity { - return utils.UnwrappingCall(obj, func(provider ConsumerIdentityProvider) ConsumerIdentity { - return provider.GetConsumerId(uctx...) - }) -} - -func GetProvidedIdentityMatcher(obj interface{}) string { - return utils.UnwrappingCall(obj, func(provider ConsumerIdentityProvider) string { - return provider.GetIdentityMatcher() - }) -} - -func CredentialsFor(ctx ContextProvider, obj interface{}, uctx ...UsageContext) (Credentials, error) { - id := GetProvidedConsumerId(obj, uctx...) - if id == nil { - return nil, errors.ErrNotSupported(KIND_CONSUMER) - } - return CredentialsForConsumer(ctx, id) -} - -func GetRootCAs(ctx ContextProvider, creds Credentials) (*x509.CertPool, error) { - var rootCAs *x509.CertPool - var err error - - if creds != nil { - c := creds.GetProperty(internal.ATTR_CERTIFICATE_AUTHORITY) - if c != "" { - rootCAs = x509.NewCertPool() - rootCAs.AppendCertsFromPEM([]byte(c)) - } - } - if rootCAs == nil { - if ctx != nil { - rootCAs = rootcertsattr.Get(ctx.CredentialsContext()).GetRootCertPool(true) - } else { - rootCAs, err = x509.SystemCertPool() - if err != nil { - return nil, err - } - } - } - return rootCAs, nil -} - -func GetClientCerts(ctx ContextProvider, creds Credentials) ([]tls.Certificate, error) { - if creds != nil { - cert := creds.GetProperty(internal.ATTR_CERTIFICATE) - priv := creds.GetProperty(internal.ATTR_PRIVATE_KEY) - if cert == "" && priv == "" { - return nil, nil - } - if cert == "" || priv == "" { - return nil, errors.New("both, private key and certificate are required for tls client authentication") - } - if cert != "" && priv != "" { - tlsCert, err := tls.X509KeyPair([]byte(cert), []byte(priv)) - if err != nil { - return nil, err - } - return []tls.Certificate{tlsCert}, nil - } - } - return nil, nil -} - -func GuessConsumerType(ctxp ContextProvider, spec string) string { - matchers := ctxp.CredentialsContext().ConsumerIdentityMatchers() - lspec := strings.ToLower(spec) - - if matchers.Get(spec) == nil { - fix := "" - for _, i := range matchers.List() { - idx := strings.Index(i.Type, ".") - if idx > 0 && i.Type[:idx] == spec { - fix = i.Type - break - } - } - if fix == "" { - for _, i := range matchers.List() { - if strings.ToLower(i.Type) == lspec { - fix = i.Type - break - } - } - } - if fix == "" { - for _, i := range matchers.List() { - idx := strings.Index(i.Type, ".") - if idx > 0 && strings.ToLower(i.Type[:idx]) == lspec { - fix = i.Type - break - } - } - } - if fix == "" { - min := -1 - for _, i := range matchers.List() { - idx := strings.Index(i.Type, ".") - if idx > 0 { - d := levenshtein.DistanceForStrings([]rune(lspec), []rune(strings.ToLower(i.Type[:idx])), levenshtein.DefaultOptions) - if d < 5 && fix == "" || min > d { - fix = i.Type - min = d - } - } - } - } - if fix == "" { - min := -1 - for _, i := range matchers.List() { - d := levenshtein.DistanceForStrings([]rune(lspec), []rune(strings.ToLower(i.Type)), levenshtein.DefaultOptions) - if d < 5 && fix == "" || min > d { - fix = i.Type - min = d - } - } - } - if fix != "" { - return fix - } - } - return spec -} diff --git a/pkg/contexts/datacontext/action/api/action_test.go b/pkg/contexts/datacontext/action/api/action_test.go deleted file mode 100644 index be26d3014..000000000 --- a/pkg/contexts/datacontext/action/api/action_test.go +++ /dev/null @@ -1,108 +0,0 @@ -package api_test - -import ( - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/identity/hostpath" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/action/api" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const NAME = "testAction" - -const CONSUMER_TYPE = "TestAction" - -const ID_HOSTNAME = hostpath.ID_HOSTNAME - -func RegisterAction(registry api.ActionTypeRegistry) { - registry.RegisterAction(NAME, "test action", "nothing special", []string{ID_HOSTNAME}) - - registry.RegisterActionType(api.NewActionType[*ActionSpec, *ActionResult](NAME, "v1")) - registry.RegisterActionType(api.NewActionTypeByConverter[*ActionSpec, *ActionSpecV2, *ActionResult, *ActionResultV2](NAME, "v2", convertSpecV2{}, convertResultV2{})) -} - -func NewActionSpec(field string) api.ActionSpec { - return &ActionSpec{ - ObjectVersionedType: runtime.NewVersionedObjectType(runtime.TypeName(NAME, "v1")), - Field: field, - } -} - -func NewActionResult(msg string) api.ActionResult { - return &ActionResult{ - ObjectVersionedType: runtime.NewVersionedObjectType(runtime.TypeName(NAME, "v1")), - Message: msg, - } -} - -//////////////////////////////////////////////////////////////////////////////// -// internal version - -type ActionSpec struct { - runtime.ObjectVersionedType `json:",inline"` - Field string `json:"field"` -} - -func (a *ActionSpec) Selector() api.Selector { - return api.Selector(a.Field) -} - -func (a *ActionSpec) GetConsumerAttributes() common.Properties { - return common.Properties(credentials.NewConsumerIdentity(CONSUMER_TYPE, - ID_HOSTNAME, a.Field, - )) -} - -type ActionResult struct { - runtime.ObjectVersionedType `json:",inline"` - Message string `json:"message"` -} - -func (r ActionResult) GetMessage() string { - return r.Message -} - -//////////////////////////////////////////////////////////////////////////////// -// external version - -type ActionSpecV2 struct { - runtime.ObjectVersionedType `json:",inline"` - Data string `json:"data"` -} - -type ActionResultV2 struct { - runtime.ObjectVersionedType `json:",inline"` - Data string `json:"data"` -} - -type convertSpecV2 struct{} - -func (c convertSpecV2) ConvertFrom(in *ActionSpec) (*ActionSpecV2, error) { - return &ActionSpecV2{ - ObjectVersionedType: runtime.NewVersionedObjectType(runtime.TypeName(NAME, "v2")), - Data: in.Field, - }, nil -} - -func (c convertSpecV2) ConvertTo(in *ActionSpecV2) (*ActionSpec, error) { - return &ActionSpec{ - ObjectVersionedType: runtime.NewVersionedObjectType(runtime.TypeName(NAME, "v2")), - Field: in.Data, - }, nil -} - -type convertResultV2 struct{} - -func (c convertResultV2) ConvertFrom(in *ActionResult) (*ActionResultV2, error) { - return &ActionResultV2{ - ObjectVersionedType: runtime.NewVersionedObjectType(runtime.TypeName(NAME, "v2")), - Data: in.Message, - }, nil -} - -func (c convertResultV2) ConvertTo(in *ActionResultV2) (*ActionResult, error) { - return &ActionResult{ - ObjectVersionedType: runtime.NewVersionedObjectType(runtime.TypeName(NAME, "v2")), - Message: in.Data, - }, nil -} diff --git a/pkg/contexts/datacontext/action/api/interface.go b/pkg/contexts/datacontext/action/api/interface.go deleted file mode 100644 index eb3a7928f..000000000 --- a/pkg/contexts/datacontext/action/api/interface.go +++ /dev/null @@ -1,84 +0,0 @@ -package api - -import ( - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/runtime" -) - -type Action interface { - Name() string - Description() string - Usage() string - ConsumerAttributes() []string - GetVersion(string) ActionType - SupportedVersions() []string -} - -//////////////////////////////////////////////////////////////////////////////// -// Action Specification - -type Selector string - -func (s Selector) ApplyActionHandlerOptionTo(opts *Options) { - opts.Selectors = append(opts.Selectors, s) -} - -type ActionSpec interface { - runtime.VersionedTypedObject - SetVersion(string) - Selector() Selector - GetConsumerAttributes() common.Properties -} - -type ActionSpecType runtime.VersionedTypedObjectType[ActionSpec] - -//////////////////////////////////////////////////////////////////////////////// -// Action Result - -type ActionResult interface { - runtime.VersionedTypedObject - SetVersion(string) - SetType(string) - GetMessage() string -} - -// CommonResult is the minimal action result. -type CommonResult struct { - runtime.ObjectVersionedType `json:",inline"` - Message string `json:"message,omitempty"` -} - -func (r *CommonResult) GetMessage() string { - return r.Message -} - -func (r *CommonResult) SetType(typ string) { - r.Type = typ -} - -//////////////////////////////////////////////////////////////////////////////// -// Action Type - -type ActionResultType runtime.VersionedTypedObjectType[ActionResult] - -type ActionType interface { - runtime.VersionedTypedObject - SpecificationType() ActionSpecType - ResultType() ActionResultType -} - -//////////////////////////////////////////////////////////////////////////////// -// Options Type - -type Option interface { - ApplyActionHandlerOptionTo(*Options) -} - -type Options struct { - Action string - Selectors []Selector - Priority int - Versions []string -} - -var _ Option = (*Options)(nil) diff --git a/pkg/contexts/datacontext/action/api/registry.go b/pkg/contexts/datacontext/action/api/registry.go deleted file mode 100644 index 40b9af6ad..000000000 --- a/pkg/contexts/datacontext/action/api/registry.go +++ /dev/null @@ -1,235 +0,0 @@ -package api - -import ( - "fmt" - "sync" - - "github.com/mandelsoft/goutils/errors" - "golang.org/x/exp/slices" - - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -const ( - KIND_ACTION = "action" - KIND_ACTIONTYPE = "action type" -) - -type ActionTypeRegistry interface { - RegisterAction(name string, description string, usage string, attrs []string) error - RegisterActionType(typ ActionType) error - - DecodeActionSpec(data []byte, unmarshaler runtime.Unmarshaler) (ActionSpec, error) - EncodeActionSpec(spec ActionSpec, marshaler runtime.Marshaler) ([]byte, error) - - DecodeActionResult(data []byte, unmarshaler runtime.Unmarshaler) (ActionResult, error) - EncodeActionResult(spec ActionResult, marshaler runtime.Marshaler) ([]byte, error) - - GetAction(name string) Action - SupportedActionVersions(name string) []string - - Copy() ActionTypeRegistry -} - -type action struct { - lock sync.Mutex - name string - shortdesc string - usage string - attributes []string - types map[string]ActionType -} - -var _ Action = (*action)(nil) - -func (a *action) Name() string { - return a.name -} - -func (a *action) Description() string { - return a.shortdesc -} - -func (a *action) Usage() string { - return a.usage -} - -func (a *action) ConsumerAttributes() []string { - return a.attributes -} - -func (a *action) GetVersion(v string) ActionType { - a.lock.Lock() - defer a.lock.Unlock() - return a.types[v] -} - -func (a *action) SupportedVersions() []string { - return utils.StringMapKeys(a.types) -} - -type actionRegistry struct { - lock sync.Mutex - actions map[string]*action - actionspecs runtime.TypeScheme[ActionSpec, ActionSpecType] - resultspecs runtime.TypeScheme[ActionResult, ActionResultType] -} - -func NewActionTypeRegistry() ActionTypeRegistry { - return &actionRegistry{ - actions: map[string]*action{}, - actionspecs: runtime.NewTypeScheme[ActionSpec, ActionSpecType](), - resultspecs: runtime.NewTypeScheme[ActionResult, ActionResultType](), - } -} - -func (r *actionRegistry) Copy() ActionTypeRegistry { - r.lock.Lock() - defer r.lock.Unlock() - - actions := map[string]*action{} - - for k, v := range r.actions { - v.lock.Lock() - a := action{ - name: v.name, - shortdesc: v.shortdesc, - usage: v.usage, - attributes: v.attributes, - } - a.types = map[string]ActionType{} - for _, t := range v.types { - a.types[t.GetType()] = t - } - v.lock.Unlock() - actions[k] = &a - } - actionspecs := runtime.NewTypeScheme[ActionSpec, ActionSpecType]() - actionspecs.AddKnownTypes(r.actionspecs) - resultspecs := runtime.NewTypeScheme[ActionResult, ActionResultType]() - resultspecs.AddKnownTypes(r.resultspecs) - return &actionRegistry{ - actions: actions, - actionspecs: actionspecs, - resultspecs: resultspecs, - } -} - -func (r *actionRegistry) RegisterAction(name string, description string, usage string, attrs []string) error { - r.lock.Lock() - defer r.lock.Unlock() - - ai := r.actions[name] - if ai != nil { - return errors.ErrAlreadyExists(KIND_ACTION, name) - } - - ai = &action{ - name: name, - shortdesc: description, - usage: usage, - attributes: slices.Clone(attrs), - types: map[string]ActionType{}, - } - r.actions[name] = ai - return nil -} - -func (r *actionRegistry) RegisterActionType(typ ActionType) error { - r.lock.Lock() - defer r.lock.Unlock() - - k := typ.GetKind() - - ai := r.actions[k] - if ai == nil { - return errors.ErrNotFound(KIND_ACTION, k) - } - - if typ.SpecificationType().GetType() != typ.ResultType().GetType() { - return errors.ErrInvalidWrap(fmt.Errorf("version mismatch: request[%s]!=result[%s]", typ.SpecificationType().GetType(), typ.ResultType().GetType()), KIND_ACTIONTYPE, k) - } - if typ.SpecificationType().GetKind() != k { - return errors.ErrInvalidWrap(fmt.Errorf("kind mismatch in types: %s", typ.SpecificationType().GetType()), KIND_ACTIONTYPE, k) - } - ai.types[typ.GetVersion()] = typ - ai.lock.Lock() - defer ai.lock.Unlock() - r.actionspecs.Register(typ.SpecificationType()) - r.resultspecs.Register(typ.ResultType()) - return nil -} - -func (r *actionRegistry) GetAction(name string) Action { - r.lock.Lock() - defer r.lock.Unlock() - - return r.actions[name] -} - -func (r *actionRegistry) DecodeActionSpec(data []byte, unmarshaler runtime.Unmarshaler) (ActionSpec, error) { - return r.actionspecs.Decode(data, unmarshaler) -} - -func (r *actionRegistry) DecodeActionResult(data []byte, unmarshaler runtime.Unmarshaler) (ActionResult, error) { - return r.resultspecs.Decode(data, unmarshaler) -} - -func (r *actionRegistry) EncodeActionSpec(spec ActionSpec, marshaler runtime.Marshaler) ([]byte, error) { - return r.actionspecs.Encode(spec, marshaler) -} - -func (r *actionRegistry) EncodeActionResult(spec ActionResult, marshaler runtime.Marshaler) ([]byte, error) { - return r.resultspecs.Encode(spec, marshaler) -} - -func (r *actionRegistry) SupportedActionVersions(name string) []string { - r.lock.Lock() - defer r.lock.Unlock() - a := r.actions[name] - if a == nil { - return nil - } - return a.SupportedVersions() -} - -//////////////////////////////////////////////////////////////////////////////// - -var registry = NewActionTypeRegistry() - -func DefaultRegistry() ActionTypeRegistry { - return registry -} - -func RegisterAction(name string, description string, usage string, attrs []string) error { - return registry.RegisterAction(name, description, usage, attrs) -} - -func RegisterType(typ ActionType) error { - return registry.RegisterActionType(typ) -} - -func GetAction(name string) Action { - return registry.GetAction(name) -} - -func DecodeActionSpec(data []byte, unmarshaler runtime.Unmarshaler) (ActionSpec, error) { - return registry.DecodeActionSpec(data, unmarshaler) -} - -func EncodeActionSpec(spec ActionSpec, marshaler runtime.Marshaler) ([]byte, error) { - return registry.EncodeActionSpec(spec, marshaler) -} - -func DecodeActionResult(data []byte, unmarshaler runtime.Unmarshaler) (ActionResult, error) { - return registry.DecodeActionResult(data, unmarshaler) -} - -func EncodeActionResult(spec ActionResult, marshaler runtime.Marshaler) ([]byte, error) { - return registry.EncodeActionResult(spec, marshaler) -} - -func SupportedActionVersions(name string) []string { - return registry.SupportedActionVersions(name) -} diff --git a/pkg/contexts/datacontext/action/api/registry_test.go b/pkg/contexts/datacontext/action/api/registry_test.go deleted file mode 100644 index d5e22c04f..000000000 --- a/pkg/contexts/datacontext/action/api/registry_test.go +++ /dev/null @@ -1,100 +0,0 @@ -package api_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/action/api" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/action/handlers" - "github.com/open-component-model/ocm/pkg/runtime" -) - -type Handler struct { - spec api.ActionSpec - creds common.Properties -} - -func (h *Handler) Handle(spec api.ActionSpec, creds common.Properties) (api.ActionResult, error) { - h.spec = spec - h.creds = creds - r := NewActionResult(spec.(*ActionSpec).Field) - r.SetVersion("v2") - return r, nil -} - -var _ handlers.ActionHandler = &Handler{} - -var _ = Describe("action registry", func() { - var registry api.ActionTypeRegistry - - BeforeEach(func() { - registry = api.NewActionTypeRegistry() - RegisterAction(registry) - }) - - Context("plain", func() { - It("registers", func() { - Expect(registry.SupportedActionVersions(NAME)).To(Equal([]string{"v1", "v2"})) - }) - - It("encoding spec v1", func() { - spec := NewActionSpec("acme.com") - spec.SetVersion("v1") - data := Must(registry.EncodeActionSpec(spec, runtime.DefaultJSONEncoding)) - Expect(string(data)).To(Equal(`{"type":"testAction/v1","field":"acme.com"}`)) - d := Must(registry.DecodeActionSpec(data, runtime.DefaultJSONEncoding)) - Expect(d).To(Equal(spec)) - }) - It("encoding spec v2", func() { - spec := NewActionSpec("acme.com") - spec.SetVersion("v2") - data := Must(registry.EncodeActionSpec(spec, runtime.DefaultJSONEncoding)) - Expect(string(data)).To(Equal(`{"type":"testAction/v2","data":"acme.com"}`)) - d := Must(registry.DecodeActionSpec(data, runtime.DefaultJSONEncoding)) - Expect(d).To(Equal(spec)) - }) - - It("encoding result v1", func() { - spec := NewActionResult("successful") - spec.SetVersion("v1") - data := Must(registry.EncodeActionResult(spec, runtime.DefaultJSONEncoding)) - Expect(string(data)).To(Equal(`{"type":"testAction/v1","message":"successful"}`)) - d := Must(registry.DecodeActionResult(data, runtime.DefaultJSONEncoding)) - Expect(d).To(Equal(spec)) - }) - It("encoding result v2", func() { - spec := NewActionResult("successful") - spec.SetVersion("v2") - data := Must(registry.EncodeActionResult(spec, runtime.DefaultJSONEncoding)) - Expect(string(data)).To(Equal(`{"type":"testAction/v2","data":"successful"}`)) - d := Must(registry.DecodeActionResult(data, runtime.DefaultJSONEncoding)) - Expect(d).To(Equal(spec)) - }) - }) - - Context("data context", func() { - var ctx datacontext.Context - var handler *Handler - - BeforeEach(func() { - handler = &Handler{} - ctx = datacontext.NewWithActions(nil, handlers.NewRegistry(registry)) - Expect(ctx.GetActions().GetActionTypes()).To(BeIdenticalTo(registry)) - MustBeSuccessful(ctx.GetActions().Register(handler, handlers.ForAction(NAME), handlers.WithVersions("v2"), handlers.ForSelectors(".*\\.com"))) - }) - - It("", func() { - spec := NewActionSpec("acme.com") - creds := common.Properties{"alice": "bob"} - r := Must(ctx.GetActions().Execute(spec, creds)) - Expect(handler.spec).To(Equal(spec)) - Expect(handler.creds).To(Equal(creds)) - rs := NewActionResult("acme.com") - rs.SetVersion("v2") - Expect(r).To(Equal(rs)) - }) - }) -}) diff --git a/pkg/contexts/datacontext/action/api/utils.go b/pkg/contexts/datacontext/action/api/utils.go deleted file mode 100644 index 1652b9568..000000000 --- a/pkg/contexts/datacontext/action/api/utils.go +++ /dev/null @@ -1,38 +0,0 @@ -package api - -import ( - "github.com/open-component-model/ocm/pkg/runtime" -) - -type _Object = runtime.ObjectVersionedTypedObject - -type actionType struct { - _Object - spectype ActionSpecType - restype ActionResultType -} - -var _ ActionType = (*actionType)(nil) - -func NewActionType[IS ActionSpec, IR ActionResult](kind, version string) ActionType { - return NewActionTypeByConverter[IS, IS, IR, IR](kind, version, runtime.IdentityConverter[IS]{}, runtime.IdentityConverter[IR]{}) -} - -func NewActionTypeByConverter[IS ActionSpec, VS runtime.TypedObject, IR ActionResult, VR runtime.TypedObject](kind, version string, specconv runtime.Converter[IS, VS], resconv runtime.Converter[IR, VR]) ActionType { - name := runtime.TypeName(kind, version) - st := runtime.NewVersionedTypedObjectTypeByConverter[ActionSpec, IS, VS](name, specconv) - rt := runtime.NewVersionedTypedObjectTypeByConverter[ActionResult, IR, VR](name, resconv) - return &actionType{ - _Object: runtime.NewVersionedTypedObject(kind, version), - spectype: st, - restype: rt, - } -} - -func (a *actionType) SpecificationType() ActionSpecType { - return a.spectype -} - -func (a *actionType) ResultType() ActionResultType { - return a.restype -} diff --git a/pkg/contexts/datacontext/action/handlers/options.go b/pkg/contexts/datacontext/action/handlers/options.go deleted file mode 100644 index 3bcb9e82e..000000000 --- a/pkg/contexts/datacontext/action/handlers/options.go +++ /dev/null @@ -1,72 +0,0 @@ -package handlers - -import ( - "golang.org/x/exp/slices" - - "github.com/open-component-model/ocm/pkg/contexts/datacontext/action/api" -) - -type ( - Option = api.Option - Options = api.Options -) - -func NewOptions(opts ...Option) *Options { - return api.NewOptions(opts...) -} - -//////////////////////////////////////////////////////////////////////////////// - -type kind struct { - action string -} - -func ForAction(a string) Option { - return kind{a} -} - -func (o kind) ApplyActionHandlerOptionTo(opts *Options) { - opts.Action = o.action -} - -//////////////////////////////////////////////////////////////////////////////// - -type prio struct { - prio int -} - -func WithPrio(p int) Option { - return prio{p} -} - -func (o prio) ApplyActionHandlerOptionTo(opts *Options) { - opts.Priority = o.prio -} - -//////////////////////////////////////////////////////////////////////////////// - -type selectors struct { - selectors []api.Selector -} - -func ForSelectors(s ...api.Selector) Option { - return selectors{s} -} - -func (o selectors) ApplyActionHandlerOptionTo(opts *Options) { - opts.Selectors = append(opts.Selectors, o.selectors...) -} - -//////////////////////////////////////////////////////////////////////////////// - -type versions struct { - versions []string -} - -func WithVersions(vers ...string) Option { - return versions{slices.Clone(vers)} -} - -func (o versions) ApplyActionHandlerOptionTo(opts *Options) { - opts.Versions = append(opts.Versions, o.versions...) -} diff --git a/pkg/contexts/datacontext/action/handlers/registry.go b/pkg/contexts/datacontext/action/handlers/registry.go deleted file mode 100644 index ef8061a75..000000000 --- a/pkg/contexts/datacontext/action/handlers/registry.go +++ /dev/null @@ -1,233 +0,0 @@ -package handlers - -import ( - "fmt" - "regexp" - "sort" - "strings" - "sync" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/general" - "golang.org/x/exp/slices" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/action/api" - "github.com/open-component-model/ocm/pkg/registrations" - "github.com/open-component-model/ocm/pkg/semverutils" - "github.com/open-component-model/ocm/pkg/utils" -) - -var defaultHandlers = NewRegistry(api.DefaultRegistry()) - -func DefaultRegistry() Registry { - return defaultHandlers -} - -//////////////////////////////////////////////////////////////////////////////// - -type ActionsProvider interface { - GetActions() Registry -} - -type ActionHandler interface { - Handle(api.ActionSpec, common.Properties) (api.ActionResult, error) -} - -type ActionHandlerMatch struct { - Handler ActionHandler - Version string - Priority int -} - -type ( - Target = ActionsProvider - HandlerConfig = registrations.HandlerConfig - HandlerRegistrationHandler = registrations.HandlerRegistrationHandler[Target, Option] - HandlerRegistrationRegistry = registrations.HandlerRegistrationRegistry[Target, Option] -) - -func NewHandlerRegistrationRegistry(base ...HandlerRegistrationRegistry) HandlerRegistrationRegistry { - return registrations.NewHandlerRegistrationRegistry[Target, Option](base...) -} - -type Registry interface { - registrations.HandlerRegistrationRegistry[Target, Option] - - GetActionTypes() api.ActionTypeRegistry - - Register(h ActionHandler, opts ...Option) error - Execute(spec api.ActionSpec, creds common.Properties) (api.ActionResult, error) - Get(spec api.ActionSpec, possible ...string) []ActionHandlerMatch - AddTo(t Registry) -} - -type registration struct { - handler ActionHandler - versions []string - priority int -} - -var _ Option = (*registration)(nil) - -func (r *registration) ApplyActionHandlerOptionTo(opts *api.Options) { - opts.Priority = r.priority -} - -type registry struct { - registrations.HandlerRegistrationRegistry[Target, Option] - types api.ActionTypeRegistry - - lock sync.Mutex - base Registry - registrations map[string]map[api.Selector]*registration -} - -var _ Registry = (*registry)(nil) - -func NewRegistry(types api.ActionTypeRegistry, base ...Registry) Registry { - b := utils.Optional(base...) - if types == nil { - if b == nil { - types = api.DefaultRegistry() - } else { - types = b.GetActionTypes() - } - } - r := ®istry{ - base: b, - types: types, - registrations: map[string]map[api.Selector]*registration{}, - HandlerRegistrationRegistry: NewHandlerRegistrationRegistry(b), - } - return r -} - -func (r *registry) GetActionTypes() api.ActionTypeRegistry { - return r.types -} - -func (r *registry) AddTo(t Registry) { - r.lock.Lock() - defer r.lock.Unlock() - - if r.base != nil { - r.base.AddTo(t) - } - for k, sel := range r.registrations { - for s, reg := range sel { - t.Register(reg.handler, ForAction(k), WithVersions(reg.versions...), s, reg) - } - } -} - -func (r *registry) Register(h ActionHandler, olist ...Option) error { - r.lock.Lock() - defer r.lock.Unlock() - - opts := NewOptions(olist...) - if opts.Action == "" { - return fmt.Errorf("action kind required for action handler registration") - } - - kinds := r.registrations[opts.Action] - if kinds == nil { - kinds = map[api.Selector]*registration{} - r.registrations[opts.Action] = kinds - } - - versions := opts.Versions - if versions == nil { - versions = r.types.SupportedActionVersions(opts.Action) - } - versions = slices.Clone(versions) - if err := semverutils.SortVersions(versions); err != nil { - return errors.Wrapf(err, "invalid version set") - } - reg := ®istration{ - handler: h, - versions: versions, - priority: general.Conditional(opts.Priority >= 0, opts.Priority, 10), - } - - for _, s := range opts.Selectors { - kinds[s] = reg - } - return nil -} - -func (r *registry) Execute(spec api.ActionSpec, creds common.Properties) (api.ActionResult, error) { - result := r.Get(spec) - sort.SliceStable(result, func(a, b int) bool { - return result[a].Priority < result[b].Priority - }) - if len(result) > 0 { - spec.SetVersion(result[0].Version) - return result[0].Handler.Handle(spec, creds) - } - return nil, nil -} - -func (r *registry) Get(spec api.ActionSpec, possible ...string) []ActionHandlerMatch { - if len(possible) == 0 { - possible = r.GetActionTypes().SupportedActionVersions(spec.GetKind()) - } - - r.lock.Lock() - defer r.lock.Unlock() - - var result []ActionHandlerMatch - - if kinds := r.registrations[spec.GetKind()]; kinds != nil { - // first, check direct selctor match - if reg := kinds[spec.Selector()]; reg != nil { - if len(reg.versions) != 0 { - if v := MatchVersion(r.types.SupportedActionVersions(spec.GetKind()), reg.versions); v != "" { - result = append(result, ActionHandlerMatch{Handler: reg.handler, Version: v, Priority: reg.priority}) - } - } - } else { - // second, try registrations as regexp matcher - for sel, reg := range kinds { - s := string(sel) - e, err := regexp.Compile(s) - if err == nil { - t := strings.Trim(s, "^$") - if t == s { - e, err = regexp.Compile("^" + s + "$") - } - } - if err == nil { - if e.MatchString(string(spec.Selector())) { - if v := MatchVersion(r.types.SupportedActionVersions(spec.GetKind()), reg.versions); v != "" { - result = append(result, ActionHandlerMatch{Handler: reg.handler, Version: v, Priority: reg.priority}) - } - } - } - } - } - } - - if r.base != nil { - result = append(result, r.base.Get(spec, possible...)...) - } - return result -} - -func MatchVersion(possible []string, avail []string) string { - p := slices.Clone(possible) - a := slices.Clone(avail) - - semverutils.SortVersions(p) - semverutils.SortVersions(a) - f := "" - for _, v := range p { - for _, c := range a { - if v == c { - f = c - break - } - } - } - return f -} diff --git a/pkg/contexts/datacontext/action/type.go b/pkg/contexts/datacontext/action/type.go deleted file mode 100644 index efdcad6d3..000000000 --- a/pkg/contexts/datacontext/action/type.go +++ /dev/null @@ -1,20 +0,0 @@ -package action - -import ( - "github.com/open-component-model/ocm/pkg/contexts/datacontext/action/api" -) - -const KIND_ACTION = api.KIND_ACTION - -type ( - Selector = api.Selector - Action = api.Action - ActionSpec = api.ActionSpec - ActionResult = api.ActionResult - ActionType = api.ActionType - ActionTypeRegistry = api.ActionTypeRegistry -) - -func DefaultRegistry() ActionTypeRegistry { - return api.DefaultRegistry() -} diff --git a/pkg/contexts/datacontext/attrs/clicfgattr/attr.go b/pkg/contexts/datacontext/attrs/clicfgattr/attr.go deleted file mode 100644 index f2c05406e..000000000 --- a/pkg/contexts/datacontext/attrs/clicfgattr/attr.go +++ /dev/null @@ -1,70 +0,0 @@ -package clicfgattr - -import ( - "encoding/json" - "fmt" - - "sigs.k8s.io/yaml" - - "github.com/open-component-model/ocm/pkg/contexts/config" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - ATTR_KEY = "ocm.software/cliconfig" - ATTR_SHORT = "cliconfig" -) - -func init() { - datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) -} - -type AttributeType struct{} - -func (a AttributeType) Name() string { - return ATTR_KEY -} - -func (a AttributeType) Description() string { - return ` -*cliconfigr* Configuration Object passed to command line pluging. -` -} - -func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { - switch c := v.(type) { - case config.Config: - return json.Marshal(v) - case []byte: - if _, err := a.Decode(c, nil); err != nil { - return nil, err - } - return c, nil - default: - return nil, fmt.Errorf("config object required") - } -} - -func (a AttributeType) Decode(data []byte, _ runtime.Unmarshaler) (interface{}, error) { - var c config.GenericConfig - err := yaml.Unmarshal(data, &c) - if err != nil { - return nil, err - } - return &c, nil -} - -//////////////////////////////////////////////////////////////////////////////// - -func Get(ctx datacontext.Context) config.Config { - v := ctx.GetAttributes().GetAttribute(ATTR_KEY) - if v == nil { - return nil - } - return v.(config.Config) -} - -func Set(ctx datacontext.Context, c config.Config) { - ctx.GetAttributes().SetAttribute(ATTR_KEY, c) -} diff --git a/pkg/contexts/datacontext/attrs/init.go b/pkg/contexts/datacontext/attrs/init.go deleted file mode 100644 index 471f9f03f..000000000 --- a/pkg/contexts/datacontext/attrs/init.go +++ /dev/null @@ -1,8 +0,0 @@ -package attrs - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/logforward" - _ "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/rootcertsattr" - _ "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/tmpcache" - _ "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/vfsattr" -) diff --git a/pkg/contexts/datacontext/attrs/logforward/attr.go b/pkg/contexts/datacontext/attrs/logforward/attr.go deleted file mode 100644 index 3d2151940..000000000 --- a/pkg/contexts/datacontext/attrs/logforward/attr.go +++ /dev/null @@ -1,66 +0,0 @@ -package logforward - -import ( - "encoding/json" - "fmt" - - logcfg "github.com/mandelsoft/logging/config" - "sigs.k8s.io/yaml" - - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - ATTR_KEY = "github.com/mandelsoft/logforward" - ATTR_SHORT = "logfwd" -) - -func init() { - datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) -} - -type AttributeType struct{} - -func (a AttributeType) Name() string { - return ATTR_KEY -} - -func (a AttributeType) Description() string { - return ` -*logconfig* Logging config structure used for config forwarding -This attribute is used to specify a logging configuration intended -to be forwarded to other tools. -(For example: TOI passes this config to the executor) -` -} - -func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { - if _, ok := v.(*logcfg.Config); !ok { - return nil, fmt.Errorf("logging config required") - } - return json.Marshal(v) -} - -func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { - var c logcfg.Config - err := yaml.Unmarshal(data, &c) - if err != nil { - return nil, err - } - return &c, nil -} - -//////////////////////////////////////////////////////////////////////////////// - -func Get(ctx datacontext.Context) *logcfg.Config { - v := ctx.GetAttributes().GetAttribute(ATTR_KEY) - if v == nil { - return nil - } - return v.(*logcfg.Config) -} - -func Set(ctx datacontext.Context, c *logcfg.Config) { - ctx.GetAttributes().SetAttribute(ATTR_KEY, c) -} diff --git a/pkg/contexts/datacontext/attrs/rootcertsattr/attr.go b/pkg/contexts/datacontext/attrs/rootcertsattr/attr.go deleted file mode 100644 index bee68ece4..000000000 --- a/pkg/contexts/datacontext/attrs/rootcertsattr/attr.go +++ /dev/null @@ -1,148 +0,0 @@ -package rootcertsattr - -import ( - "crypto/x509" - "encoding/json" - "sync" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/signing/signutils" -) - -const ( - ATTR_KEY = "github.com/mandelsoft/ocm/rootcerts" - ATTR_SHORT = "rootcerts" -) - -type ( - Context = datacontext.AttributesContext - ContextProvider = datacontext.ContextProvider -) - -func init() { - datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) -} - -type AttributeType struct{} - -func (a AttributeType) Name() string { - return ATTR_KEY -} - -func (a AttributeType) Description() string { - return ` -*JSON* -General root certificate settings given as JSON document with the following -format: - -
-{
-  "rootCertificates"": [
-     {
-       "data": ""<base64>"
-     },
-     {
-       "path": ""<file path>"
-     }
-  ],
-
- -One of following data fields are possible: -- data: base64 encoded binary data -- stringdata: plain text data -- path: a file path to read the data from -` -} - -func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { - attr, ok := v.(*Attribute) - if !ok { - return nil, errors.ErrInvalid("certificate attribute") - } - cfg := New() - - attr.lock.Lock() - defer attr.lock.Unlock() - - for _, c := range attr.rootCertificates { - data := signutils.CertificateToPem(c) - cfg.AddRootCertificateData(data) - } - - return json.Marshal(cfg) -} - -func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { - var value Config - err := unmarshaller.Unmarshal(data, &value) - if err != nil { - return nil, err - } - - attr := &Attribute{} - err = value.ApplyToAttribute(attr) - if err != nil { - return nil, err - } - return attr, nil -} - -//////////////////////////////////////////////////////////////////////////////// - -type Attribute struct { - lock sync.Mutex - rootCertificates []*x509.Certificate -} - -func (a *Attribute) RegisterRootCertificates(cert signutils.GenericCertificateChain) error { - certs, err := signutils.GetCertificateChain(cert, false) - if err != nil { - return err - } - - a.lock.Lock() - defer a.lock.Unlock() - - a.rootCertificates = append(a.rootCertificates, certs...) - return nil -} - -func (a *Attribute) HasRootCertificates() bool { - a.lock.Lock() - defer a.lock.Unlock() - return len(a.rootCertificates) > 0 -} - -func (a *Attribute) GetRootCertPool(system bool) *x509.CertPool { - var pool *x509.CertPool - - if system { - pool, _ = x509.SystemCertPool() - } - if pool == nil { - pool = x509.NewCertPool() - } - - a.lock.Lock() - defer a.lock.Unlock() - - for _, c := range a.rootCertificates { - pool.AddCert(c) - } - return pool -} - -//////////////////////////////////////////////////////////////////////////////// - -func Get(ctx ContextProvider) *Attribute { - return ctx.AttributesContext().GetAttributes().GetOrCreateAttribute(ATTR_KEY, func(ctx datacontext.Context) interface{} { - return &Attribute{} - }).(*Attribute) -} - -func Set(ctx ContextProvider, attribute *Attribute) error { - return ctx.AttributesContext().GetAttributes().SetAttribute(ATTR_KEY, attribute) -} diff --git a/pkg/contexts/datacontext/attrs/rootcertsattr/attr_test.go b/pkg/contexts/datacontext/attrs/rootcertsattr/attr_test.go deleted file mode 100644 index ef1515303..000000000 --- a/pkg/contexts/datacontext/attrs/rootcertsattr/attr_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package rootcertsattr_test - -import ( - "encoding/json" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/config" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - me "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/rootcertsattr" -) - -const NAME = "test" - -var certdata = []byte(` ------BEGIN CERTIFICATE----- -MIIDBDCCAeygAwIBAgIQF+kRr0G+faDEAH5Y4P1J7DANBgkqhkiG9w0BAQsFADAc -MQwwCgYDVQQKEwNPQ00xDDAKBgNVBAMTA29jbTAeFw0yMzEyMjkxMDIyMzdaFw0y -NDEyMjgxMDIyMzdaMBwxDDAKBgNVBAoTA09DTTEMMAoGA1UEAxMDb2NtMIIBIjAN -BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvpTQIQFNy23ygef3pshdeNjT7TME -kPEuqrqcF3KIX1cX16pHMQeU+VzXAFRj3xCy3LAM8ZzLsdHSwZDsIsGdg0nAbGjz -+USez/9TGC58ktr/84Kh0gHDE28YSVhsnNSrBJcWaBlYZz4Iy89O2Xc4jbK34Cwg -Si0ES+Ru1lxLD6FSLYLe43wCIjWRJRrMFcua6nI0P4MCpcKmTkXG2/xz80QSobI3 -z/isqOT54FKHW8DZZVlQMOxh+loeLksfEq7EYVkQoUWEV6xyR24TEpMGfxERgDre -l7lmx8nIFzRMXkot+P19XWfUBgqctVEiDF4DlRE+SvCZsNCrg7nQuC2AZQIDAQAB -o0IwQDAOBgNVHQ8BAf8EBAMCAqQwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU -1iQqrWM/bCXMk+5c1bulfI5zlKcwDQYJKoZIhvcNAQELBQADggEBAAQO6lw6ePuX -E+NyhDYCulueMWHC7GRUKa1KpouFT2yM0BSQnP04VakTlwVO3w4w2KucSVVomHR3 -hTY9Ypx7iGLaqdXHmUZvx3uaTM5IXQKMMWL1LJsxAvuzucehgDlOnFBD91tKsr5o -VRvRU5ya0igBCnnGpFu7NuH3C9pgF01lrQ3EhUHuNeazxleaE3/uQWmAXfxFB4ci -gHMKSEk3HuYA1raDJFv4ihwO5pXHvlDhcW0C1oMG9lOCh8TXpVzzBDZiH1kWPWSs -gW9YBu7/p/22U4++X23RyaheGuysfRAMv9cTv+8T0J8NHaAmQz4/QHFXh+0/tQgU -EVQVGDF6KNU= ------END CERTIFICATE----- -`) - -var _ = Describe("attribute", func() { - var cfgctx config.Context - var ctx me.Context - - BeforeEach(func() { - cfgctx = config.New(datacontext.MODE_DEFAULTED) - ctx = cfgctx.AttributesContext() - }) - - It("marshal/unmarshal", func() { - cfg := me.New() - cfg.AddRootCertificateData(certdata) - - data, err := json.Marshal(cfg) - Expect(err).To(Succeed()) - - r := &me.Config{} - Expect(json.Unmarshal(data, r)).To(Succeed()) - Expect(r).To(Equal(cfg)) - }) - - It("applies root certificate", func() { - cfg := me.New() - cfg.AddRootCertificateData(certdata) - - Expect(cfgctx.ApplyConfig(cfg, "from test")).To(Succeed()) - Expect(me.Get(ctx).HasRootCertificates()).To(BeTrue()) - }) -}) diff --git a/pkg/contexts/datacontext/attrs/rootcertsattr/config.go b/pkg/contexts/datacontext/attrs/rootcertsattr/config.go deleted file mode 100644 index 4516793c5..000000000 --- a/pkg/contexts/datacontext/attrs/rootcertsattr/config.go +++ /dev/null @@ -1,108 +0,0 @@ -package rootcertsattr - -import ( - "sync" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/vfs/pkg/vfs" - - cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/signing/signutils" - "github.com/open-component-model/ocm/pkg/utils" -) - -const ( - ConfigType = "rootcerts" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX - ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" -) - -func init() { - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1, usage)) -} - -// Config describes a memory based repository interface. -type Config struct { - runtime.ObjectVersionedType `json:",inline"` - RootCertificates []cfgcpi.ContentSpec `json:"rootCertificates,omitempty"` -} - -// New creates a new memory ConfigSpec. -func New() *Config { - return &Config{ - ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), - } -} - -func (a *Config) GetType() string { - return ConfigType -} - -func (a *Config) AddRootCertificateFile(name string, fss ...vfs.FileSystem) { - a.RootCertificates = append(a.RootCertificates, cfgcpi.ContentSpec{Path: name, FileSystem: utils.Optional(fss...)}) -} - -func (a *Config) AddRootCertificateData(data []byte) { - a.RootCertificates = append(a.RootCertificates, cfgcpi.ContentSpec{Data: data}) -} - -func (a *Config) AddRootCertificate(chain signutils.GenericCertificateChain) error { - certs, err := signutils.GetCertificateChain(chain, false) - if err != nil { - return err - } - a.RootCertificates = append(a.RootCertificates, cfgcpi.ContentSpec{Data: signutils.CertificateChainToPem(certs), Parsed: certs}) - return nil -} - -func (a *Config) ApplyTo(ctx cfgcpi.Context, target interface{}) error { - if t, ok := target.(Context); ok { - if t.AttributesContext().IsAttributesContext() { // apply only to root context - return errors.Wrapf(a.ApplyToAttribute(Get(t)), "applying config to certattr failed") - } - } - return cfgcpi.ErrNoContext(ConfigType) -} - -func (a *Config) ApplyToAttribute(attr *Attribute) error { - for i, k := range a.RootCertificates { - err := attr.RegisterRootCertificates(k) - if err != nil { - return errors.Wrapf(err, "invalid certificate %d", i) - } - } - return nil -} - -const usage = ` -The config type ` + ConfigType + ` can be used to define -general root certificates. A certificate value might be given by one of the fields: -- path: path of file with key data -- data: base64 encoded binary data -- stringdata: data a string parsed by key handler - -
-    rootCertificates:
-      - path: <file path>
-
- -` - -type Appliers struct { - lock sync.Mutex - appliers []cfgcpi.ConfigApplier -} - -func (r *Appliers) Register(a ...cfgcpi.ConfigApplier) { - r.lock.Lock() - defer r.lock.Unlock() - - r.appliers = append(r.appliers, a...) -} - -var DefaultAppliers = &Appliers{} - -func RegisterApplier(a ...cfgcpi.ConfigApplier) { - DefaultAppliers.Register(a...) -} diff --git a/pkg/contexts/datacontext/attrs/tmpcache/attr.go b/pkg/contexts/datacontext/attrs/tmpcache/attr.go deleted file mode 100644 index bbe00a818..000000000 --- a/pkg/contexts/datacontext/attrs/tmpcache/attr.go +++ /dev/null @@ -1,104 +0,0 @@ -package tmpcache - -import ( - "fmt" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/vfsattr" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -const ( - ATTR_KEY = "github.com/mandelsoft/tempblobcache" - ATTR_SHORT = "blobcache" -) - -func init() { - datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) -} - -type AttributeType struct{} - -func (a AttributeType) Name() string { - return ATTR_KEY -} - -func (a AttributeType) Description() string { - return ` -*string* Foldername for temporary blob cache -The temporary blob cache is used to accessing large blobs from remote sytems. -The are temporarily stored in the filesystem, instead of the memory, to avoid -blowing up the memory consumption. -` -} - -func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { - if a, ok := v.(*Attribute); !ok { - return nil, fmt.Errorf("temppcache attribute") - } else { - return []byte(a.Path), nil - } -} - -func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { - var s string - err := runtime.DefaultYAMLEncoding.Unmarshal(data, &s) - if err != nil { - return nil, errors.Wrapf(err, "invalid attribute value for %s", ATTR_KEY) - } - return &Attribute{ - Path: s, - }, nil -} - -//////////////////////////////////////////////////////////////////////////////// - -type Attribute struct { - Path string - 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 { - return nil, err - } - return vfs.TempFile(a.Filesystem, a.Path, pat) -} - -//////////////////////////////////////////////////////////////////////////////// - -func Get(ctx datacontext.Context) *Attribute { - 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 { - a.Filesystem = fs - } - return a - } - return &Attribute{fs.FSTempDir(), fs} -} - -func Set(ctx datacontext.Context, a *Attribute) { - ctx.GetAttributes().SetAttribute(ATTR_KEY, a) -} diff --git a/pkg/contexts/datacontext/attrs/vfsattr/attr.go b/pkg/contexts/datacontext/attrs/vfsattr/attr.go deleted file mode 100644 index 4b93bb7c3..000000000 --- a/pkg/contexts/datacontext/attrs/vfsattr/attr.go +++ /dev/null @@ -1,62 +0,0 @@ -package vfsattr - -import ( - "fmt" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/vfs/pkg/osfs" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - ATTR_KEY = "github.com/mandelsoft/vfs" - ATTR_SHORT = "vfs" -) - -func init() { - datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) -} - -type AttributeType struct{} - -func (a AttributeType) Name() string { - return ATTR_KEY -} - -func (a AttributeType) Description() string { - return ` -*intern* (not via command line) -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.CachingFileSystem required") - } - return nil, nil -} - -func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { - return nil, errors.ErrNotSupported("decode attribute", ATTR_KEY) -} - -//////////////////////////////////////////////////////////////////////////////// - -var _osfs = osfs.New() - -func Get(ctx datacontext.Context) vfs.FileSystem { - v := ctx.GetAttributes().GetAttribute(ATTR_KEY) - if v == nil { - return _osfs - } - fs, _ := v.(vfs.FileSystem) - return fs -} - -func Set(ctx datacontext.Context, fs vfs.FileSystem) { - ctx.GetAttributes().SetAttribute(ATTR_KEY, fs) -} diff --git a/pkg/contexts/datacontext/builder.go b/pkg/contexts/datacontext/builder.go deleted file mode 100644 index bb6a7ae19..000000000 --- a/pkg/contexts/datacontext/builder.go +++ /dev/null @@ -1,63 +0,0 @@ -package datacontext - -import ( - "context" - - "github.com/open-component-model/ocm/pkg/contexts/datacontext/action/api" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/action/handlers" -) - -type Builder struct { - ctx context.Context - attributes Attributes - actions handlers.Registry -} - -func (b *Builder) getContext() context.Context { - if b.ctx == nil { - return context.Background() - } - return b.ctx -} - -func (b Builder) WithContext(ctx context.Context) Builder { - b.ctx = ctx - return b -} - -func (b Builder) WithAttributes(paranetAttr Attributes) Builder { - b.attributes = paranetAttr - return b -} - -func (b Builder) WithActionHandlers(hdlrs handlers.Registry) Builder { - b.actions = hdlrs - return b -} - -func (b Builder) Bound() (Context, context.Context) { - c := b.New() - return c, context.WithValue(b.getContext(), key, c) -} - -func (b Builder) New(m ...BuilderMode) Context { - mode := Mode(m...) - - if b.actions == nil { - switch mode { - case MODE_INITIAL: - b.actions = handlers.NewRegistry(api.NewActionTypeRegistry()) - case MODE_CONFIGURED: - b.actions = handlers.NewRegistry(api.DefaultRegistry().Copy()) - handlers.DefaultRegistry().AddTo(b.actions) - case MODE_EXTENDED: - b.actions = handlers.NewRegistry(api.DefaultRegistry(), handlers.DefaultRegistry()) - case MODE_DEFAULTED: - fallthrough - case MODE_SHARED: - b.actions = handlers.DefaultRegistry() - } - } - - return newWithActions(mode, nil, b.actions) -} diff --git a/pkg/contexts/datacontext/config/attrs/config_test.go b/pkg/contexts/datacontext/config/attrs/config_test.go deleted file mode 100644 index 4b4eb7a55..000000000 --- a/pkg/contexts/datacontext/config/attrs/config_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package attrs_test - -import ( - "fmt" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/config" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - local "github.com/open-component-model/ocm/pkg/contexts/datacontext/config/attrs" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ATTR_KEY = "test" - -func init() { - datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}) -} - -type AttributeType struct{} - -func (a AttributeType) Name() string { - return ATTR_KEY -} - -func (a AttributeType) Description() string { - return ` -A Test attribute. -` -} - -type Attribute struct { - Value string `json:"value"` -} - -func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { - if _, ok := v.(*Attribute); !ok { - return nil, fmt.Errorf("boolean required") - } - return marshaller.Marshal(v) -} - -func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { - var value Attribute - err := unmarshaller.Unmarshal(data, &value) - return &value, err -} - -//////////////////////////////////////////////////////////////////////////////// - -var _ = Describe("generic attributes", func() { - attribute := &Attribute{"TEST"} - var ctx config.Context - - BeforeEach(func() { - ctx = config.WithSharedAttributes(datacontext.New(nil)).New() - }) - - Context("applies", func() { - It("applies later attribute config", func() { - sub := credentials.WithConfigs(ctx).New() - spec := local.New() - Expect(spec.AddAttribute(ATTR_KEY, attribute)).To(Succeed()) - Expect(ctx.ApplyConfig(spec, "test")).To(Succeed()) - - Expect(sub.GetAttributes().GetAttribute(ATTR_KEY, nil)).To(Equal(attribute)) - }) - - It("applies earlier attribute config", func() { - spec := local.New() - Expect(spec.AddAttribute(ATTR_KEY, attribute)).To(Succeed()) - Expect(ctx.ApplyConfig(spec, "test")).To(Succeed()) - - sub := credentials.WithConfigs(ctx).New() - Expect(sub.GetAttributes().GetAttribute(ATTR_KEY, nil)).To(Equal(attribute)) - }) - }) -}) diff --git a/pkg/contexts/datacontext/config/attrs/type.go b/pkg/contexts/datacontext/config/attrs/type.go deleted file mode 100644 index 3e8f3eeb6..000000000 --- a/pkg/contexts/datacontext/config/attrs/type.go +++ /dev/null @@ -1,87 +0,0 @@ -package attrs - -import ( - "encoding/json" - - "github.com/mandelsoft/goutils/errors" - - cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - ConfigType = "attributes" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX - ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" -) - -func init() { - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1, usage)) -} - -// Config describes a memory based repository interface. -type Config struct { - runtime.ObjectVersionedType `json:",inline"` - // Attributes descibe a set of geeric attribute settings - Attributes map[string]json.RawMessage `json:"attributes,omitempty"` -} - -// New creates a new memory ConfigSpec. -func New() *Config { - return &Config{ - ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), - Attributes: map[string]json.RawMessage{}, - } -} - -func (a *Config) GetType() string { - return ConfigType -} - -func (a *Config) AddAttribute(attr string, value interface{}) error { - data, err := datacontext.DefaultAttributeScheme.Encode(attr, value, runtime.DefaultJSONEncoding) - if err == nil { - a.Attributes[attr] = data - } - return err -} - -func (a *Config) AddRawAttribute(attr string, data []byte) error { - _, err := datacontext.DefaultAttributeScheme.Decode(attr, data, runtime.DefaultJSONEncoding) - if err == nil { - a.Attributes[attr] = data - } - return err -} - -func (a *Config) ApplyTo(ctx cfgcpi.Context, target interface{}) error { - list := errors.ErrListf("applying config") - t, ok := target.(cfgcpi.Context) - if !ok { - return cfgcpi.ErrNoContext(ConfigType) - } - if a.Attributes == nil { - return nil - } - for a, e := range a.Attributes { - eff := datacontext.DefaultAttributeScheme.Shortcuts()[a] - if eff != "" { - a = eff - } - list.Add(errors.Wrapf(t.GetAttributes().SetEncodedAttribute(a, e, runtime.DefaultJSONEncoding), "attribute %q", a)) - } - return list.Result() -} - -const usage = ` -The config type ` + ConfigType + ` can be used to define a list -of arbitrary attribute specifications: - -
-    type: ` + ConfigType + `
-    attributes:
-       <name>: <yaml defining the attribute>
-       ...
-
-` diff --git a/pkg/contexts/datacontext/config/init.go b/pkg/contexts/datacontext/config/init.go deleted file mode 100644 index 3235db34f..000000000 --- a/pkg/contexts/datacontext/config/init.go +++ /dev/null @@ -1,6 +0,0 @@ -package config - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/datacontext/config/attrs" - _ "github.com/open-component-model/ocm/pkg/contexts/datacontext/config/logging" -) diff --git a/pkg/contexts/datacontext/config/logging/config_test.go b/pkg/contexts/datacontext/config/logging/config_test.go deleted file mode 100644 index d57a017c7..000000000 --- a/pkg/contexts/datacontext/config/logging/config_test.go +++ /dev/null @@ -1,240 +0,0 @@ -package logging_test - -import ( - "bytes" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/logging/testhelper" - - "github.com/mandelsoft/logging" - "github.com/tonglil/buflogr" - - "github.com/open-component-model/ocm/pkg/contexts/config" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - logcfg "github.com/open-component-model/ocm/pkg/contexts/datacontext/config/logging" - log "github.com/open-component-model/ocm/pkg/logging" -) - -var _ = Describe("logging configuration", func() { - var ctx datacontext.AttributesContext - var cfg config.Context - var buf bytes.Buffer - var orig logging.Context - - BeforeEach(func() { - orig = logging.DefaultContext().(*logging.ContextReference).Context - logging.SetDefaultContext(logging.NewDefault()) - log.SetContext(nil) - ctx = datacontext.New(nil) - cfg = config.WithSharedAttributes(ctx).New() - - buf.Reset() - def := buflogr.NewWithBuffer(&buf) - ctx.LoggingContext().SetBaseLogger(def) - }) - - AfterEach(func() { - // logging.SetDefaultContext(orig) - }) - _ = cfg - _ = orig - - It("just logs with defaults", func() { - LogTest(ctx) - - Expect(buf.String()).To(StringEqualTrimmedWithContext(` -V[3] info realm ocm -V[2] warn realm ocm -ERROR error realm ocm -`)) - }) - - It("just logs with settings from default context", func() { - logging.DefaultContext().AddRule(logging.NewConditionRule(logging.DebugLevel)) - LogTest(ctx) - - Expect(buf.String()).To(StringEqualTrimmedWithContext(` -V[4] debug realm ocm -V[3] info realm ocm -V[2] warn realm ocm -ERROR error realm ocm -`)) - }) - - It("just logs with settings from default context", func() { - logging.DefaultContext().AddRule(logging.NewConditionRule(logging.DebugLevel)) - LogTest(cfg) - - Expect(buf.String()).To(StringEqualTrimmedWithContext(` -V[4] debug realm ocm -V[3] info realm ocm -V[2] warn realm ocm -ERROR error realm ocm -`)) - }) - - It("just logs with settings for root context", func() { - spec := ` -type: ` + logcfg.ConfigTypeV1 + ` -contextType: ` + datacontext.CONTEXT_TYPE + ` -settings: - rules: - - rule: - level: Debug -` - _, err := cfg.ApplyData([]byte(spec), nil, "testconfig") - Expect(err).To(Succeed()) - LogTest(ctx) - LogTest(cfg, "cfg") - - Expect(buf.String()).To(StringEqualTrimmedWithContext(` -V[4] debug realm ocm -V[3] info realm ocm -V[2] warn realm ocm -ERROR error realm ocm -V[4] cfgdebug realm ocm -V[3] cfginfo realm ocm -V[2] cfgwarn realm ocm -ERROR cfgerror realm ocm -`)) - }) - - It("just logs with settings for root context by context provider", func() { - spec := ` -type: ` + logcfg.ConfigTypeV1 + ` -settings: - rules: - - rule: - level: Debug -` - _, err := cfg.ApplyData([]byte(spec), nil, "testconfig") - Expect(err).To(Succeed()) - - LogTest(ctx) - LogTest(cfg, "cfg") - - Expect(buf.String()).To(StringEqualTrimmedWithContext(` -V[4] debug realm ocm -V[3] info realm ocm -V[2] warn realm ocm -ERROR error realm ocm -V[4] cfgdebug realm ocm -V[3] cfginfo realm ocm -V[2] cfgwarn realm ocm -ERROR cfgerror realm ocm -`)) - }) - - It("just logs with settings for config context", func() { - spec := ` -type: ` + logcfg.ConfigTypeV1 + ` -contextType: ` + config.CONTEXT_TYPE + ` -settings: - rules: - - rule: - level: Debug -` - _, err := cfg.ApplyData([]byte(spec), nil, "testconfig") - Expect(err).To(Succeed()) - - LogTest(ctx) - LogTest(cfg, "cfg") - - Expect(buf.String()).To(StringEqualTrimmedWithContext(` -V[3] info realm ocm -V[2] warn realm ocm -ERROR error realm ocm -V[4] cfgdebug realm ocm -V[3] cfginfo realm ocm -V[2] cfgwarn realm ocm -ERROR cfgerror realm ocm -`)) - }) - - Context("default logging", func() { - spec1 := ` -type: ` + logcfg.ConfigTypeV1 + ` -contextType: default -settings: - rules: - - rule: - level: Debug -` - spec2 := ` -type: ` + logcfg.ConfigTypeV1 + ` -contextType: default -settings: - rules: - - rule: - level: Info -` - spec3 := ` -type: ` + logcfg.ConfigTypeV1 + ` -contextType: default -extraId: extra -settings: - rules: - - rule: - level: Debug -` - - var ctx logging.Context - - BeforeEach(func() { - log.SetContext(logging.NewDefault()) - buf.Reset() - def := buflogr.NewWithBuffer(&buf) - ctx = log.Context() - ctx.SetBaseLogger(def) - }) - - It("just logs with config", func() { - _, err := cfg.ApplyData([]byte(spec1), nil, "spec1") - Expect(err).To(Succeed()) - LogTest(ctx) - - Expect(buf.String()).To(StringEqualTrimmedWithContext(` -V[4] debug realm ocm -V[3] info realm ocm -V[2] warn realm ocm -ERROR error realm ocm -`)) - }) - - It("applies config once", func() { - _, err := cfg.ApplyData([]byte(spec1), nil, "spec1") - Expect(err).To(Succeed()) - _, err = cfg.ApplyData([]byte(spec2), nil, "spec2") - Expect(err).To(Succeed()) - _, err = cfg.ApplyData([]byte(spec1), nil, "spec1.2") - Expect(err).To(Succeed()) - - LogTest(ctx) - - Expect(buf.String()).To(StringEqualTrimmedWithContext(` -V[3] info realm ocm -V[2] warn realm ocm -ERROR error realm ocm -`)) - }) - It("re-applies config with extra id", func() { - _, err := cfg.ApplyData([]byte(spec1), nil, "spec1") - Expect(err).To(Succeed()) - _, err = cfg.ApplyData([]byte(spec2), nil, "spec2") - Expect(err).To(Succeed()) - _, err = cfg.ApplyData([]byte(spec3), nil, "spec3") - Expect(err).To(Succeed()) - - LogTest(ctx) - - Expect(buf.String()).To(StringEqualTrimmedWithContext(` -V[4] debug realm ocm -V[3] info realm ocm -V[2] warn realm ocm -ERROR error realm ocm -`)) - }) - }) -}) diff --git a/pkg/contexts/datacontext/config/logging/type.go b/pkg/contexts/datacontext/config/logging/type.go deleted file mode 100644 index f4f13f488..000000000 --- a/pkg/contexts/datacontext/config/logging/type.go +++ /dev/null @@ -1,139 +0,0 @@ -package logging - -import ( - "github.com/mandelsoft/logging" - logcfg "github.com/mandelsoft/logging/config" - - logdata "github.com/open-component-model/ocm/pkg/cobrautils/logopts/logging" - "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - local "github.com/open-component-model/ocm/pkg/logging" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - ConfigType = "logging" + cpi.OCM_CONFIG_TYPE_SUFFIX - ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" -) - -func init() { - cpi.RegisterConfigType(cpi.NewConfigType[*Config](ConfigType, usage)) - cpi.RegisterConfigType(cpi.NewConfigType[*Config](ConfigTypeV1, usage)) -} - -// Config describes logging settings for a dedicated context type. -type Config struct { - runtime.ObjectVersionedType `json:",inline"` - - // ContextType described the context type to apply the setting. - // If not set, the settings will be applied to any logging context provider, - // which are not derived contexts. - ContextType string `json:"contextType,omitempty"` - Settings logcfg.Config `json:"settings"` - - // ExtraId is used for the context type "default", "ocm" or "global" to be able - // to reapply the same config again using a different - // identity given by the settings hash + the id. - ExtraId string `json:"extraId,omitempty"` -} - -// New creates a logging config specification. -func New(ctxtype string, deflvl int) *Config { - return &Config{ - ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), - ContextType: ctxtype, - Settings: logcfg.Config{ - DefaultLevel: logging.LevelName(deflvl), - }, - } -} - -// NewWithConfig creates a logging config specification from a -// logging config object. -func NewWithConfig(ctxtype string, cfg *logcfg.Config) *Config { - return &Config{ - ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), - ContextType: ctxtype, - Settings: *cfg, - } -} - -func (c *Config) AddRuleSpec(r logcfg.Rule) error { - c.Settings.Rules = append(c.Settings.Rules, r) - return nil -} - -func (c *Config) GetType() string { - return ConfigType -} - -func (c *Config) ApplyTo(ctx cpi.Context, target interface{}) error { - // first: check for forward configuration - if lc, ok := target.(*logdata.LoggingConfiguration); ok { - switch c.ContextType { - case "default", "ocm", "global", "slave": - lc.LogConfig.DefaultLevel = c.Settings.DefaultLevel - lc.LogConfig.Rules = append(lc.LogConfig.Rules, c.Settings.Rules...) - } - return nil - } - - // second: main use case is to configure vrious logging contexts - switch c.ContextType { - // configure local static logging context. - // here, config is only applied once for every - // setting hash. - case "default": - return local.Configure(&c.Settings, c.ExtraId) - - case "ocm": - return local.ConfigureOCM(&c.Settings, c.ExtraId) - - case "global": - return local.ConfigureGlobal(&c.Settings, c.ExtraId) - - case "slave": - return nil - - // configure logging context providers. - case "": - if _, ok := target.(datacontext.AttributesContext); !ok { - return cpi.ErrNoContext("attribute context") - } - - // configure dedicated context types. - default: - dc, ok := target.(datacontext.Context) - if !ok { - return cpi.ErrNoContext("data context") - } - if dc.GetType() != c.ContextType { - return cpi.ErrNoContext(c.ContextType) - } - } - lctx, ok := target.(logging.ContextProvider) - if !ok { - return cpi.ErrNoContext("logging context") - } - return logcfg.DefaultRegistry().Configure(lctx.LoggingContext(), &c.Settings) -} - -const usage = ` -The config type ` + ConfigType + ` can be used to configure the logging -aspect of a dedicated context type: - -
-    type: ` + ConfigType + `
-    contextType: ` + datacontext.CONTEXT_TYPE + `
-    settings:
-      defaultLevel: Info
-      rules:
-        - ...
-
- -The context type ` + datacontext.CONTEXT_TYPE + ` is the root context of a -context hierarchy. - -If no context type is specified, the config will be applies to any target -acting as logging context provider, which is not a non-root context. -` diff --git a/pkg/contexts/datacontext/context.go b/pkg/contexts/datacontext/context.go deleted file mode 100644 index d12eedbb7..000000000 --- a/pkg/contexts/datacontext/context.go +++ /dev/null @@ -1,412 +0,0 @@ -package datacontext - -import ( - "context" - "fmt" - "io" - "reflect" - "sync" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/finalizer" - "github.com/mandelsoft/logging" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/action/handlers" - ocmlog "github.com/open-component-model/ocm/pkg/logging" - "github.com/open-component-model/ocm/pkg/refmgmt" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/runtimefinalizer" - "github.com/open-component-model/ocm/pkg/utils" -) - -const OCM_CONTEXT_SUFFIX = ".context" + common.OCM_TYPE_GROUP_SUFFIX - -// BuilderMode controls the handling of unset information in the -// builder configuration when calling the New method. -type BuilderMode int - -const ( - // MODE_SHARED uses the default contexts for unset nested context types. - MODE_SHARED BuilderMode = iota - // MODE_DEFAULTED uses dedicated context instances configured with the - // context type specific default registrations. - MODE_DEFAULTED - // MODE_EXTENDED uses dedicated context instances configured with - // context type registrations extending the default registrations. - MODE_EXTENDED - // MODE_CONFIGURED uses dedicated context instances configured with the - // context type registrations configured with the actual state of the - // default registrations. - MODE_CONFIGURED - // MODE_INITIAL uses completely new contexts for unset nested context types - // and initial registrations. - MODE_INITIAL -) - -const MULTI_REF = false - -func (m BuilderMode) String() string { - switch m { - case MODE_SHARED: - return "shared" - case MODE_DEFAULTED: - return "defaulted" - case MODE_EXTENDED: - return "extended" - case MODE_CONFIGURED: - return "configured" - case MODE_INITIAL: - return "initial" - default: - return fmt.Sprintf("(invalid %d)", m) - } -} - -func Mode(m ...BuilderMode) BuilderMode { - return utils.OptionalDefaulted(MODE_EXTENDED, m...) -} - -type ContextIdentity = runtimefinalizer.ObjectIdentity - -type ContextProvider interface { - // AttributesContext returns the shared attributes - AttributesContext() AttributesContext -} - -// Delegates is the interface for common -// Context features, which might be delegated -// to aggregated contexts. -type Delegates interface { - ocmlog.LogProvider - handlers.ActionsProvider -} - -type _delegates struct { - logging logging.Context - actions handlers.Registry -} - -func (d _delegates) LoggingContext() logging.Context { - return d.logging -} - -func (d _delegates) AttributionContext() logging.AttributionContext { - return d.logging.AttributionContext() -} - -func (d _delegates) Logger(messageContext ...logging.MessageContext) logging.Logger { - return d.logging.Logger(messageContext) -} - -func (d _delegates) GetActions() handlers.Registry { - return d.actions -} - -func ComposeDelegates(l logging.Context, a handlers.Registry) Delegates { - return _delegates{l, a} -} - -type ContextBinder interface { - // BindTo binds the context to a context.Context and makes it - // retrievable by a ForContext method - BindTo(ctx context.Context) context.Context -} - -// Context describes a common interface for a data context used for a dedicated -// purpose. -// Such has a type and always specific attribute store. -// Every Context can be bound to a context.Context. -type Context interface { - ContextBinder - ContextProvider - Delegates - - IsIdenticalTo(Context) bool - - // GetType returns the context type - GetType() string - GetId() ContextIdentity - - GetAttributes() Attributes - - Finalize() error - Finalizer() *finalizer.Finalizer -} - -type InternalContext interface { - Context - runtimefinalizer.RecorderProvider - GetKey() interface{} - GetAllocatable() refmgmt.Allocatable -} - -//////////////////////////////////////////////////////////////////////////////// - -// CONTEXT_TYPE is the global type for an attribute context. -const CONTEXT_TYPE = "attributes" + OCM_CONTEXT_SUFFIX - -type AttributesContext interface { - Context - - IsAttributesContext() bool - AttributesContext() AttributesContext - - BindTo(ctx context.Context) context.Context -} - -// AttributeFactory is used to atomicly create a new attribute for a context. -type AttributeFactory func(Context) interface{} - -type Attributes interface { - finalizer.Finalizable - - GetAttribute(name string, def ...interface{}) interface{} - SetAttribute(name string, value interface{}) error - SetEncodedAttribute(name string, data []byte, unmarshaller runtime.Unmarshaler) error - GetOrCreateAttribute(name string, creator AttributeFactory) interface{} -} - -// DefaultContext is the default context initialized by init functions. -var DefaultContext = NewWithActions(nil, handlers.DefaultRegistry()) - -// ForContext returns the Context to use for context.Context. -// This is either an explicit context or the default context. -func ForContext(ctx context.Context) AttributesContext { - c, _ := ForContextByKey(ctx, key, DefaultContext) - if c == nil { - return nil - } - return c.(AttributesContext) -} - -// WithContext create a new Context bound to a context.Context. -func WithContext(ctx context.Context, parentAttrs Attributes) (Context, context.Context) { - c := New(parentAttrs) - return c, c.BindTo(ctx) -} - -//////////////////////////////////////////////////////////////////////////////// - -type Updater interface { - Update() error -} - -type UpdateFunc func() error - -func (u UpdateFunc) Update() error { - return u() -} - -type delegates = Delegates - -//////////////////////////////////////////////////////////////////////////////// - -var key = reflect.TypeOf(_context{}) - -type _context struct { - *contextBase - updater Updater -} - -// gcWrapper is used as garbage collectable -// wrapper for a context implementation -// to establish a runtime finalizer. -type gcWrapper struct { - GCWrapper - *_context -} - -func newView(c *_context, ref ...bool) AttributesContext { - if utils.Optional(ref...) { - return FinalizedContext[gcWrapper](c) - } - return c -} - -func (w *gcWrapper) SetContext(c *_context) { - w._context = c -} - -var ( - _ Context = (*_context)(nil) - _ ViewCreator[AttributesContext] = (*_context)(nil) -) - -// New provides a root attribute context. -func New(parentAttrs ...Attributes) AttributesContext { - return NewWithActions(utils.Optional(parentAttrs...), handlers.NewRegistry(nil, handlers.DefaultRegistry())) -} - -func NewWithActions(parentAttrs Attributes, actions handlers.Registry) AttributesContext { - return newWithActions(MODE_DEFAULTED, parentAttrs, actions) -} - -func newWithActions(mode BuilderMode, parentAttrs Attributes, actions handlers.Registry) AttributesContext { - c := &_context{} - c.contextBase = newContextBase(c, CONTEXT_TYPE, key, parentAttrs, &c.updater, - ComposeDelegates(logging.NewWithBase(ocmlog.Context()), handlers.NewRegistry(nil, actions)), - ) - return SetupContext(mode, c.CreateView()) // see above -} - -func (c *_context) CreateView() AttributesContext { - return newView(c, true) -} - -func (c *_context) AttributesContext() AttributesContext { - if c.updater != nil { - c.updater.Update() - } - return newView(c) -} - -func (c *_context) IsAttributesContext() bool { - return true -} - -func (c *_context) Actions() handlers.Registry { - if c.updater != nil { - c.updater.Update() - } - return c.contextBase.GetActions() -} - -func (c *_context) LoggingContext() logging.Context { - if c.updater != nil { - c.updater.Update() - } - return c.contextBase.LoggingContext() -} - -func (c *_context) Logger(messageContext ...logging.MessageContext) logging.Logger { - if c.updater != nil { - c.updater.Update() - } - return c.contextBase.Logger(messageContext...) -} - -//////////////////////////////////////////////////////////////////////////////// - -var contextrange, attrsrange = runtimefinalizer.NumberRange{}, runtimefinalizer.NumberRange{} - -type _attributes struct { - sync.RWMutex - id uint64 - ctx Context - parent Attributes - updater *Updater - attributes map[string]interface{} -} - -var _ Attributes = &_attributes{} - -func NewAttributes(ctx Context, parent Attributes, updater *Updater) Attributes { - return newAttributes(ctx, parent, updater) -} - -func newAttributes(ctx Context, parent Attributes, updater *Updater) *_attributes { - return &_attributes{ - id: attrsrange.NextId(), - ctx: ctx, - parent: parent, - updater: updater, - attributes: map[string]interface{}{}, - } -} - -func (c *_attributes) Finalize() error { - list := errors.ErrListf("finalizing attributes") - for n, a := range c.attributes { - if f, ok := a.(finalizer.Finalizable); ok { - list.Addf(nil, f.Finalize(), "attribute %s", n) - } - } - return list.Result() -} - -func (c *_attributes) GetAttribute(name string, def ...interface{}) interface{} { - if *c.updater != nil { - (*c.updater).Update() - } - c.RLock() - defer c.RUnlock() - if a := c.attributes[name]; a != nil { - return a - } - if c.parent != nil { - if a := c.parent.GetAttribute(name); a != nil { - return a - } - } - return utils.Optional(def...) -} - -func (c *_attributes) SetEncodedAttribute(name string, data []byte, unmarshaller runtime.Unmarshaler) error { - s := DefaultAttributeScheme.Shortcuts()[name] - if s != "" { - name = s - } - v, err := DefaultAttributeScheme.Decode(name, data, unmarshaller) - if err != nil { - return err - } - c.SetAttribute(name, v) - return nil -} - -func (c *_attributes) setAttribute(name string, value interface{}) error { - c.Lock() - defer c.Unlock() - - _, err := DefaultAttributeScheme.Encode(name, value, nil) - if err != nil && !errors.IsErrUnknownKind(err, "attribute") { - return err - } - old := c.attributes[name] - if old != nil && old != value { - if c, ok := old.(io.Closer); ok { - c.Close() - } - } - value, err = DefaultAttributeScheme.Convert(name, value) - if err != nil && !errors.IsErrUnknownKind(err, "attribute") { - return err - } - c.attributes[name] = value - return nil -} - -func (c *_attributes) SetAttribute(name string, value interface{}) error { - err := c.setAttribute(name, value) - if err == nil { - if *c.updater != nil { - (*c.updater).Update() - } - } - return err -} - -func (c *_attributes) getOrCreateAttribute(name string, creator AttributeFactory) interface{} { - c.Lock() - defer c.Unlock() - if v := c.attributes[name]; v != nil { - return v - } - if c.parent != nil { - if v := c.parent.GetAttribute(name); v != nil { - return v - } - } - v := creator(c.ctx) - c.attributes[name] = v - return v -} - -func (c *_attributes) GetOrCreateAttribute(name string, creator AttributeFactory) interface{} { - r := c.getOrCreateAttribute(name, creator) - if *c.updater != nil { - (*c.updater).Update() - } - return r -} diff --git a/pkg/contexts/datacontext/context_test.go b/pkg/contexts/datacontext/context_test.go deleted file mode 100644 index 11e97cf1b..000000000 --- a/pkg/contexts/datacontext/context_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package datacontext_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - me "github.com/open-component-model/ocm/pkg/contexts/datacontext" -) - -var _ = Describe("area test", func() { - It("can be garbage collected", func() { - // ocmlog.Context().AddRule(logging.NewConditionRule(logging.DebugLevel, me.Realm)) - - ctx := me.New() - Expect(ctx.IsIdenticalTo(ctx)).To(BeTrue()) - - ctx2 := ctx.AttributesContext() - Expect(ctx.IsIdenticalTo(ctx2)).To(BeTrue()) - - ctx3 := me.New() - Expect(ctx.IsIdenticalTo(ctx3)).To(BeFalse()) - }) -}) diff --git a/pkg/contexts/datacontext/gc_test.go b/pkg/contexts/datacontext/gc_test.go deleted file mode 100644 index 23022c9ff..000000000 --- a/pkg/contexts/datacontext/gc_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package datacontext_test - -import ( - "runtime" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/mandelsoft/goutils/general" - - me "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/runtimefinalizer" -) - -var _ = Describe("area test", func() { - It("can be garbage collected", func() { - ctx := me.New() - r := runtimefinalizer.GetRuntimeFinalizationRecorder(ctx) - id := ctx.GetId() - Expect(me.GetContextRefCount(ctx)).To(Equal(1)) - ctx = nil - runtime.GC() - time.Sleep(time.Second) - Expect(r.Get()).To(ConsistOf(id)) - }) - - It("provides second reference", func() { - // ocmlog.Context().AddRule(logging.NewConditionRule(logging.DebugLevel, me.Realm)) - multiRefs := general.Conditional(me.MULTI_REF, 2, 1) - - ctx := me.New() - Expect(me.GetContextRefCount(ctx)).To(Equal(1)) - - actx := ctx.AttributesContext() - Expect(me.GetContextRefCount(ctx)).To(Equal(multiRefs)) - - r := runtimefinalizer.GetRuntimeFinalizationRecorder(ctx) - Expect(r).NotTo(BeNil()) - - runtime.GC() - time.Sleep(time.Second) - ctx.GetType() - Expect(r.Get()).To(BeNil()) - - actx.GetType() - actx = nil - runtime.GC() - time.Sleep(time.Second) - ctx.GetType() - Expect(r.Get()).To(BeNil()) - Expect(me.GetContextRefCount(ctx)).To(Equal(1)) - - ctx = nil - for i := 0; i < 100; i++ { - runtime.GC() - time.Sleep(time.Millisecond) - } - - Expect(r.Get()).To(ContainElement(ContainSubstring(me.CONTEXT_TYPE))) - }) - - It("creates views", func() { - ctx := me.New() - r := runtimefinalizer.GetRuntimeFinalizationRecorder(ctx) - - Expect(me.GetContextRefCount(ctx)).To(Equal(1)) - Expect(me.IsPersistentContextRef(ctx)).To(BeTrue()) - - view := me.PersistentContextRef(ctx) - Expect(me.GetContextRefCount(view)).To(Equal(1)) // reuse persistent ref - Expect(me.IsPersistentContextRef(view)).To(BeTrue()) - - non := view.AttributesContext() - Expect(me.IsPersistentContextRef(non)).To(BeFalse()) - - view2 := me.PersistentContextRef(non) - Expect(me.GetContextRefCount(view2)).To(Equal(2)) // create new view - Expect(me.IsPersistentContextRef(view2)).To(BeTrue()) - - Expect(ctx.IsIdenticalTo(view)).To(BeTrue()) - Expect(ctx.IsIdenticalTo(view2)).To(BeTrue()) - - ctx = nil - view = nil - view2 = nil - - runtime.GC() - time.Sleep(time.Second) - Expect(len(r.Get())).To(Equal(1)) // ref non is not persistent - }) -}) diff --git a/pkg/contexts/datacontext/logging.go b/pkg/contexts/datacontext/logging.go deleted file mode 100644 index 4fa21e3ff..000000000 --- a/pkg/contexts/datacontext/logging.go +++ /dev/null @@ -1,13 +0,0 @@ -package datacontext - -import ( - ocmlog "github.com/open-component-model/ocm/pkg/logging" -) - -var Realm = ocmlog.DefineSubRealm("context lifecycle", "context") - -var Logger = ocmlog.DynamicLogger(Realm) - -func Debug(c Context, msg string, keypairs ...interface{}) { - c.LoggingContext().Logger(Realm).Debug(msg, append(keypairs, "id", c.GetId())...) -} diff --git a/pkg/contexts/datacontext/session.go b/pkg/contexts/datacontext/session.go deleted file mode 100644 index a56434cc1..000000000 --- a/pkg/contexts/datacontext/session.go +++ /dev/null @@ -1,149 +0,0 @@ -package datacontext - -import ( - "io" - "sync" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/common/accessio" -) - -// Session is a context keeping track of objects requiring a close -// after final use. When closing a session all subsequent objects -// will be closed in the opposite order they are added. -// Added closers may be closed prio to the session without causing -// errors. -type Session interface { - // Closer adds a closer returned by a function call providing a closer and an error - // to the session if not error is returned. The results of the call are forwarded to - // the own result. Unfortunately, Go does not support type parameters for methods, - // therefore only an io.Closer can be returned a function result. - Closer(closer io.Closer, extra ...interface{}) (io.Closer, error) - GetOrCreate(key interface{}, creator func(SessionBase) Session) Session - AddCloser(closer io.Closer, callbacks ...accessio.CloserCallback) io.Closer - Close() error - IsClosed() bool -} - -type SessionBase interface { - Lock() - Unlock() - RLock() - RUnlock() - - Session() Session - IsClosed() bool - AddCloser(closer io.Closer, callbacks ...accessio.CloserCallback) io.Closer -} - -type ObjectKey struct { - Object interface{} - Name string -} - -type session struct { - base sessionBase -} - -type sessionBase struct { - sync.RWMutex - session Session - closed bool - closer []io.Closer - sessions map[interface{}]Session -} - -func NewSession() Session { - s := &session{ - sessionBase{ - sessions: map[interface{}]Session{}, - }, - } - s.base.session = s - return s -} - -func GetOrCreateSubSession(s Session, key interface{}, creator func(SessionBase) Session) Session { - if s == nil { - s = NewSession() - } - return s.GetOrCreate(key, creator) -} - -func (s *session) IsClosed() bool { - s.base.RLock() - defer s.base.RUnlock() - return s.base.closed -} - -func (s *session) Close() error { - s.base.Lock() - defer s.base.Unlock() - return s.base.Close() -} - -func (s *session) Closer(closer io.Closer, extra ...interface{}) (io.Closer, error) { - for _, e := range extra { - if err, ok := e.(error); ok && err != nil { - return nil, err - } - } - if closer == nil { - return nil, nil - } - s.base.Lock() - defer s.base.Unlock() - s.base.AddCloser(closer) - - return closer, nil -} - -func (s *session) AddCloser(closer io.Closer, callbacks ...accessio.CloserCallback) io.Closer { - if closer == nil { - return nil - } - s.base.Lock() - defer s.base.Unlock() - return s.base.AddCloser(closer, callbacks...) -} - -func (s *session) GetOrCreate(key interface{}, creator func(SessionBase) Session) Session { - s.base.Lock() - defer s.base.Unlock() - return s.base.GetOrCreate(key, creator) -} - -func (s *sessionBase) Session() Session { - return s.session -} - -func (s *sessionBase) IsClosed() bool { - return s.closed -} - -func (s *sessionBase) Close() error { - if s.closed { - return nil - } - s.closed = true - list := errors.ErrListf("closing session") - for i := len(s.closer) - 1; i >= 0; i-- { - list.Add(s.closer[i].Close()) - } - return list.Result() -} - -func (s *sessionBase) AddCloser(closer io.Closer, callbacks ...accessio.CloserCallback) io.Closer { - s.closer = append(s.closer, accessio.OnceCloser(closer, callbacks...)) - return closer -} - -func (s *sessionBase) GetOrCreate(key interface{}, creator func(SessionBase) Session) Session { - cur := s.sessions[key] - if cur == nil { - cur = creator(s) - s.sessions[key] = cur - } - return cur -} diff --git a/pkg/contexts/oci/action_test.go b/pkg/contexts/oci/action_test.go deleted file mode 100644 index 4b1c6af4c..000000000 --- a/pkg/contexts/oci/action_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package oci_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/oci" - oci_repository_prepare "github.com/open-component-model/ocm/pkg/contexts/oci/actions/oci-repository-prepare" -) - -var _ = Describe("action registration", func() { - It("registers oci prepare", func() { - a := oci.DefaultContext().GetActions().GetActionTypes().GetAction(oci_repository_prepare.Type) - Expect(a).NotTo(BeNil()) - v := a.GetVersion("v1") - Expect(v).NotTo(BeNil()) - }) -}) diff --git a/pkg/contexts/oci/actions/init.go b/pkg/contexts/oci/actions/init.go deleted file mode 100644 index 95a51189f..000000000 --- a/pkg/contexts/oci/actions/init.go +++ /dev/null @@ -1,5 +0,0 @@ -package actions - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/oci/actions/oci-repository-prepare" -) diff --git a/pkg/contexts/oci/actions/oci-repository-prepare/exec.go b/pkg/contexts/oci/actions/oci-repository-prepare/exec.go deleted file mode 100644 index 450abfaad..000000000 --- a/pkg/contexts/oci/actions/oci-repository-prepare/exec.go +++ /dev/null @@ -1,12 +0,0 @@ -package oci_repository_prepare - -import ( - "github.com/mandelsoft/goutils/generics" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/action/handlers" -) - -func Execute(hdlrs handlers.Registry, host, repo string, creds common.Properties) (*ActionResult, error) { - return generics.CastR[*ActionResult](hdlrs.Execute(Spec(host, repo), creds)) -} diff --git a/pkg/contexts/oci/actions/oci-repository-prepare/type.go b/pkg/contexts/oci/actions/oci-repository-prepare/type.go deleted file mode 100644 index 6637b9cbc..000000000 --- a/pkg/contexts/oci/actions/oci-repository-prepare/type.go +++ /dev/null @@ -1,81 +0,0 @@ -package oci_repository_prepare - -import ( - "path" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/action/api" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -const Type = "oci.repository.prepare" - -func init() { - api.RegisterAction(Type, "Prepare the usage of a repository in an OCI registry.", usage, - []string{identity.ID_HOSTNAME, identity.ID_PORT, identity.ID_PATHPREFIX}) - - api.RegisterType(api.NewActionType[*ActionSpecV1, *ActionResultV1](Type, "v1")) -} - -var usage = ` -The hostname of the target repository is used as selector. The action should -assure, that the requested repository is available on the target OCI registry. - -Spec version v1 uses the following specification fields: -- hostname *string*: The hostname of the OCI registry. -- repository *string*: The OCI repository name. -` - -//////////////////////////////////////////////////////////////////////////////// -// internal version - -type ActionSpec = ActionSpecV1 - -type ActionResult = ActionResultV1 - -func Spec(host string, repo string) *ActionSpec { - return &ActionSpec{ - ObjectVersionedType: runtime.ObjectVersionedType{runtime.TypeName(Type, "v1")}, - Hostname: host, - Repository: repo, - } -} - -func Result(msg string) *ActionResult { - return &ActionResult{ - CommonResult: api.CommonResult{ - ObjectVersionedType: runtime.ObjectVersionedType{runtime.TypeName(Type, "v1")}, - Message: msg, - }, - } -} - -//////////////////////////////////////////////////////////////////////////////// -// serialization formats - -type ActionSpecV1 struct { - runtime.ObjectVersionedType - Hostname string `json:"hostname"` - Repository string `json:"repository"` -} - -func (s *ActionSpecV1) Selector() api.Selector { - return api.Selector(s.Hostname) -} - -func (s *ActionSpecV1) GetConsumerAttributes() common.Properties { - host, port, base := utils.SplitLocator(s.Hostname) - return common.Properties{ - cpi.ID_TYPE: identity.CONSUMER_TYPE, - identity.ID_HOSTNAME: host, - identity.ID_PATHPREFIX: path.Join(base, s.Repository), - identity.ID_PORT: port, - } -} - -type ActionResultV1 struct { - api.CommonResult `json:",inline"` -} diff --git a/pkg/contexts/oci/art_test.go b/pkg/contexts/oci/art_test.go deleted file mode 100644 index 1e722090b..000000000 --- a/pkg/contexts/oci/art_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package oci_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/contexts/oci" -) - -func CheckArt(ref string, exp *oci.ArtSpec) { - spec, err := oci.ParseArt(ref) - if exp == nil { - Expect(err).To(HaveOccurred()) - } else { - Expect(err).To(Succeed()) - Expect(spec).To(Equal(*exp)) - } -} - -var _ = Describe("art parsing", func() { - digest := digest.Digest("sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a") - tag := "v1" - - It("succeeds", func() { - CheckArt("ubuntu", &oci.ArtSpec{Repository: "ubuntu"}) - CheckArt("ubuntu/test", &oci.ArtSpec{Repository: "ubuntu/test"}) - CheckArt("ubuntu/test@"+digest.String(), &oci.ArtSpec{Repository: "ubuntu/test", Digest: &digest}) - CheckArt("ubuntu/test:"+tag, &oci.ArtSpec{Repository: "ubuntu/test", Tag: &tag}) - CheckArt("ubuntu/test:"+tag+"@"+digest.String(), &oci.ArtSpec{Repository: "ubuntu/test", Digest: &digest, Tag: &tag}) - }) - - It("fails", func() { - CheckArt("ubu@ntu", nil) - CheckArt("ubu@sha256:123", nil) - }) -}) diff --git a/pkg/contexts/oci/artdesc/artifact.go b/pkg/contexts/oci/artdesc/artifact.go deleted file mode 100644 index ad66c1253..000000000 --- a/pkg/contexts/oci/artdesc/artifact.go +++ /dev/null @@ -1,298 +0,0 @@ -package artdesc - -import ( - "encoding/json" - out "fmt" - - "github.com/containerd/containerd/images" - "github.com/mandelsoft/goutils/errors" - "github.com/opencontainers/go-digest" - ociv1 "github.com/opencontainers/image-spec/specs-go/v1" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc/helper" -) - -const SchemeVersion = helper.SchemeVersion - -const ( - MediaTypeImageManifest = ociv1.MediaTypeImageManifest - MediaTypeImageIndex = ociv1.MediaTypeImageIndex - MediaTypeImageLayer = ociv1.MediaTypeImageLayer - MediaTypeImageLayerGzip = ociv1.MediaTypeImageLayerGzip - - MediaTypeDockerSchema2Manifest = images.MediaTypeDockerSchema2Manifest - MediaTypeDockerSchema2ManifestList = images.MediaTypeDockerSchema2ManifestList - - MediaTypeImageConfig = ociv1.MediaTypeImageConfig -) - -var legacy = false - -type ( - Descriptor = ociv1.Descriptor - Platform = ociv1.Platform -) - -type ArtifactDescriptor interface { - IsManifest() bool - IsIndex() bool - IsValid() bool - - Digest() digest.Digest - Blob() (blobaccess.BlobAccess, error) - Artifact() *Artifact - Manifest() (*Manifest, error) - Index() (*Index, error) -} - -type BlobDescriptorSource interface { - GetBlobDescriptor(digest.Digest) *Descriptor - MimeType() string - IsValid() bool -} - -// Artifact is the unified representation of an OCI artifact -// according to https://github.com/opencontainers/image-spec/blob/main/manifest.md -// It is either an image manifest or an image index manifest (fat image). -type Artifact struct { - manifest *Manifest - index *Index -} - -var ( - _ ArtifactDescriptor = (*Artifact)(nil) - _ BlobDescriptorSource = (*Artifact)(nil) - _ json.Marshaler = (*Artifact)(nil) - _ json.Unmarshaler = (*Artifact)(nil) -) - -func New() *Artifact { - return &Artifact{} -} - -func NewManifestArtifact() *Artifact { - a := New() - a.SetManifest(NewManifest()) - return a -} - -func NewIndexArtifact() *Artifact { - a := New() - a.SetIndex(NewIndex()) - return a -} - -func (d *Artifact) Digest() digest.Digest { - var blob blobaccess.BlobAccess - if d.manifest != nil { - blob, _ = d.manifest.Blob() - } - if d.index != nil { - blob, _ = d.index.Blob() - } - if blob != nil { - return blob.Digest() - } - return "" -} - -func (d *Artifact) Blob() (blobaccess.BlobAccess, error) { - if d.manifest != nil { - return d.manifest.Blob() - } - if d.index != nil { - return d.index.Blob() - } - return nil, errors.ErrInvalid("oci artifact") -} - -func (d *Artifact) Artifact() *Artifact { - return d -} - -func (d *Artifact) MimeType() string { - if d.IsIndex() { - return d.index.MimeType() - } - if d.IsManifest() { - return d.manifest.MimeType() - } - return "" -} - -func (d *Artifact) SetManifest(m *Manifest) error { - if d.IsIndex() || d.IsManifest() { - return errors.Newf("artifact descriptor already instantiated") - } - d.manifest = m - return nil -} - -func (d *Artifact) SetIndex(i *Index) error { - if d.IsIndex() || d.IsManifest() { - return errors.Newf("artifact descriptor already instantiated") - } - d.index = i - return nil -} - -func (d *Artifact) IsValid() bool { - return d.manifest != nil || d.index != nil -} - -func (d *Artifact) IsManifest() bool { - return d.manifest != nil -} - -func (d *Artifact) IsIndex() bool { - return d.index != nil -} - -func (d *Artifact) Index() (*Index, error) { - if d.index != nil { - return d.index, nil - } - return nil, errors.ErrInvalid() -} - -func (d *Artifact) Manifest() (*Manifest, error) { - if d.manifest != nil { - return d.manifest, nil - } - return nil, errors.ErrInvalid() -} - -func (d *Artifact) SetAnnotation(name, value string) error { - return d.modifyAnnotation(func(annos *map[string]string) { - if *annos == nil { - *annos = map[string]string{} - } - (*annos)[name] = value - }) -} - -func (d *Artifact) GetAnnotation(name string) string { - var annos map[string]string - switch { - case d.manifest != nil: - annos = d.manifest.Annotations - case d.index != nil: - annos = d.index.Annotations - default: - return "" - } - if len(annos) == 0 { - return "" - } - return annos[name] -} - -func (d *Artifact) DeleteAnnotation(name string) error { - return d.modifyAnnotation(func(annos *map[string]string) { - if *annos == nil { - return - } - delete(*annos, name) - if len(*annos) == 0 { - *annos = nil - } - }) -} - -func (d *Artifact) modifyAnnotation(mod func(annos *map[string]string)) error { - var annos map[string]string - - switch { - case d.manifest != nil: - annos = d.manifest.Annotations - case d.index != nil: - annos = d.index.Annotations - default: - return errors.Newf("void artifact access") - } - mod(&annos) - if d.manifest != nil { - d.manifest.Annotations = annos - } else { - d.index.Annotations = annos - } - return nil -} - -func (d *Artifact) ToBlobAccess() (blobaccess.BlobAccess, error) { - if d.IsManifest() { - return d.manifest.Blob() - } - if d.IsIndex() { - return d.index.Blob() - } - return nil, errors.ErrInvalid("artifact descriptor") -} - -func (d *Artifact) GetBlobDescriptor(digest digest.Digest) *Descriptor { - if d.IsManifest() { - m, err := d.Manifest() - if err != nil { - out.Printf("manifest was empty for artifact digest %s", digest) - - return nil - } - return m.GetBlobDescriptor(digest) - } - if d.IsIndex() { - i, _ := d.Index() - return i.GetBlobDescriptor(digest) - } - return nil -} - -func (d Artifact) MarshalJSON() ([]byte, error) { - if d.manifest != nil { - d.manifest.MediaType = ArtifactMimeType(d.manifest.MediaType, ociv1.MediaTypeImageManifest, legacy) - return json.Marshal(d.manifest) - } - if d.index != nil { - d.index.MediaType = ArtifactMimeType(d.index.MediaType, ociv1.MediaTypeImageIndex, legacy) - return json.Marshal(d.index) - } - return []byte("null"), nil -} - -func (d *Artifact) UnmarshalJSON(data []byte) error { - if string(data) == "null" { - return nil - } - var m helper.GenericDescriptor - - err := json.Unmarshal(data, &m) - if err != nil { - return err - } - - err = m.Validate() - if err != nil { - return err - } - if m.IsManifest() { - d.manifest = (*Manifest)(m.AsManifest()) - d.index = nil - } else { - d.index = (*Index)(m.AsIndex()) - d.manifest = nil - } - return nil -} - -func Decode(data []byte) (*Artifact, error) { - var d Artifact - - if err := json.Unmarshal(data, &d); err != nil { - return nil, err - } - return &d, nil -} - -func Encode(d *Artifact) ([]byte, error) { - return json.Marshal(d) -} diff --git a/pkg/contexts/oci/artdesc/config.go b/pkg/contexts/oci/artdesc/config.go deleted file mode 100644 index b0e990ee0..000000000 --- a/pkg/contexts/oci/artdesc/config.go +++ /dev/null @@ -1,25 +0,0 @@ -package artdesc - -import ( - "encoding/json" - - ociv1 "github.com/opencontainers/image-spec/specs-go/v1" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" -) - -type ImageConfig = ociv1.Image - -func ParseImageConfig(blob blobaccess.BlobAccess) (*ImageConfig, error) { - var cfg ImageConfig - - data, err := blob.Get() - if err != nil { - return nil, err - } - err = json.Unmarshal(data, &cfg) - if err != nil { - return nil, err - } - return &cfg, nil -} diff --git a/pkg/contexts/oci/artdesc/index.go b/pkg/contexts/oci/artdesc/index.go deleted file mode 100644 index 05b209ae0..000000000 --- a/pkg/contexts/oci/artdesc/index.go +++ /dev/null @@ -1,117 +0,0 @@ -package artdesc - -import ( - "encoding/json" - - "github.com/mandelsoft/goutils/errors" - "github.com/opencontainers/go-digest" - "github.com/opencontainers/image-spec/specs-go" - ociv1 "github.com/opencontainers/image-spec/specs-go/v1" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" -) - -type Index ociv1.Index - -var _ BlobDescriptorSource = (*Index)(nil) - -func NewIndex() *Index { - return &Index{ - Versioned: specs.Versioned{SchemeVersion}, - MediaType: MediaTypeImageIndex, - Manifests: nil, - Annotations: nil, - } -} - -var _ ArtifactDescriptor = (*Index)(nil) - -func (i *Index) IsManifest() bool { - return false -} - -func (i *Index) IsIndex() bool { - return true -} - -func (i *Index) Digest() digest.Digest { - blob, _ := i.Blob() - if blob != nil { - return blob.Digest() - } - return "" -} - -func (i *Index) Artifact() *Artifact { - return &Artifact{index: i} -} - -func (i *Index) Manifest() (*Manifest, error) { - return nil, errors.ErrInvalid() -} - -func (i *Index) Index() (*Index, error) { - return i, nil -} - -func (i *Index) IsValid() bool { - return true -} - -func (i *Index) GetBlobDescriptor(digest digest.Digest) *Descriptor { - for _, m := range i.Manifests { - if m.Digest == digest { - return &m - } - } - return nil -} - -func (i *Index) MimeType() string { - return ArtifactMimeType(i.MediaType, MediaTypeImageIndex, legacy) -} - -func (i *Index) SetAnnotation(name, value string) { - if i.Annotations == nil { - i.Annotations = map[string]string{} - } - i.Annotations[name] = value -} - -func (i *Index) DeleteAnnotation(name string) { - if i.Annotations == nil { - return - } - delete(i.Annotations, name) - if len(i.Annotations) == 0 { - i.Annotations = nil - } -} - -func (i *Index) Blob() (blobaccess.BlobAccess, error) { - i.MediaType = i.MimeType() - data, err := json.Marshal(i) - if err != nil { - return nil, err - } - return blobaccess.ForData(i.MediaType, data), nil -} - -func (i *Index) AddManifest(d *Descriptor) { - i.Manifests = append(i.Manifests, *d) -} - -//////////////////////////////////////////////////////////////////////////////// - -func DecodeIndex(data []byte) (*Index, error) { - var d Index - - if err := json.Unmarshal(data, &d); err != nil { - return nil, err - } - return &d, nil -} - -func EncodeIndex(d *Index) ([]byte, error) { - return json.Marshal(d) -} diff --git a/pkg/contexts/oci/artdesc/utils.go b/pkg/contexts/oci/artdesc/utils.go deleted file mode 100644 index 8c0ea74b9..000000000 --- a/pkg/contexts/oci/artdesc/utils.go +++ /dev/null @@ -1,132 +0,0 @@ -package artdesc - -import ( - "strings" - - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" -) - -func DefaultBlobDescriptor(blob blobaccess.BlobAccess) *Descriptor { - return &Descriptor{ - MediaType: blob.MimeType(), - Digest: blob.Digest(), - Size: blob.Size(), - URLs: nil, - Annotations: nil, - Platform: nil, - } -} - -func IsDigest(version string) (bool, digest.Digest) { - if strings.HasPrefix(version, "@") { - return true, digest.Digest(version[1:]) - } - if strings.Contains(version, ":") { - return true, digest.Digest(version) - } - return false, "" -} - -func ToContentMediaType(media string) string { -loop: - for { - last := strings.LastIndex(media, "+") - if last < 0 { - break - } - switch media[last+1:] { - case "tar": - fallthrough - case "gzip": - fallthrough - case "yaml": - fallthrough - case "json": - media = media[:last] - default: - break loop - } - } - return media -} - -func ToDescriptorMediaType(media string) string { - return ToContentMediaType(media) + "+json" -} - -func ToArchiveMediaTypes(media string) []string { - base := ToContentMediaType(media) - return []string{base + "+tar", base + "+tar+gzip"} -} - -func IsOCIMediaType(media string) bool { - c := ToContentMediaType(media) - for _, t := range ContentTypes() { - if t == c { - return true - } - } - return false -} - -func ContentTypes() []string { - r := []string{} - for _, t := range DescriptorTypes() { - r = append(r, ToContentMediaType(t)) - } - return r -} - -func DescriptorTypes() []string { - return []string{ - MediaTypeImageManifest, - MediaTypeImageIndex, - MediaTypeDockerSchema2Manifest, - MediaTypeDockerSchema2ManifestList, - } -} - -func ArchiveBlobTypes() []string { - r := []string{} - for _, t := range ContentTypes() { - r = append(r, ToArchiveMediaTypes(t)...) - } - return r -} - -func ArtifactMimeType(cur, def string, legacy bool) string { - if cur != "" { - return cur - } - return MapArtifactMimeType(def, legacy) -} - -func MapArtifactMimeType(mime string, legacy bool) string { - if legacy { - switch mime { - case MediaTypeImageManifest: - return MediaTypeDockerSchema2Manifest - case MediaTypeImageIndex: - return MediaTypeDockerSchema2ManifestList - } - } else { - switch mime { - case MediaTypeDockerSchema2Manifest: - // return MediaTypeImageManifest - case MediaTypeDockerSchema2ManifestList: - // return MediaTypeImageIndex - } - } - return mime -} - -func MapArtifactBlobMimeType(blob blobaccess.BlobAccess, legacy bool) blobaccess.BlobAccess { - mime := blob.MimeType() - mapped := MapArtifactMimeType(mime, legacy) - if mapped != mime { - return blobaccess.WithMimeType(mapped, blob) - } - return blob -} diff --git a/pkg/contexts/oci/artdesc/utils_test.go b/pkg/contexts/oci/artdesc/utils_test.go deleted file mode 100644 index 2a598a80a..000000000 --- a/pkg/contexts/oci/artdesc/utils_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package artdesc_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" -) - -var _ = Describe("utils", func() { - It("strips media type", func() { - Expect(artdesc.ToContentMediaType(artdesc.MediaTypeImageManifest)).To(Equal("application/vnd.oci.image.manifest.v1")) - Expect(artdesc.ToContentMediaType(artdesc.MediaTypeImageIndex)).To(Equal("application/vnd.oci.image.index.v1")) - }) - - It("maps to descriptor media typ", func() { - Expect(artdesc.ToDescriptorMediaType(artdesc.ToContentMediaType(artdesc.MediaTypeImageManifest) + "+tar+gzip")).To(Equal(artdesc.MediaTypeImageManifest)) - Expect(artdesc.ToDescriptorMediaType(artdesc.ToContentMediaType(artdesc.MediaTypeImageIndex) + "+tar+gzip")).To(Equal(artdesc.MediaTypeImageIndex)) - }) -}) diff --git a/pkg/contexts/oci/attrs/cacheattr/attr.go b/pkg/contexts/oci/attrs/cacheattr/attr.go deleted file mode 100644 index 1bea28b5f..000000000 --- a/pkg/contexts/oci/attrs/cacheattr/attr.go +++ /dev/null @@ -1,75 +0,0 @@ -package cacheattr - -import ( - "fmt" - "os" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -const ( - ATTR_KEY = "github.com/mandelsoft/oci/cache" - ATTR_SHORT = "cache" -) - -func init() { - datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) -} - -type AttributeType struct{} - -func (a AttributeType) Name() string { - return ATTR_KEY -} - -func (a AttributeType) Description() string { - return ` -*string* -Filesystem folder to use for caching OCI blobs -` -} - -func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { - if _, ok := v.(accessio.BlobCache); !ok { - return nil, fmt.Errorf("accessio.BlobCache required") - } - return nil, nil -} - -func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { - var value string - err := unmarshaller.Unmarshal(data, &value) - if value != "" { - value, err = utils.ResolvePath(value) - if err != nil { - return nil, err - } - // TODO: This should use the virtual filesystem. - err = os.MkdirAll(value, 0o700) - if err == nil { - return accessio.NewStaticBlobCache(value) - } - } else if err == nil { - err = errors.Newf("file path missing") - } - return value, err -} - -//////////////////////////////////////////////////////////////////////////////// - -func Get(ctx datacontext.Context) accessio.BlobCache { - a := ctx.GetAttributes().GetAttribute(ATTR_KEY) - if a == nil { - return nil - } - return a.(accessio.BlobCache) -} - -func Set(ctx datacontext.Context, cache accessio.BlobCache) error { - return ctx.GetAttributes().SetAttribute(ATTR_KEY, cache) -} diff --git a/pkg/contexts/oci/attrs/cacheattr/attr_test.go b/pkg/contexts/oci/attrs/cacheattr/attr_test.go deleted file mode 100644 index 0dac071db..000000000 --- a/pkg/contexts/oci/attrs/cacheattr/attr_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package cacheattr_test - -import ( - "os" - "reflect" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/config" - "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/oci/attrs/cacheattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/runtime" -) - -var _ = Describe("attribute", func() { - var ctx ocm.Context - var cfgctx config.Context - var cache accessio.BlobCache - - BeforeEach(func() { - var err error - cfgctx = config.WithSharedAttributes(datacontext.New(nil)).New() - credctx := credentials.WithConfigs(cfgctx).New() - ocictx := oci.WithCredentials(credctx).New() - ctx = ocm.WithOCIRepositories(ocictx).New() - cache, err = accessio.NewDefaultBlobCache() - Expect(err).To(Succeed()) - }) - AfterEach(func() { - cache.Unref() - }) - - It("local setting", func() { - Expect(cacheattr.Get(ctx)).To(BeNil()) - Expect(cacheattr.Set(ctx, cache)).To(Succeed()) - Expect(cacheattr.Get(ctx)).To(BeIdenticalTo(cache)) - }) - - It("global setting", func() { - Expect(cacheattr.Get(cfgctx)).To(BeNil()) - Expect(cacheattr.Set(ctx, cache)).To(Succeed()) - Expect(cacheattr.Get(ctx)).To(BeIdenticalTo(cache)) - }) - - It("parses string", func() { - dir := os.TempDir() - cache, err := cacheattr.AttributeType{}.Decode([]byte(dir), runtime.DefaultYAMLEncoding) - Expect(err).To(Succeed()) - Expect(reflect.TypeOf(cache).String()).To(Equal("*accessio.blobCache")) - }) -}) diff --git a/pkg/contexts/oci/attrs/init.go b/pkg/contexts/oci/attrs/init.go deleted file mode 100644 index 42dc1a55a..000000000 --- a/pkg/contexts/oci/attrs/init.go +++ /dev/null @@ -1,5 +0,0 @@ -package attrs - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/oci/attrs/cacheattr" -) diff --git a/pkg/contexts/oci/builder.go b/pkg/contexts/oci/builder.go deleted file mode 100644 index a6c8f85d5..000000000 --- a/pkg/contexts/oci/builder.go +++ /dev/null @@ -1,29 +0,0 @@ -package oci - -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/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 WithRepositoyTypeScheme(scheme RepositoryTypeScheme) internal.Builder { - return internal.Builder{}.WithRepositoyTypeScheme(scheme) -} - -func WithRepositorySpecHandlers(reg RepositorySpecHandlers) internal.Builder { - return internal.Builder{}.WithRepositorySpecHandlers(reg) -} - -func New(mode ...datacontext.BuilderMode) Context { - return internal.Builder{}.New(mode...) -} diff --git a/pkg/contexts/oci/config/config_test.go b/pkg/contexts/oci/config/config_test.go deleted file mode 100644 index 3cc1b311f..000000000 --- a/pkg/contexts/oci/config/config_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package config_test - -import ( - "encoding/json" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/oci/config" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ocireg" -) - -func normalize(i interface{}) ([]byte, error) { - data, err := json.Marshal(i) - if err != nil { - return nil, err - } - var generic map[string]interface{} - err = json.Unmarshal(data, &generic) - if err != nil { - return nil, err - } - return json.Marshal(generic) -} - -var _ = Describe("oci config", func() { - spec := ocireg.NewRepositorySpec("ghcr.io") - data, err := normalize(spec) - Expect(err).To(Succeed()) - - specdata := "{\"aliases\":{\"alias\":" + string(data) + "},\"type\":\"" + config.ConfigType + "\"}" - - Context("serialize", func() { - It("serializes config", func() { - cfg := config.New() - err := cfg.SetAlias("alias", spec) - Expect(err).To(Succeed()) - - data, err := normalize(cfg) - - Expect(err).To(Succeed()) - Expect(data).To(Equal([]byte(specdata))) - - cfg2 := config.New() - err = json.Unmarshal(data, cfg2) - Expect(err).To(Succeed()) - Expect(cfg2).To(Equal(cfg)) - }) - }) - - Context("apply", func() { - It("applies directly", func() { - ctx := cpi.New() - - cfg := config.New() - err := cfg.SetAlias("alias", spec) - Expect(err).To(Succeed()) - - Expect(cfg.ApplyTo(ctx.ConfigContext(), ctx)).To(Succeed()) - - found := ctx.GetAlias("alias") - Expect(found).To(Equal(cfg.Aliases["alias"])) - }) - - It("applies via config context", func() { - ctx := cpi.New() - - cfg := config.New() - err := cfg.SetAlias("alias", spec) - Expect(err).To(Succeed()) - - Expect(ctx.ConfigContext().ApplyConfig(cfg, "programmatic")).To(Succeed()) - - found := ctx.GetAlias("alias") - Expect(found).To(Equal(cfg.Aliases["alias"])) - }) - }) -}) diff --git a/pkg/contexts/oci/config/type.go b/pkg/contexts/oci/config/type.go deleted file mode 100644 index ad7fafbb1..000000000 --- a/pkg/contexts/oci/config/type.go +++ /dev/null @@ -1,70 +0,0 @@ -package config - -import ( - "github.com/open-component-model/ocm/pkg/contexts/config" - cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - ConfigType = "oci" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX - ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" -) - -func init() { - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1, usage)) -} - -// Config describes a memory based config interface. -type Config struct { - runtime.ObjectVersionedType `json:",inline"` - Aliases map[string]*cpi.GenericRepositorySpec `json:"aliases,omitempty"` -} - -// New creates a new memory ConfigSpec. -func New() *Config { - return &Config{ - ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), - } -} - -func (a *Config) GetType() string { - return ConfigType -} - -func (a *Config) SetAlias(name string, spec cpi.RepositorySpec) error { - g, err := cpi.ToGenericRepositorySpec(spec) - if err != nil { - return err - } - if a.Aliases == nil { - a.Aliases = map[string]*cpi.GenericRepositorySpec{} - } - a.Aliases[name] = g - return nil -} - -func (a *Config) ApplyTo(ctx config.Context, target interface{}) error { - t, ok := target.(cpi.Context) - if !ok { - return config.ErrNoContext(ConfigType) - } - for n, s := range a.Aliases { - t.SetAlias(n, s) - } - return nil -} - -const usage = ` -The config type ` + ConfigType + ` can be used to define -OCI registry aliases: - -
-    type: ` + ConfigType + `
-    aliases:
-       <name>: <OCI registry specification>
-       ...
-
-` diff --git a/pkg/contexts/oci/cpi/interface.go b/pkg/contexts/oci/cpi/interface.go deleted file mode 100644 index 8d372f2bf..000000000 --- a/pkg/contexts/oci/cpi/interface.go +++ /dev/null @@ -1,95 +0,0 @@ -package cpi - -// This is the Context Provider Interface for credential providers - -import ( - "github.com/opencontainers/go-digest" - ociv1 "github.com/opencontainers/image-spec/specs-go/v1" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/oci/internal" -) - -const CONTEXT_TYPE = internal.CONTEXT_TYPE - -const CommonTransportFormat = internal.CommonTransportFormat - -type ( - Context = internal.Context - ContextProvider = internal.ContextProvider - Repository = internal.Repository - RepositorySpecHandlers = internal.RepositorySpecHandlers - RepositorySpecHandler = internal.RepositorySpecHandler - UniformRepositorySpec = internal.UniformRepositorySpec - RepositoryType = internal.RepositoryType - RepositoryTypeProvider = internal.RepositoryTypeProvider - RepositoryTypeScheme = internal.RepositoryTypeScheme - RepositorySpec = internal.RepositorySpec - IntermediateRepositorySpecAspect = internal.IntermediateRepositorySpecAspect - GenericRepositorySpec = internal.GenericRepositorySpec - ArtifactAccess = internal.ArtifactAccess - Artifact = internal.Artifact - ArtifactSource = internal.ArtifactSource - ArtifactSink = internal.ArtifactSink - BlobSource = internal.BlobSource - BlobSink = internal.BlobSink - NamespaceLister = internal.NamespaceLister - NamespaceAccess = internal.NamespaceAccess - ManifestAccess = internal.ManifestAccess - IndexAccess = internal.IndexAccess - BlobAccess = internal.BlobAccess - DataAccess = internal.DataAccess - RepositorySource = internal.RepositorySource - ConsumerIdentityProvider = internal.ConsumerIdentityProvider -) - -type Descriptor = ociv1.Descriptor - -func DefaultContext() Context { - return internal.DefaultContext -} - -func New(m ...datacontext.BuilderMode) Context { - return internal.Builder{}.New(m...) -} - -func FromProvider(p ContextProvider) Context { - return internal.FromProvider(p) -} - -func RegisterRepositorySpecHandler(handler RepositorySpecHandler, types ...string) { - internal.RegisterRepositorySpecHandler(handler, types...) -} - -func ToGenericRepositorySpec(spec RepositorySpec) (*GenericRepositorySpec, error) { - return internal.ToGenericRepositorySpec(spec) -} - -func UniformRepositorySpecForHostURL(typ string, host string) *UniformRepositorySpec { - return internal.UniformRepositorySpecForHostURL(typ, host) -} - -const ( - KIND_OCIARTIFACT = internal.KIND_OCIARTIFACT - KIND_MEDIATYPE = blobaccess.KIND_MEDIATYPE - KIND_BLOB = blobaccess.KIND_BLOB -) - -func ErrUnknownArtifact(name, version string) error { - return internal.ErrUnknownArtifact(name, version) -} - -func ErrBlobNotFound(digest digest.Digest) error { - return blobaccess.ErrBlobNotFound(digest) -} - -func IsErrBlobNotFound(err error) bool { - return blobaccess.IsErrBlobNotFound(err) -} - -// provide context interface for other files to avoid diffs in imports. -var ( - newStrictRepositoryTypeScheme = internal.NewStrictRepositoryTypeScheme - defaultRepositoryTypeScheme = internal.DefaultRepositoryTypeScheme -) diff --git a/pkg/contexts/oci/cpi/repotypes.go b/pkg/contexts/oci/cpi/repotypes.go deleted file mode 100644 index 7633e3eb9..000000000 --- a/pkg/contexts/oci/cpi/repotypes.go +++ /dev/null @@ -1,36 +0,0 @@ -package cpi - -// this file is identical for contexts oci and credentials and similar for -// ocm. - -import ( - "github.com/open-component-model/ocm/pkg/runtime" -) - -type RepositoryTypeVersionScheme = runtime.TypeVersionScheme[RepositorySpec, RepositoryType] - -func NewRepositoryTypeVersionScheme(kind string) RepositoryTypeVersionScheme { - return runtime.NewTypeVersionScheme[RepositorySpec, RepositoryType](kind, newStrictRepositoryTypeScheme()) -} - -func RegisterRepositoryType(rtype RepositoryType) { - defaultRepositoryTypeScheme.Register(rtype) -} - -func RegisterRepositoryTypeVersions(s RepositoryTypeVersionScheme) { - defaultRepositoryTypeScheme.AddKnownTypes(s) -} - -//////////////////////////////////////////////////////////////////////////////// - -func NewRepositoryType[I RepositorySpec](name string) RepositoryType { - return runtime.NewVersionedTypedObjectType[RepositorySpec, I](name) -} - -func NewRepositoryTypeByConverter[I RepositorySpec, V runtime.TypedObject](name string, converter runtime.Converter[I, V]) RepositoryType { - return runtime.NewVersionedTypedObjectTypeByConverter[RepositorySpec, I](name, converter) -} - -func NewRepositoryTypeByFormatVersion(name string, fmt runtime.FormatVersion[RepositorySpec]) RepositoryType { - return runtime.NewVersionedTypedObjectTypeByFormatVersion[RepositorySpec](name, fmt) -} diff --git a/pkg/contexts/oci/cpi/state.go b/pkg/contexts/oci/cpi/state.go deleted file mode 100644 index 1abc4808a..000000000 --- a/pkg/contexts/oci/cpi/state.go +++ /dev/null @@ -1,84 +0,0 @@ -package cpi - -import ( - "reflect" - - "github.com/open-component-model/ocm/pkg/common/accessobj" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" -) - -type ManifestStateHandler struct{} - -var _ accessobj.StateHandler = &ManifestStateHandler{} - -func NewManifestStateHandler() accessobj.StateHandler { - return &ManifestStateHandler{} -} - -func (i ManifestStateHandler) Initial() interface{} { - return artdesc.NewManifest() -} - -func (i ManifestStateHandler) Encode(d interface{}) ([]byte, error) { - return artdesc.EncodeManifest(d.(*artdesc.Manifest)) -} - -func (i ManifestStateHandler) Decode(data []byte) (interface{}, error) { - return artdesc.DecodeManifest(data) -} - -func (i ManifestStateHandler) Equivalent(a, b interface{}) bool { - return reflect.DeepEqual(a, b) -} - -//////////////////////////////////////////////////////////////////////////////// - -type IndexStateHandler struct{} - -var _ accessobj.StateHandler = &IndexStateHandler{} - -func NewIndexStateHandler() accessobj.StateHandler { - return &IndexStateHandler{} -} - -func (i IndexStateHandler) Initial() interface{} { - return artdesc.NewIndex() -} - -func (i IndexStateHandler) Encode(d interface{}) ([]byte, error) { - return artdesc.EncodeIndex(d.(*artdesc.Index)) -} - -func (i IndexStateHandler) Decode(data []byte) (interface{}, error) { - return artdesc.DecodeIndex(data) -} - -func (i IndexStateHandler) Equivalent(a, b interface{}) bool { - return reflect.DeepEqual(a, b) -} - -//////////////////////////////////////////////////////////////////////////////// - -type ArtifactStateHandler struct{} - -var _ accessobj.StateHandler = &ArtifactStateHandler{} - -func NewArtifactStateHandler() accessobj.StateHandler { - return &ArtifactStateHandler{} -} - -func (i ArtifactStateHandler) Initial() interface{} { - return artdesc.New() -} - -func (i ArtifactStateHandler) Encode(d interface{}) ([]byte, error) { - return artdesc.Encode(d.(*artdesc.Artifact)) -} - -func (i ArtifactStateHandler) Decode(data []byte) (interface{}, error) { - return artdesc.Decode(data) -} - -func (i ArtifactStateHandler) Equivalent(a, b interface{}) bool { - return reflect.DeepEqual(a, b) -} diff --git a/pkg/contexts/oci/cpi/support/artifact.go b/pkg/contexts/oci/cpi/support/artifact.go deleted file mode 100644 index a2ed0493f..000000000 --- a/pkg/contexts/oci/cpi/support/artifact.go +++ /dev/null @@ -1,271 +0,0 @@ -package support - -import ( - "compress/gzip" - "fmt" - "io" - - "github.com/mandelsoft/goutils/errors" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/blobaccess/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/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/contexts/oci/internal" -) - -var ErrNoIndex = errors.New("manifest does not support access to subsequent artifacts") - -type ArtifactAccessImpl struct { - cpi.ArtifactAccessImplBase - artifactBase -} - -var _ cpi.ArtifactAccessImpl = (*ArtifactAccessImpl)(nil) - -func NewArtifactForBlob(container NamespaceAccessImpl, blob blobaccess.BlobAccess, closer ...io.Closer) (cpi.ArtifactAccess, error) { - mode := accessobj.ACC_WRITABLE - if container.IsReadOnly() { - mode = accessobj.ACC_READONLY - } - state, err := accessobj.NewBlobStateForBlob(mode, blob, cpi.NewArtifactStateHandler()) - if err != nil { - return nil, err - } - return newArtifact(container, state, closer...) -} - -func NewArtifact(container NamespaceAccessImpl, defs ...cpi.Artifact) (cpi.ArtifactAccess, error) { - var def cpi.Artifact - if len(defs) != 0 && defs[0] != nil && defs[0].IsValid() { - def = defs[0].Artifact() - } - mode := accessobj.ACC_WRITABLE - if container.IsReadOnly() { - mode = accessobj.ACC_READONLY - } - state, err := accessobj.NewBlobStateForObject(mode, def, cpi.NewArtifactStateHandler()) - if err != nil { - return nil, fmt.Errorf("failed to fetch new blob state: %w", err) - } - return newArtifact(container, state) -} - -func newArtifact(container NamespaceAccessImpl, state accessobj.State, closer ...io.Closer) (cpi.ArtifactAccess, error) { - base, err := cpi.NewArtifactAccessImplBase(container, closer...) - if err != nil { - return nil, err - } - impl := &ArtifactAccessImpl{ - ArtifactAccessImplBase: *base, - artifactBase: newArtifactBase(container, state), - } - return cpi.NewArtifactAccess(impl), nil -} - -func (a *ArtifactAccessImpl) AddBlob(access cpi.BlobAccess) error { - return a.container.AddBlob(access) -} - -func (a *ArtifactAccessImpl) NewArtifact(art ...cpi.Artifact) (cpi.ArtifactAccess, error) { - if !a.IsIndex() { - return nil, ErrNoIndex - } - if a.IsReadOnly() { - return nil, accessio.ErrReadOnly - } - return NewArtifact(a.container, art...) -} - -//////////////////////////////////////////////////////////////////////////////// - -func (a *ArtifactAccessImpl) Artifact() *artdesc.Artifact { - return a.GetDescriptor() -} - -func (a *ArtifactAccessImpl) GetDescriptor() *artdesc.Artifact { - d := a.state.GetState().(*artdesc.Artifact) - if d.IsValid() { - return d - } - return nil -} - -//////////////////////////////////////////////////////////////////////////////// -// from artdesc.Artifact - -func (a *ArtifactAccessImpl) GetBlobDescriptor(digest digest.Digest) *cpi.Descriptor { - d := a.GetDescriptor().GetBlobDescriptor(digest) - /* - if d == nil { - d = a.container.GetBlobDescriptor(digest) - } - */ - return d -} - -func (a *ArtifactAccessImpl) Index() (*artdesc.Index, error) { - a.lock.Lock() - defer a.lock.Unlock() - d, ok := a.state.GetState().(*artdesc.Artifact) - if !ok { - return nil, fmt.Errorf("failed to assert type %T to *artdesc.Artifact", a.state.GetState()) - } - if !d.IsValid() { - idx := artdesc.NewIndex() - if err := d.SetIndex(idx); err != nil { - return nil, errors.Newf("artifact is manifest") - } - } - return d.Index() -} - -func (a *ArtifactAccessImpl) Manifest() (*artdesc.Manifest, error) { - a.lock.Lock() - defer a.lock.Unlock() - d := a.state.GetState().(*artdesc.Artifact) - if !d.IsValid() { - m := artdesc.NewManifest() - if err := d.SetManifest(m); err != nil { - return nil, errors.Newf("artifact is index") - } - } - return d.Manifest() -} - -func (a *ArtifactAccessImpl) ManifestAccess(v cpi.ArtifactAccess) internal.ManifestAccess { - a.lock.Lock() - defer a.lock.Unlock() - d := a.state.GetState().(*artdesc.Artifact) - if !d.IsManifest() { - m := artdesc.NewManifest() - if err := d.SetManifest(m); err != nil { - return nil - } - } - return NewManifestForArtifact(v, a) -} - -func (a *ArtifactAccessImpl) IndexAccess(v cpi.ArtifactAccess) internal.IndexAccess { - a.lock.Lock() - defer a.lock.Unlock() - d := a.state.GetState().(*artdesc.Artifact) - if !d.IsIndex() { - i := artdesc.NewIndex() - if err := d.SetIndex(i); err != nil { - return nil - } - } - return NewIndexForArtifact(v, a) -} - -func (a *ArtifactAccessImpl) GetArtifact(digest digest.Digest) (cpi.ArtifactAccess, error) { - if !a.IsIndex() { - return nil, ErrNoIndex - } - return a.container.GetArtifact("@" + digest.String()) -} - -func (a *ArtifactAccessImpl) GetBlobData(digest digest.Digest) (int64, cpi.DataAccess, error) { - return a.container.GetBlobData(digest) -} - -func (a *ArtifactAccessImpl) GetBlob(digest digest.Digest) (cpi.BlobAccess, error) { - d := a.GetBlobDescriptor(digest) - if d != nil { - size, data, err := a.container.GetBlobData(digest) - if err != nil { - return nil, err - } - err = AdjustSize(d, size) - if err != nil { - return nil, err - } - return blobaccess.ForDataAccess(d.Digest, d.Size, d.MediaType, data), nil - } - return nil, cpi.ErrBlobNotFound(digest) -} - -func (a *ArtifactAccessImpl) AddArtifact(art cpi.Artifact, platform *artdesc.Platform) (cpi.BlobAccess, error) { - if a.IsReadOnly() { - return nil, accessio.ErrReadOnly - } - d, err := a.Index() - if err != nil { - return nil, err - } - - a.lock.Lock() - defer a.lock.Unlock() - - blob, err := a.container.AddArtifact(art) - if err != nil { - return nil, err - } - d.Manifests = append(d.Manifests, cpi.Descriptor{ - MediaType: blob.MimeType(), - Digest: blob.Digest(), - Size: blob.Size(), - URLs: nil, - Annotations: nil, - Platform: platform, - }) - return blob, nil -} - -func (a *ArtifactAccessImpl) AddLayer(blob cpi.BlobAccess, d *cpi.Descriptor) (int, error) { - if a.IsReadOnly() { - return -1, accessio.ErrReadOnly - } - m, err := a.Manifest() - if err != nil { - return -1, err - } - - a.lock.Lock() - defer a.lock.Unlock() - if d == nil { - d = &artdesc.Descriptor{} - } - d.Digest = blob.Digest() - d.Size = blob.Size() - if d.MediaType == "" { - d.MediaType = blob.MimeType() - if d.MediaType == "" { - d.MediaType = artdesc.MediaTypeImageLayer - r, err := blob.Reader() - if err != nil { - return -1, err - } - defer r.Close() - zr, err := gzip.NewReader(r) - if err == nil { - err = zr.Close() - if err == nil { - d.MediaType = artdesc.MediaTypeImageLayerGzip - } - } - } - } - - err = a.container.AddBlob(blob) - if err != nil { - return -1, err - } - - m.Layers = append(m.Layers, *d) - return len(m.Layers) - 1, nil -} - -func AdjustSize(d *artdesc.Descriptor, size int64) error { - if size != blobaccess.BLOB_UNKNOWN_SIZE { - if d.Size == blobaccess.BLOB_UNKNOWN_SIZE { - d.Size = size - } else if d.Size != size { - return errors.Newf("blob size mismatch %d != %d", size, d.Size) - } - } - return nil -} diff --git a/pkg/contexts/oci/cpi/support/namespace.go b/pkg/contexts/oci/cpi/support/namespace.go deleted file mode 100644 index 99a78d743..000000000 --- a/pkg/contexts/oci/cpi/support/namespace.go +++ /dev/null @@ -1,113 +0,0 @@ -package support - -import ( - "github.com/mandelsoft/goutils/errors" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/refmgmt" -) - -// BlobProvider manages the technical access to blobs. -type BlobProvider interface { - refmgmt.Allocatable - cpi.BlobSource - cpi.BlobSink -} - -// NamespaceContainer is the interface used by subsequent access objects -// to access the base implementation. -type NamespaceContainer interface { - SetImplementation(impl NamespaceAccessImpl) - - IsReadOnly() bool - // IsClosed() bool - - cpi.BlobSource - cpi.BlobSink - - Close() error - - // GetBlobDescriptor(digest digest.Digest) *cpi.Descriptor - - GetArtifact(i NamespaceAccessImpl, vers string) (cpi.ArtifactAccess, error) - NewArtifact(i NamespaceAccessImpl, arts ...cpi.Artifact) (cpi.ArtifactAccess, error) - - AddArtifact(artifact cpi.Artifact, tags ...string) (access blobaccess.BlobAccess, err error) - - AddTags(digest digest.Digest, tags ...string) error - ListTags() ([]string, error) - HasArtifact(vers string) (bool, error) -} - -//////////////////////////////////////////////////////////////////////////////// - -type NamespaceAccessImpl interface { - cpi.NamespaceAccessImpl - - // GetBlobDescriptor(digest digest.Digest) *cpi.Descriptor - IsReadOnly() bool - - WithContainer(container NamespaceContainer) NamespaceAccessImpl -} - -type namespaceAccessImpl struct { - *cpi.NamespaceAccessImplBase - NamespaceContainer // inherit as many as possible methods for cpi.NamespaceAccessImpl -} - -var _ NamespaceAccessImpl = (*namespaceAccessImpl)(nil) - -func NewNamespaceAccessImpl(namespace string, c NamespaceContainer, repo cpi.RepositoryViewManager) (NamespaceAccessImpl, error) { - base, err := cpi.NewNamespaceAccessImplBase(namespace, repo) - if err != nil { - return nil, err - } - impl := &namespaceAccessImpl{ - NamespaceAccessImplBase: base, - NamespaceContainer: c, - } - - c.SetImplementation(impl) - return impl, nil -} - -func (n *namespaceAccessImpl) Close() error { - return accessio.Close(n.NamespaceAccessImplBase, n.NamespaceContainer) -} - -func NewNamespaceAccess(namespace string, c NamespaceContainer, repo cpi.RepositoryViewManager, kind ...string) (cpi.NamespaceAccess, error) { - impl, err := NewNamespaceAccessImpl(namespace, c, repo) - if err != nil { - return nil, err - } - return cpi.NewNamespaceAccess(impl, kind...), nil -} - -func GetArtifactSetContainer(i cpi.NamespaceAccessImpl) (NamespaceContainer, error) { - if c, ok := i.(*namespaceAccessImpl); ok { - return c.NamespaceContainer, nil - } - return nil, errors.ErrNotSupported() -} - -func (i *namespaceAccessImpl) WithContainer(c NamespaceContainer) NamespaceAccessImpl { - return &namespaceAccessImpl{ - NamespaceAccessImplBase: i.NamespaceAccessImplBase, - NamespaceContainer: c, - } -} - -func (i *namespaceAccessImpl) GetArtifact(vers string) (cpi.ArtifactAccess, error) { - return i.NamespaceContainer.GetArtifact(i, vers) -} - -func (i *namespaceAccessImpl) AddArtifact(artifact cpi.Artifact, tags ...string) (access blobaccess.BlobAccess, err error) { - return i.NamespaceContainer.AddArtifact(artifact, tags...) -} - -func (i *namespaceAccessImpl) NewArtifact(arts ...cpi.Artifact) (cpi.ArtifactAccess, error) { - return i.NamespaceContainer.NewArtifact(i, arts...) -} diff --git a/pkg/contexts/oci/cpi/utils.go b/pkg/contexts/oci/cpi/utils.go deleted file mode 100644 index 5eec403f9..000000000 --- a/pkg/contexts/oci/cpi/utils.go +++ /dev/null @@ -1,63 +0,0 @@ -package cpi - -import ( - "strings" - - "github.com/open-component-model/ocm/pkg/contexts/oci/grammar" -) - -type StringList []string - -func (s *StringList) Add(n string) { - for _, e := range *s { - if n == e { - return - } - } - *s = append(*s, n) -} - -func FilterByNamespacePrefix(prefix string, list []string) []string { - result := []string{} - sub := prefix - if prefix != "" && !strings.HasSuffix(prefix, grammar.RepositorySeparator) { - sub = prefix + grammar.RepositorySeparator - } - for _, k := range list { - if k == prefix || strings.HasPrefix(k, sub) { - result = append(result, k) - } - } - return result -} - -func FilterChildren(closure bool, prefix string, list []string) []string { - if closure { - return FilterByNamespacePrefix(prefix, list) - } - sub := prefix - if prefix != "" && !strings.HasSuffix(prefix, grammar.RepositorySeparator) { - sub = prefix + grammar.RepositorySeparator - } - set := map[string]bool{} - for _, n := range list { - if n == prefix { - set[n] = true - } else if strings.HasPrefix(n, sub) { - rest := n[len(sub):] - i := strings.Index(rest, grammar.RepositorySeparator) - if i < 0 { - set[n] = true - } else { - set[n[:i+len(sub)]] = true - } - } - } - result := make([]string, 0, len(set)) - for _, n := range list { - if set[n] { - result = append(result, n) - } - } - return result -} diff --git a/pkg/contexts/oci/cpi/utils_test.go b/pkg/contexts/oci/cpi/utils_test.go deleted file mode 100644 index 7f5fa8347..000000000 --- a/pkg/contexts/oci/cpi/utils_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package cpi_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" -) - -var _ = Describe("OCI CPI utils", func() { - list := []string{ - "a/b/c/d", - "a/b/c", - "a/b", - "a/c/d", - "a/c", - "b/c", - } - - It("calculates exclusive number", func() { - Expect(cpi.FilterByNamespacePrefix("a/b/", list)).To(Equal([]string{ - "a/b/c/d", - "a/b/c", - })) - }) - It("calculates inclusive number", func() { - Expect(cpi.FilterByNamespacePrefix("a/b", list)).To(Equal([]string{ - "a/b/c/d", - "a/b/c", - "a/b", - })) - }) - - It("calculates closure", func() { - Expect(cpi.FilterChildren(true, "a/b", list)).To(Equal([]string{ - "a/b/c/d", - "a/b/c", - "a/b", - })) - }) - - It("calculates children", func() { - Expect(cpi.FilterChildren(false, "a/b/", list)).To(Equal([]string{ - "a/b/c", - })) - }) - - It("calculates inclusive children", func() { - Expect(cpi.FilterChildren(false, "a/b", list)).To(Equal([]string{ - "a/b/c", - "a/b", - })) - }) - - It("calculates children closure", func() { - Expect(cpi.FilterChildren(true, "a/b", cpi.FilterByNamespacePrefix("a/b", list))).To(Equal([]string{ - "a/b/c/d", - "a/b/c", - "a/b", - })) - }) -}) diff --git a/pkg/contexts/oci/cpi/view.go b/pkg/contexts/oci/cpi/view.go deleted file mode 100644 index 4c7bccc26..000000000 --- a/pkg/contexts/oci/cpi/view.go +++ /dev/null @@ -1,397 +0,0 @@ -package cpi - -import ( - "fmt" - "io" - - "github.com/mandelsoft/goutils/errors" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/internal" - "github.com/open-component-model/ocm/pkg/refmgmt/resource" - "github.com/open-component-model/ocm/pkg/utils" -) - -var ErrClosed = resource.ErrClosed - -//////////////////////////////////////////////////////////////////////////////// - -type _RepositoryView interface { - resource.ResourceViewInt[Repository] // here you have to redeclare -} - -type RepositoryViewManager = resource.ViewManager[Repository] // here you have to use an alias - -type RepositoryImpl interface { - internal.RepositoryImpl - resource.ResourceImplementation[Repository] -} - -type _RepositoryImplBase = resource.ResourceImplBase[Repository] - -type RepositoryImplBase struct { - _RepositoryImplBase - ctx Context -} - -func (b *RepositoryImplBase) GetContext() Context { - return b.ctx -} - -func NewRepositoryImplBase(ctx Context) RepositoryImplBase { - return RepositoryImplBase{ - _RepositoryImplBase: resource.ResourceImplBase[Repository]{}, - ctx: ctx, - } -} - -type repositoryView struct { - _RepositoryView - impl RepositoryImpl -} - -var ( - _ Repository = (*repositoryView)(nil) - _ internal.ConsumerIdentityProvider = (*repositoryView)(nil) -) - -func GetRepositoryImplementation(n Repository) (RepositoryImpl, error) { - if v, ok := n.(*repositoryView); ok { - return v.impl, nil - } - return nil, errors.ErrNotSupported("repository implementation type", fmt.Sprintf("%T", n)) -} - -func repositoryViewCreator(i RepositoryImpl, v resource.CloserView, d RepositoryViewManager) Repository { - return &repositoryView{ - _RepositoryView: resource.NewView[Repository](v, d), - impl: i, - } -} - -func NewRepository(impl RepositoryImpl, name ...string) Repository { - return resource.NewResource[Repository](impl, repositoryViewCreator, utils.OptionalDefaulted("OCI repo", name...), true) -} - -func (r *repositoryView) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { - return credentials.GetProvidedConsumerId(r.impl, uctx...) -} - -func (r *repositoryView) GetIdentityMatcher() string { - return credentials.GetProvidedIdentityMatcher(r.impl) -} - -func (r *repositoryView) GetSpecification() internal.RepositorySpec { - return r.impl.GetSpecification() -} - -func (r *repositoryView) GetContext() Context { - return r.impl.GetContext() -} - -func (r *repositoryView) NamespaceLister() (lister internal.NamespaceLister) { - return r.impl.NamespaceLister() -} - -func (r *repositoryView) ExistsArtifact(name string, ref string) (ok bool, err error) { - err = r.Execute(func() error { - ok, err = r.impl.ExistsArtifact(name, ref) - return err - }) - return ok, err -} - -func (r *repositoryView) LookupArtifact(name string, ref string) (acc ArtifactAccess, err error) { - err = r.Execute(func() error { - acc, err = r.impl.LookupArtifact(name, ref) - return err - }) - return acc, err -} - -func (r *repositoryView) LookupNamespace(name string) (acc NamespaceAccess, err error) { - err = r.Execute(func() error { - acc, err = r.impl.LookupNamespace(name) - return err - }) - return acc, err -} - -//////////////////////////////////////////////////////////////////////////////// - -type _NamespaceAccessView interface { - resource.ResourceViewInt[NamespaceAccess] // here you have to redeclare -} - -type NamespaceAccessViewManager = resource.ViewManager[NamespaceAccess] // here you have to use an alias - -type NamespaceAccessImpl interface { - internal.NamespaceAccessImpl - - resource.ResourceImplementation[NamespaceAccess] - - GetNamespace() string -} - -type _NamespaceAccessImplBase = resource.ResourceImplBase[NamespaceAccess] - -type NamespaceAccessImplBase struct { - *_NamespaceAccessImplBase - namespace string -} - -func NewNamespaceAccessImplBase(namespace string, repo RepositoryViewManager, closer ...io.Closer) (*NamespaceAccessImplBase, error) { - base, err := resource.NewResourceImplBase[NamespaceAccess](repo, closer...) - if err != nil { - return nil, err - } - return &NamespaceAccessImplBase{ - _NamespaceAccessImplBase: base, - namespace: namespace, - }, nil -} - -func (b *NamespaceAccessImplBase) GetNamespace() string { - return b.namespace -} - -type namespaceAccessView struct { - _NamespaceAccessView - impl NamespaceAccessImpl -} - -var _ NamespaceAccess = (*namespaceAccessView)(nil) - -func GetNamespaceAccessImplementation(n NamespaceAccess) (NamespaceAccessImpl, error) { - if v, ok := n.(*namespaceAccessView); ok { - return v.impl, nil - } - return nil, errors.ErrNotSupported("namespace implementation type", fmt.Sprintf("%T", n)) -} - -func namespaceAccessViewCreator(i NamespaceAccessImpl, v resource.CloserView, d NamespaceAccessViewManager) NamespaceAccess { - return &namespaceAccessView{ - _NamespaceAccessView: resource.NewView[NamespaceAccess](v, d), - impl: i, - } -} - -func NewNamespaceAccess(impl NamespaceAccessImpl, kind ...string) NamespaceAccess { - return resource.NewResource[NamespaceAccess](impl, namespaceAccessViewCreator, fmt.Sprintf("%s %s", utils.OptionalDefaulted("namespace", kind...), impl.GetNamespace()), true) -} - -func (n *namespaceAccessView) GetNamespace() string { - return n.impl.GetNamespace() -} - -func (n *namespaceAccessView) GetArtifact(version string) (acc internal.ArtifactAccess, err error) { - err = n.Execute(func() error { - acc, err = n.impl.GetArtifact(version) - return err - }) - return acc, err -} - -func (n *namespaceAccessView) GetBlobData(digest digest.Digest) (size int64, acc internal.DataAccess, err error) { - err = n.Execute(func() error { - size, acc, err = n.impl.GetBlobData(digest) - return err - }) - return size, acc, err -} - -func (n *namespaceAccessView) AddBlob(access internal.BlobAccess) error { - return n.Execute(func() error { - return n.impl.AddBlob(access) - }) -} - -func (n *namespaceAccessView) HasArtifact(vers string) (ok bool, err error) { - err = n.Execute(func() error { - ok, err = n.impl.HasArtifact(vers) - return err - }) - return ok, err -} - -func (n *namespaceAccessView) AddArtifact(a internal.Artifact, tags ...string) (acc internal.BlobAccess, err error) { - err = n.Execute(func() error { - acc, err = n.impl.AddArtifact(a, tags...) - return err - }) - return acc, err -} - -func (n *namespaceAccessView) AddTags(digest digest.Digest, tags ...string) error { - return n.Execute(func() error { - return n.impl.AddTags(digest, tags...) - }) -} - -func (n *namespaceAccessView) ListTags() (list []string, err error) { - err = n.Execute(func() error { - list, err = n.impl.ListTags() - return err - }) - return list, err -} - -func (n *namespaceAccessView) NewArtifact(artifact ...Artifact) (acc internal.ArtifactAccess, err error) { - err = n.Execute(func() error { - acc, err = n.impl.NewArtifact(artifact...) - return err - }) - return acc, err -} - -//////////////////////////////////////////////////////////////////////////////// - -type _ArtifactAccessView interface { - resource.ResourceViewInt[ArtifactAccess] -} - -type ArtifactAccessViewManager = resource.ViewManager[ArtifactAccess] - -type ArtifactAccessImpl interface { - internal.ArtifactAccessImpl - - resource.ResourceImplementation[ArtifactAccess] - - // creation of slave objects require the original view they are created for. - - ManifestAccess(ArtifactAccess) ManifestAccess - IndexAccess(ArtifactAccess) IndexAccess -} - -type ArtifactAccessImplBase = resource.ResourceImplBase[ArtifactAccess] - -func NewArtifactAccessImplBase(ns NamespaceAccessViewManager, closer ...io.Closer) (*ArtifactAccessImplBase, error) { - return resource.NewResourceImplBase[ArtifactAccess](ns, closer...) -} - -type artifactAccessView struct { - _ArtifactAccessView - impl ArtifactAccessImpl -} - -var _ ArtifactAccess = (*artifactAccessView)(nil) - -func artifactAccessViewCreator(i ArtifactAccessImpl, v resource.CloserView, d resource.ViewManager[ArtifactAccess]) ArtifactAccess { - return &artifactAccessView{ - _ArtifactAccessView: resource.NewView[ArtifactAccess](v, d), - impl: i, - } -} - -func NewArtifactAccess(impl ArtifactAccessImpl) ArtifactAccess { - return resource.NewResource[ArtifactAccess](impl, artifactAccessViewCreator, "artifact", true) -} - -func (a *artifactAccessView) IsManifest() bool { - return a.impl.IsManifest() -} - -func (a *artifactAccessView) IsIndex() bool { - return a.impl.IsIndex() -} - -func (a *artifactAccessView) IsValid() bool { - return a.impl.IsValid() -} - -func (a *artifactAccessView) Digest() digest.Digest { - return a.impl.Digest() -} - -func (a *artifactAccessView) Blob() (internal.BlobAccess, error) { - return a.impl.Blob() -} - -func (a *artifactAccessView) GetDescriptor() *artdesc.Artifact { - return a.impl.GetDescriptor() -} - -func (a *artifactAccessView) Artifact() *artdesc.Artifact { - return a.impl.Artifact() -} - -func (a *artifactAccessView) Manifest() (*artdesc.Manifest, error) { - return a.impl.Manifest() -} - -func (a *artifactAccessView) ManifestAccess() internal.ManifestAccess { - return a.impl.ManifestAccess(a) -} - -func (a *artifactAccessView) Index() (*artdesc.Index, error) { - return a.impl.Index() -} - -func (a *artifactAccessView) IndexAccess() internal.IndexAccess { - return a.impl.IndexAccess(a) -} - -func (a *artifactAccessView) GetBlobData(digest digest.Digest) (size int64, acc internal.DataAccess, err error) { - size = -1 - err = a.Execute(func() error { - size, acc, err = a.impl.GetBlobData(digest) - return err - }) - return size, acc, err -} - -func (a *artifactAccessView) AddBlob(access internal.BlobAccess) error { - if err := utils.ValidateObject(access); err != nil { - return err - } - return a.Execute(func() error { - return a.impl.AddBlob(access) - }) -} - -func (a *artifactAccessView) GetBlob(digest digest.Digest) (acc internal.BlobAccess, err error) { - err = a.Execute(func() error { - acc, err = a.impl.GetBlob(digest) - return err - }) - return acc, err -} - -func (a *artifactAccessView) GetArtifact(digest digest.Digest) (acc internal.ArtifactAccess, err error) { - err = a.Execute(func() error { - acc, err = a.impl.GetArtifact(digest) - return err - }) - return acc, err -} - -func (a *artifactAccessView) AddArtifact(artifact internal.Artifact, platform *artdesc.Platform) (acc internal.BlobAccess, err error) { - err = a.Execute(func() error { - acc, err = a.impl.AddArtifact(artifact, platform) - return err - }) - return acc, err -} - -func (a *artifactAccessView) NewArtifact(art ...Artifact) (acc ArtifactAccess, err error) { - err = a.Execute(func() error { - acc, err = a.impl.NewArtifact(art...) - return err - }) - return acc, err -} - -func (a *artifactAccessView) AddLayer(access internal.BlobAccess, descriptor *artdesc.Descriptor) (index int, err error) { - if err := utils.ValidateObject(access); err != nil { - return -1, err - } - - index = -1 - err = a.Execute(func() error { - index, err = a.impl.AddLayer(access, descriptor) - return err - }) - return index, err -} diff --git a/pkg/contexts/oci/gc_test.go b/pkg/contexts/oci/gc_test.go deleted file mode 100644 index bc48501b9..000000000 --- a/pkg/contexts/oci/gc_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package oci_test - -import ( - "runtime" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - me "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/runtimefinalizer" -) - -var _ = Describe("area test", func() { - It("can be garbage collected", func() { - ctx := me.New() - - r := runtimefinalizer.GetRuntimeFinalizationRecorder(ctx) - Expect(r).NotTo(BeNil()) - - runtime.GC() - time.Sleep(time.Second) - ctx.GetType() - Expect(r.Get()).To(BeNil()) - - ctx = nil - for i := 0; i < 100; i++ { - runtime.GC() - time.Sleep(time.Millisecond) - } - - Expect(r.Get()).To(ContainElement(ContainSubstring(me.CONTEXT_TYPE))) - }) -}) diff --git a/pkg/contexts/oci/identity/deprecated.go b/pkg/contexts/oci/identity/deprecated.go deleted file mode 100644 index 6b61c4111..000000000 --- a/pkg/contexts/oci/identity/deprecated.go +++ /dev/null @@ -1,51 +0,0 @@ -package identity - -import ( - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" -) - -// CONSUMER_TYPE is the OCT registry type. -// Deprecated: use package github.com/open-component-model/ocm/contexts/credentials/builtin/oci/identity . -const CONSUMER_TYPE = identity.CONSUMER_TYPE - -// used identity properties. -const ( - // Deprecated: use package github.com/open-component-model/ocm/contexts/credentials/builtin/oci/identity . - ID_TYPE = identity.ID_TYPE - // Deprecated: use package github.com/open-component-model/ocm/contexts/credentials/builtin/oci/identity . - ID_HOSTNAME = identity.ID_HOSTNAME - // Deprecated: use package github.com/open-component-model/ocm/contexts/credentials/builtin/oci/identity . - ID_PORT = identity.ID_PORT - // Deprecated: use package github.com/open-component-model/ocm/contexts/credentials/builtin/oci/identity . - ID_PATHPREFIX = identity.ID_PATHPREFIX - // Deprecated: use package github.com/open-component-model/ocm/contexts/credentials/builtin/oci/identity . - ID_SCHEME = identity.ID_SCHEME -) - -// used credential properties. -const ( - // Deprecated: use package github.com/open-component-model/ocm/contexts/credentials/builtin/oci/identity . - ATTR_USERNAME = identity.ATTR_USERNAME - // Deprecated: use package github.com/open-component-model/ocm/contexts/credentials/builtin/oci/identity . - ATTR_PASSWORD = identity.ID_PORT - // Deprecated: use package github.com/open-component-model/ocm/contexts/credentials/builtin/oci/identity . - ATTR_IDENTITY_TOKEN = identity.ATTR_IDENTITY_TOKEN - // Deprecated: use package github.com/open-component-model/ocm/contexts/credentials/builtin/oci/identity . - ATTR_CERTIFICATE_AUTHORITY = identity.ATTR_CERTIFICATE_AUTHORITY -) - -// Deprecated: use package github.com/open-component-model/ocm/contexts/credentials/builtin/oci/identity .. -func IdentityMatcher(pattern, cur, id cpi.ConsumerIdentity) bool { - return identity.IdentityMatcher(pattern, cur, id) -} - -// Deprecated: use package github.com/open-component-model/ocm/contexts/credentials/builtin/oci/identity . -func GetCredentials(ctx cpi.ContextProvider, locator, repo string) (cpi.Credentials, error) { - return identity.GetCredentials(ctx, locator, repo) -} - -// Deprecated: use package github.com/open-component-model/ocm/contexts/credentials/builtin/oci/identity . -func GetConsumerId(locator, repo string) cpi.ConsumerIdentity { - return identity.GetConsumerId(locator, repo) -} diff --git a/pkg/contexts/oci/init.go b/pkg/contexts/oci/init.go deleted file mode 100644 index 97dbc0b28..000000000 --- a/pkg/contexts/oci/init.go +++ /dev/null @@ -1,8 +0,0 @@ -package oci - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/oci/actions" - _ "github.com/open-component-model/ocm/pkg/contexts/oci/attrs" - _ "github.com/open-component-model/ocm/pkg/contexts/oci/config" - _ "github.com/open-component-model/ocm/pkg/contexts/oci/repositories" -) diff --git a/pkg/contexts/oci/interface.go b/pkg/contexts/oci/interface.go deleted file mode 100644 index dac0af725..000000000 --- a/pkg/contexts/oci/interface.go +++ /dev/null @@ -1,64 +0,0 @@ -package oci - -import ( - "context" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/oci/internal" -) - -const ( - KIND_OCIARTIFACT = internal.KIND_OCIARTIFACT - KIND_MEDIATYPE = blobaccess.KIND_MEDIATYPE - KIND_BLOB = blobaccess.KIND_BLOB -) - -const CONTEXT_TYPE = internal.CONTEXT_TYPE - -const CommonTransportFormat = internal.CommonTransportFormat - -type ( - Context = internal.Context - ContextProvider = internal.ContextProvider - Repository = internal.Repository - RepositorySpecHandlers = internal.RepositorySpecHandlers - RepositorySpecHandler = internal.RepositorySpecHandler - UniformRepositorySpec = internal.UniformRepositorySpec - RepositoryTypeScheme = internal.RepositoryTypeScheme - RepositorySpec = internal.RepositorySpec - RepositoryType = internal.RepositoryType - IntermediateRepositorySpecAspect = internal.IntermediateRepositorySpecAspect - GenericRepositorySpec = internal.GenericRepositorySpec - ArtifactAccess = internal.ArtifactAccess - NamespaceLister = internal.NamespaceLister - NamespaceAccess = internal.NamespaceAccess - ManifestAccess = internal.ManifestAccess - IndexAccess = internal.IndexAccess - BlobAccess = internal.BlobAccess - DataAccess = internal.DataAccess - ConsumerIdentityProvider = internal.ConsumerIdentityProvider -) - -func DefaultContext() internal.Context { - return internal.DefaultContext -} - -func FromContext(ctx context.Context) Context { - return internal.ForContext(ctx) -} - -func FromProvider(p ContextProvider) Context { - return internal.FromProvider(p) -} - -func DefinedForContext(ctx context.Context) (Context, bool) { - return internal.DefinedForContext(ctx) -} - -func IsErrBlobNotFound(err error) bool { - return blobaccess.IsErrBlobNotFound(err) -} - -func ToGenericRepositorySpec(spec RepositorySpec) (*GenericRepositorySpec, error) { - return internal.ToGenericRepositorySpec(spec) -} diff --git a/pkg/contexts/oci/internal/builder.go b/pkg/contexts/oci/internal/builder.go deleted file mode 100644 index c930d7ed5..000000000 --- a/pkg/contexts/oci/internal/builder.go +++ /dev/null @@ -1,92 +0,0 @@ -package internal - -import ( - "context" - - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" -) - -type Builder struct { - ctx context.Context - credentials credentials.Context - reposcheme RepositoryTypeScheme - spechandlers RepositorySpecHandlers -} - -func (b *Builder) getContext() context.Context { - if b.ctx == nil { - return context.Background() - } - return b.ctx -} - -func (b Builder) WithContext(ctx context.Context) Builder { - b.ctx = ctx - return b -} - -func (b Builder) WithCredentials(ctx credentials.Context) Builder { - b.credentials = ctx - return b -} - -func (b Builder) WithRepositoyTypeScheme(scheme RepositoryTypeScheme) Builder { - b.reposcheme = scheme - return b -} - -func (b Builder) WithRepositorySpecHandlers(reg RepositorySpecHandlers) Builder { - b.spechandlers = reg - return b -} - -func (b Builder) Bound() (Context, context.Context) { - c := b.New() - return c, context.WithValue(b.getContext(), key, c) -} - -func (b Builder) New(m ...datacontext.BuilderMode) Context { - mode := datacontext.Mode(m...) - ctx := b.getContext() - - if b.credentials == nil { - var ok bool - b.credentials, ok = credentials.DefinedForContext(ctx) - if !ok && mode != datacontext.MODE_SHARED { - b.credentials = credentials.New(mode) - } else { - b.credentials = credentials.FromContext(ctx) - } - } - if b.reposcheme == nil { - switch mode { - case datacontext.MODE_INITIAL: - b.reposcheme = NewRepositoryTypeScheme(nil) - case datacontext.MODE_CONFIGURED: - b.reposcheme = NewRepositoryTypeScheme(nil) - b.reposcheme.AddKnownTypes(DefaultRepositoryTypeScheme) - case datacontext.MODE_EXTENDED: - b.reposcheme = NewRepositoryTypeScheme(nil, DefaultRepositoryTypeScheme) - case datacontext.MODE_DEFAULTED: - fallthrough - case datacontext.MODE_SHARED: - b.reposcheme = DefaultRepositoryTypeScheme - } - } - if b.spechandlers == nil { - switch mode { - case datacontext.MODE_INITIAL: - b.spechandlers = NewRepositorySpecHandlers() - case datacontext.MODE_CONFIGURED: - b.spechandlers = DefaultRepositorySpecHandlers.Copy() - case datacontext.MODE_EXTENDED: - fallthrough - case datacontext.MODE_DEFAULTED: - fallthrough - case datacontext.MODE_SHARED: - b.spechandlers = DefaultRepositorySpecHandlers - } - } - return datacontext.SetupContext(mode, newContext(b.credentials, b.reposcheme, b.spechandlers, b.credentials)) -} diff --git a/pkg/contexts/oci/internal/builder_test.go b/pkg/contexts/oci/internal/builder_test.go deleted file mode 100644 index ea13e485a..000000000 --- a/pkg/contexts/oci/internal/builder_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package internal_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/config" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - local "github.com/open-component-model/ocm/pkg/contexts/oci/internal" -) - -var _ = Describe("builder test", func() { - It("creates local", func() { - ctx := local.Builder{}.New(datacontext.MODE_SHARED) - - Expect(ctx.AttributesContext()).To(BeIdenticalTo(datacontext.DefaultContext)) - Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) - Expect(ctx.RepositoryTypes()).To(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) - - Expect(ctx.ConfigContext().GetId()).To(BeIdenticalTo(config.DefaultContext().GetId())) - - Expect(ctx.CredentialsContext()).To(BeIdenticalTo(credentials.DefaultContext())) - }) - - It("creates defaulted", func() { - ctx := local.Builder{}.New(datacontext.MODE_DEFAULTED) - - Expect(ctx.AttributesContext()).NotTo(BeIdenticalTo(datacontext.DefaultContext)) - Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) - Expect(ctx.RepositoryTypes()).To(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) - - Expect(ctx.ConfigContext()).NotTo(BeIdenticalTo(config.DefaultContext())) - Expect(ctx.ConfigContext().ConfigTypes()).To(BeIdenticalTo(config.DefaultContext().ConfigTypes())) - - Expect(ctx.CredentialsContext()).NotTo(BeIdenticalTo(credentials.DefaultContext())) - Expect(ctx.CredentialsContext().RepositoryTypes()).To(BeIdenticalTo(credentials.DefaultContext().RepositoryTypes())) - }) - - It("creates configured", func() { - ctx := local.Builder{}.New(datacontext.MODE_CONFIGURED) - - Expect(ctx.AttributesContext()).NotTo(BeIdenticalTo(datacontext.DefaultContext)) - Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) - Expect(ctx.RepositoryTypes()).NotTo(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) - Expect(ctx.RepositoryTypes().KnownTypeNames()).To(Equal(local.DefaultRepositoryTypeScheme.KnownTypeNames())) - - Expect(ctx.ConfigContext()).NotTo(BeIdenticalTo(config.DefaultContext())) - Expect(ctx.ConfigContext().ConfigTypes()).NotTo(BeIdenticalTo(config.DefaultContext().ConfigTypes())) - Expect(ctx.ConfigContext().ConfigTypes().KnownTypeNames()).To(Equal(config.DefaultContext().ConfigTypes().KnownTypeNames())) - - Expect(ctx.CredentialsContext()).NotTo(BeIdenticalTo(config.DefaultContext())) - Expect(ctx.CredentialsContext().RepositoryTypes()).NotTo(BeIdenticalTo(credentials.DefaultContext().RepositoryTypes())) - Expect(ctx.CredentialsContext().RepositoryTypes().KnownTypeNames()).To(Equal(credentials.DefaultContext().RepositoryTypes().KnownTypeNames())) - }) - - It("creates iniial", func() { - ctx := local.Builder{}.New(datacontext.MODE_INITIAL) - - Expect(ctx.AttributesContext()).NotTo(BeIdenticalTo(datacontext.DefaultContext)) - Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) - Expect(ctx.RepositoryTypes()).NotTo(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) - Expect(len(ctx.RepositoryTypes().KnownTypeNames())).To(Equal(0)) - - Expect(ctx.ConfigContext()).NotTo(BeIdenticalTo(config.DefaultContext())) - Expect(len(ctx.ConfigContext().ConfigTypes().KnownTypeNames())).To(Equal(0)) - - Expect(ctx.CredentialsContext()).NotTo(BeIdenticalTo(credentials.DefaultContext())) - Expect(len(ctx.CredentialsContext().RepositoryTypes().KnownTypeNames())).To(Equal(0)) - }) -}) diff --git a/pkg/contexts/oci/internal/context.go b/pkg/contexts/oci/internal/context.go deleted file mode 100644 index 36f279b16..000000000 --- a/pkg/contexts/oci/internal/context.go +++ /dev/null @@ -1,195 +0,0 @@ -package internal - -import ( - "context" - "reflect" - "strings" - - "github.com/open-component-model/ocm/pkg/contexts/config" - cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "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/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -const CONTEXT_TYPE = "oci" + datacontext.OCM_CONTEXT_SUFFIX - -const CommonTransportFormat = "CommonTransportFormat" - -type ContextProvider interface { - OCIContext() Context -} - -type Context interface { - datacontext.Context - config.ContextProvider - credentials.ContextProvider - ContextProvider - - RepositorySpecHandlers() RepositorySpecHandlers - MapUniformRepositorySpec(u *UniformRepositorySpec) (RepositorySpec, error) - - RepositoryTypes() RepositoryTypeScheme - - RepositoryForSpec(spec RepositorySpec, creds ...credentials.CredentialsSource) (Repository, error) - RepositoryForConfig(data []byte, unmarshaler runtime.Unmarshaler, creds ...credentials.CredentialsSource) (Repository, error) - RepositorySpecForConfig(data []byte, unmarshaler runtime.Unmarshaler) (RepositorySpec, error) - - GetAlias(name string) RepositorySpec - SetAlias(name string, spec RepositorySpec) -} - -var key = reflect.TypeOf(_context{}) - -// DefaultContext is the default context initialized by init functions. -var DefaultContext = Builder{}.New(datacontext.MODE_SHARED) - -// ForContext returns the Context to use for context.Context. -// This is either an explicit context or the default context. -func ForContext(ctx context.Context) Context { - c, _ := datacontext.ForContextByKey(ctx, key, DefaultContext) - return c.(Context) -} - -func FromProvider(p ContextProvider) Context { - if p == nil { - return nil - } - return p.OCIContext() -} - -func DefinedForContext(ctx context.Context) (Context, bool) { - c, ok := datacontext.ForContextByKey(ctx, key, DefaultContext) - if c != nil { - return c.(Context), ok - } - return nil, ok -} - -//////////////////////////////////////////////////////////////////////////////// - -type _InternalContext = datacontext.InternalContext - -type _context struct { - _InternalContext - updater cfgcpi.Updater - - credentials credentials.Context - - knownRepositoryTypes RepositoryTypeScheme - specHandlers RepositorySpecHandlers - aliases map[string]RepositorySpec -} - -var ( - _ Context = (*_context)(nil) - _ datacontext.ViewCreator[Context] = (*_context)(nil) -) - -// gcWrapper is used as garbage collectable -// wrapper for a context implementation -// to establish a runtime finalizer. -type gcWrapper struct { - datacontext.GCWrapper - *_context -} - -func newView(c *_context, ref ...bool) Context { - if utils.Optional(ref...) { - return datacontext.FinalizedContext[gcWrapper](c) - } - return c -} - -func (w *gcWrapper) SetContext(c *_context) { - w._context = c -} - -func newContext(credctx credentials.Context, reposcheme RepositoryTypeScheme, specHandlers RepositorySpecHandlers, delegates datacontext.Delegates) Context { - c := &_context{ - credentials: datacontext.PersistentContextRef(credctx), - knownRepositoryTypes: reposcheme, - specHandlers: specHandlers, - aliases: map[string]RepositorySpec{}, - } - c._InternalContext = datacontext.NewContextBase(c, CONTEXT_TYPE, key, credctx.ConfigContext().GetAttributes(), delegates) - c.updater = cfgcpi.NewUpdaterForFactory(credctx.ConfigContext(), c.OCIContext) - return newView(c, true) -} - -func (c *_context) CreateView() Context { - return newView(c, true) -} - -func (c *_context) OCIContext() Context { - return newView(c) -} - -func (c *_context) Update() error { - return c.updater.Update() -} - -func (c *_context) AttributesContext() datacontext.AttributesContext { - return c.credentials.AttributesContext() -} - -func (c *_context) ConfigContext() config.Context { - return c.updater.GetContext() -} - -func (c *_context) CredentialsContext() credentials.Context { - return c.credentials -} - -func (c *_context) RepositoryTypes() RepositoryTypeScheme { - return c.knownRepositoryTypes -} - -func (c *_context) RepositorySpecHandlers() RepositorySpecHandlers { - return c.specHandlers -} - -func (c *_context) MapUniformRepositorySpec(u *UniformRepositorySpec) (RepositorySpec, error) { - return c.specHandlers.MapUniformRepositorySpec(c.OCIContext(), u) -} - -func (c *_context) RepositorySpecForConfig(data []byte, unmarshaler runtime.Unmarshaler) (RepositorySpec, error) { - return c.knownRepositoryTypes.Decode(data, unmarshaler) -} - -func (c *_context) RepositoryForSpec(spec RepositorySpec, creds ...credentials.CredentialsSource) (Repository, error) { - cred, err := credentials.CredentialsChain(creds).Credentials(c.CredentialsContext()) - if err != nil { - return nil, err - } - return spec.Repository(c.OCIContext(), cred) -} - -func (c *_context) RepositoryForConfig(data []byte, unmarshaler runtime.Unmarshaler, creds ...credentials.CredentialsSource) (Repository, error) { - spec, err := c.knownRepositoryTypes.Decode(data, unmarshaler) - if err != nil { - return nil, err - } - return c.RepositoryForSpec(spec, creds...) -} - -func (c *_context) GetAlias(name string) RepositorySpec { - err := c.updater.Update() - if err != nil { - return nil - } - c.updater.RLock() - defer c.updater.RUnlock() - spec := c.aliases[name] - if spec == nil && strings.HasSuffix(name, ".alias") { - spec = c.aliases[name[:len(name)-6]] - } - return spec -} - -func (c *_context) SetAlias(name string, spec RepositorySpec) { - c.updater.Lock() - defer c.updater.Unlock() - c.aliases[name] = spec -} diff --git a/pkg/contexts/oci/internal/errors.go b/pkg/contexts/oci/internal/errors.go deleted file mode 100644 index 8b537522b..000000000 --- a/pkg/contexts/oci/internal/errors.go +++ /dev/null @@ -1,19 +0,0 @@ -package internal - -import ( - "fmt" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" -) - -const ( - KIND_OCIARTIFACT = "oci artifact" - KIND_BLOB = blobaccess.KIND_BLOB - KIND_MEDIATYPE = blobaccess.KIND_MEDIATYPE -) - -func ErrUnknownArtifact(name, version string) error { - return errors.ErrUnknown(KIND_OCIARTIFACT, fmt.Sprintf("%s:%s", name, version)) -} diff --git a/pkg/contexts/oci/internal/repository.go b/pkg/contexts/oci/internal/repository.go deleted file mode 100644 index 20335719e..000000000 --- a/pkg/contexts/oci/internal/repository.go +++ /dev/null @@ -1,157 +0,0 @@ -package internal - -import ( - "io" - - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/refmgmt/resource" -) - -type RepositoryImpl interface { - GetSpecification() RepositorySpec - GetContext() Context - - NamespaceLister() NamespaceLister - ExistsArtifact(name string, ref string) (bool, error) - LookupArtifact(name string, ref string) (ArtifactAccess, error) - LookupNamespace(name string) (NamespaceAccess, error) - - io.Closer -} - -type Repository interface { - resource.ResourceView[Repository] - - RepositoryImpl -} - -// ConsumerIdentityProvider is an optional interface for repositories -// to tell about their credential requests. -type ConsumerIdentityProvider = credentials.ConsumerIdentityProvider - -type RepositorySource interface { - GetRepository() Repository -} - -type ( - BlobAccess = blobaccess.BlobAccess - DataAccess = blobaccess.DataAccess -) - -type BlobSource interface { - GetBlobData(digest digest.Digest) (int64, DataAccess, error) -} - -type BlobSink interface { - AddBlob(BlobAccess) error -} - -type ArtifactSink interface { - AddBlob(BlobAccess) error - AddArtifact(a Artifact, tags ...string) (BlobAccess, error) - AddTags(digest digest.Digest, tags ...string) error -} - -type ArtifactSource interface { - GetArtifact(version string) (ArtifactAccess, error) - GetBlobData(digest digest.Digest) (int64, DataAccess, error) -} - -type NamespaceAccessImpl interface { - ArtifactSource - ArtifactSink - GetNamespace() string - ListTags() ([]string, error) - - HasArtifact(vers string) (bool, error) - - NewArtifact(...Artifact) (ArtifactAccess, error) - io.Closer -} - -type NamespaceAccess interface { - resource.ResourceView[NamespaceAccess] - - NamespaceAccessImpl -} - -type Artifact artdesc.ArtifactDescriptor - -type ArtifactAccessImpl interface { - Artifact - BlobSource - BlobSink - - GetDescriptor() *artdesc.Artifact - GetBlob(digest digest.Digest) (BlobAccess, error) - - GetArtifact(digest digest.Digest) (ArtifactAccess, error) - AddBlob(BlobAccess) error - - AddArtifact(Artifact, *artdesc.Platform) (BlobAccess, error) - AddLayer(BlobAccess, *artdesc.Descriptor) (int, error) - - NewArtifact(...Artifact) (ArtifactAccess, error) - - io.Closer -} - -type ArtifactAccessSlaves interface { - ManifestAccess() ManifestAccess - IndexAccess() IndexAccess -} - -type ArtifactAccess interface { - resource.ResourceView[ArtifactAccess] - - ArtifactAccessImpl - ArtifactAccessSlaves -} - -type ManifestAccess interface { - Artifact - - GetDescriptor() *artdesc.Manifest - GetBlobDescriptor(digest digest.Digest) *artdesc.Descriptor - GetConfigBlob() (BlobAccess, error) - GetBlob(digest digest.Digest) (BlobAccess, error) - - AddBlob(BlobAccess) error - AddLayer(BlobAccess, *artdesc.Descriptor) (int, error) - SetConfigBlob(blob BlobAccess, d *artdesc.Descriptor) error -} - -type IndexAccess interface { - Artifact - - GetDescriptor() *artdesc.Index - GetBlobDescriptor(digest digest.Digest) *artdesc.Descriptor - GetBlob(digest digest.Digest) (BlobAccess, error) - - GetArtifact(digest digest.Digest) (ArtifactAccess, error) - /* - GetIndex(digest digest.Digest) (IndexAccess, error) - GetManifest(digest digest.Digest) (ManifestAccess, error) - */ - - AddBlob(BlobAccess) error - AddArtifact(Artifact, *artdesc.Platform) (BlobAccess, error) -} - -// NamespaceLister provides the optional repository list functionality of -// a repository. -type NamespaceLister interface { - // NumNamespaces returns the number of namespaces found for a prefix - // If the given prefix does not end with a /, a repository with the - // prefix name is included - NumNamespaces(prefix string) (int, error) - - // GetNamespaces returns the name of namespaces found for a prefix - // If the given prefix does not end with a /, a repository with the - // prefix name is included - GetNamespaces(prefix string, closure bool) ([]string, error) -} diff --git a/pkg/contexts/oci/internal/repotypes.go b/pkg/contexts/oci/internal/repotypes.go deleted file mode 100644 index 550e0d4fd..000000000 --- a/pkg/contexts/oci/internal/repotypes.go +++ /dev/null @@ -1,160 +0,0 @@ -package internal - -import ( - "encoding/json" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/generics" - "github.com/modern-go/reflect2" - - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -type RepositoryType interface { - runtime.VersionedTypedObjectType[RepositorySpec] -} - -type IntermediateRepositorySpecAspect interface { - IsIntermediate() bool -} - -type RepositorySpec interface { - runtime.VersionedTypedObject - - Name() string - UniformRepositorySpec() *UniformRepositorySpec - Repository(Context, credentials.Credentials) (Repository, error) -} - -type ( - RepositorySpecDecoder = runtime.TypedObjectDecoder[RepositorySpec] - RepositoryTypeProvider = runtime.KnownTypesProvider[RepositorySpec, RepositoryType] -) - -type RepositoryTypeScheme interface { - runtime.TypeScheme[RepositorySpec, RepositoryType] -} - -type _Scheme = runtime.TypeScheme[RepositorySpec, RepositoryType] - -type repositoryTypeScheme struct { - _Scheme -} - -func NewRepositoryTypeScheme(defaultDecoder RepositorySpecDecoder, base ...RepositoryTypeScheme) RepositoryTypeScheme { - scheme := runtime.MustNewDefaultTypeScheme[RepositorySpec, RepositoryType](&UnknownRepositorySpec{}, true, defaultDecoder, utils.Optional(base...)) - return &repositoryTypeScheme{scheme} -} - -func NewStrictRepositoryTypeScheme(base ...RepositoryTypeScheme) runtime.VersionedTypeRegistry[RepositorySpec, RepositoryType] { - scheme := runtime.MustNewDefaultTypeScheme[RepositorySpec, RepositoryType](nil, false, nil, utils.Optional(base...)) - return &repositoryTypeScheme{scheme} -} - -func (t *repositoryTypeScheme) KnownTypes() runtime.KnownTypes[RepositorySpec, RepositoryType] { - return t._Scheme.KnownTypes() -} - -// DefaultRepositoryTypeScheme contains all globally known access serializer. -var DefaultRepositoryTypeScheme = NewRepositoryTypeScheme(nil) - -func RegisterRepositoryType(atype RepositoryType) { - DefaultRepositoryTypeScheme.Register(atype) -} - -func CreateRepositorySpec(t runtime.TypedObject) (RepositorySpec, error) { - return DefaultRepositoryTypeScheme.Convert(t) -} - -//////////////////////////////////////////////////////////////////////////////// - -type UnknownRepositorySpec struct { - runtime.UnstructuredVersionedTypedObject `json:",inline"` -} - -var ( - _ RepositorySpec = &UnknownRepositorySpec{} - _ runtime.Unknown = &UnknownRepositorySpec{} -) - -func (r *UnknownRepositorySpec) IsUnknown() bool { - return true -} - -func (r *UnknownRepositorySpec) Name() string { - return "unknown-" + r.GetKind() -} - -func (r *UnknownRepositorySpec) UniformRepositorySpec() *UniformRepositorySpec { - return UniformRepositorySpecForUnstructured(&r.UnstructuredVersionedTypedObject) -} - -func (r *UnknownRepositorySpec) Repository(Context, credentials.Credentials) (Repository, error) { - return nil, errors.ErrUnknown("repository type", r.GetType()) -} - -//////////////////////////////////////////////////////////////////////////////// - -type GenericRepositorySpec struct { - runtime.UnstructuredVersionedTypedObject `json:",inline"` -} - -var _ RepositorySpec = &GenericRepositorySpec{} - -func ToGenericRepositorySpec(spec RepositorySpec) (*GenericRepositorySpec, error) { - if reflect2.IsNil(spec) { - return nil, nil - } - if g, ok := spec.(*GenericRepositorySpec); ok { - return g, nil - } - data, err := json.Marshal(spec) - if err != nil { - return nil, err - } - return newGenericRepositorySpec(data, runtime.DefaultJSONEncoding) -} - -func NewGenericRepositorySpec(data []byte, unmarshaler runtime.Unmarshaler) (RepositorySpec, error) { - return generics.CastPointerR[RepositorySpec](newGenericRepositorySpec(data, unmarshaler)) -} - -func newGenericRepositorySpec(data []byte, unmarshaler runtime.Unmarshaler) (*GenericRepositorySpec, error) { - unstr := &runtime.UnstructuredVersionedTypedObject{} - if unmarshaler == nil { - unmarshaler = runtime.DefaultYAMLEncoding - } - err := unmarshaler.Unmarshal(data, unstr) - if err != nil { - return nil, err - } - return &GenericRepositorySpec{*unstr}, nil -} - -func (s *GenericRepositorySpec) Name() string { - return "generic-" + s.GetKind() -} - -func (s *GenericRepositorySpec) UniformRepositorySpec() *UniformRepositorySpec { - return UniformRepositorySpecForUnstructured(&s.UnstructuredVersionedTypedObject) -} - -func (s *GenericRepositorySpec) Evaluate(ctx Context) (RepositorySpec, error) { - raw, err := s.GetRaw() - if err != nil { - return nil, err - } - return ctx.RepositoryTypes().Decode(raw, runtime.DefaultJSONEncoding) -} - -func (s *GenericRepositorySpec) Repository(ctx Context, creds credentials.Credentials) (Repository, error) { - spec, err := s.Evaluate(ctx) - if err != nil { - return nil, err - } - return spec.Repository(ctx, creds) -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/pkg/contexts/oci/internal/uniform.go b/pkg/contexts/oci/internal/uniform.go deleted file mode 100644 index d3ab3e296..000000000 --- a/pkg/contexts/oci/internal/uniform.go +++ /dev/null @@ -1,233 +0,0 @@ -package internal - -import ( - "encoding/json" - "fmt" - "net/url" - "strings" - "sync" - - "github.com/containerd/containerd/reference" - "github.com/mandelsoft/goutils/errors" - "github.com/sirupsen/logrus" - "golang.org/x/exp/slices" - - "github.com/open-component-model/ocm/pkg/contexts/oci/grammar" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - dockerHubDomain = "docker.io" - dockerHubLegacyDomain = "index.docker.io" -) - -// UniformRepositorySpec is a generic specification of the repository -// for handling as part of standard references. -type UniformRepositorySpec struct { - // Type - Type string `json:"type,omitempty"` - // Scheme - Scheme string `json:"scheme,omitempty"` - // Host is the hostname of an oci ref. - Host string `json:"host,omitempty"` - // Info is the file path used to host ctf component versions - Info string `json:"filePath,omitempty"` - - // CreateIfMissing indicates whether a file based or dynamic repo should be created if it does not exist - CreateIfMissing bool `json:"createIfMissing,omitempty"` - // TypeHint should be set if CreateIfMissing is true to help to decide what kind of repo to create - TypeHint string `json:"typeHint,omitempty"` -} - -// CredHost fallback to legacy docker domain if applicable -// this is how containerd translates the old domain for DockerHub to the new one, taken from containerd/reference/docker/reference.go:674. -func (u *UniformRepositorySpec) CredHost() string { - if u.Host == dockerHubDomain { - return dockerHubLegacyDomain - } - return u.Host -} - -func (u *UniformRepositorySpec) HostPort() (string, string) { - i := strings.Index(u.Host, ":") - if i < 0 { - return u.Host, "" - } - return u.Host[:i], u.Host[i+1:] -} - -// ComposeRef joins the actual repository spec and a given artifact spec. -func (u *UniformRepositorySpec) ComposeRef(art string) string { - if art == "" { - return u.String() - } - sep := "/" - if u.Info != "" { - sep = "//" - } - return fmt.Sprintf("%s%s%s", u.String(), sep, art) -} - -func (u *UniformRepositorySpec) RepositoryRef() string { - t := u.Type - if t != "" { - t += "::" - } - if u.Info != "" { - return fmt.Sprintf("%s%s", t, u.Info) - } - if u.Scheme == "" { - return fmt.Sprintf("%s%s", t, u.Host) - } - return fmt.Sprintf("%s%s://%s", t, u.Scheme, u.Host) -} - -func (u *UniformRepositorySpec) SetType(typ string) { - t, _ := grammar.SplitTypeSpec(typ) - u.Type = t - u.TypeHint = typ -} - -func (u *UniformRepositorySpec) String() string { - return u.RepositoryRef() -} - -func UniformRepositorySpecForHostURL(typ string, host string) *UniformRepositorySpec { - s := "" - h := host - var parsed *url.URL - ref, err := reference.Parse(host) - if err == nil { - parsed, err = url.Parse("https://" + ref.Locator) - } else { - parsed, err = url.Parse(host) - } - if err == nil { - s = parsed.Scheme - h = parsed.Host - } - u := &UniformRepositorySpec{ - Type: typ, - Scheme: s, - Host: h, - } - return u -} - -func UniformRepositorySpecForUnstructured(un *runtime.UnstructuredVersionedTypedObject) *UniformRepositorySpec { - m := un.Object.FlatCopy() - delete(m, runtime.ATTR_TYPE) - - d, err := json.Marshal(m) - if err != nil { - logrus.Error(err) - } - - return &UniformRepositorySpec{Type: un.Type, Info: string(d)} -} - -type RepositorySpecHandler interface { - MapReference(ctx Context, u *UniformRepositorySpec) (RepositorySpec, error) -} - -type RepositorySpecHandlers interface { - Register(hdlr RepositorySpecHandler, types ...string) - Copy() RepositorySpecHandlers - MapUniformRepositorySpec(ctx Context, u *UniformRepositorySpec) (RepositorySpec, error) -} - -var DefaultRepositorySpecHandlers = NewRepositorySpecHandlers() - -func RegisterRepositorySpecHandler(hdlr RepositorySpecHandler, types ...string) { - DefaultRepositorySpecHandlers.Register(hdlr, types...) -} - -type specHandlers struct { - lock sync.RWMutex - handlers map[string][]RepositorySpecHandler -} - -func NewRepositorySpecHandlers() RepositorySpecHandlers { - return &specHandlers{handlers: map[string][]RepositorySpecHandler{}} -} - -func (s *specHandlers) Register(hdlr RepositorySpecHandler, types ...string) { - s.lock.Lock() - defer s.lock.Unlock() - - if hdlr != nil { - for _, typ := range types { - s.handlers[typ] = append(s.handlers[typ], hdlr) - } - } -} - -func (s *specHandlers) Copy() RepositorySpecHandlers { - s.lock.RLock() - defer s.lock.RUnlock() - - n := NewRepositorySpecHandlers().(*specHandlers) - for typ, hdlrs := range s.handlers { - n.handlers[typ] = slices.Clone(hdlrs) - } - return n -} - -func (s *specHandlers) MapUniformRepositorySpec(ctx Context, u *UniformRepositorySpec) (RepositorySpec, error) { - var err error - s.lock.RLock() - defer s.lock.RUnlock() - deferr := errors.ErrNotSupported("uniform repository ref", u.String()) - - if u.Info != "" && string(u.Info[0]) == "{" && u.Host == "" && u.Scheme == "" { - data, err := runtime.CompleteSpecWithType(u.Type, []byte(u.Info)) - if err != nil { - return nil, err - } - return ctx.RepositorySpecForConfig(data, runtime.DefaultJSONEncoding) - } - - if u.Type == "" { - if u.Info != "" { - spec := ctx.GetAlias(u.Info) - if spec != nil { - return spec, nil - } - deferr = errors.ErrUnknown("repository", u.Info) - } - if u.Host != "" { - spec := ctx.GetAlias(u.Host) - if spec != nil { - return spec, nil - } - deferr = errors.ErrUnknown("repository", u.Host) - } - } - for _, h := range s.handlers[u.Type] { - spec, err := h.MapReference(ctx, u) - if err != nil || spec != nil { - return spec, err - } - } - if u.Info != "" { - spec := &runtime.UnstructuredVersionedTypedObject{} - err = runtime.DefaultJSONEncoding.Unmarshal([]byte(u.Info), spec) - if err == nil { - if spec.GetType() == spec.GetKind() && spec.GetVersion() == "v1" { // only type set, use it as version - spec.SetType(u.Type + runtime.VersionSeparator + spec.GetType()) - } - if spec.GetKind() != u.Type { - return nil, errors.ErrInvalid() - } - return ctx.RepositoryTypes().Convert(spec) - } - } - for _, h := range s.handlers["*"] { - spec, err := h.MapReference(ctx, u) - if err != nil || spec != nil { - return spec, err - } - } - - return nil, deferr -} diff --git a/pkg/contexts/oci/ociutils/handler.go b/pkg/contexts/oci/ociutils/handler.go deleted file mode 100644 index 05eec8140..000000000 --- a/pkg/contexts/oci/ociutils/handler.go +++ /dev/null @@ -1,30 +0,0 @@ -package ociutils - -import ( - "sync" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" -) - -type InfoHandler interface { - Description(pr common.Printer, m cpi.ManifestAccess, config []byte) - Info(m cpi.ManifestAccess, config []byte) interface{} -} - -var ( - lock sync.Mutex - handlers = map[string]InfoHandler{} -) - -func RegisterInfoHandler(mime string, h InfoHandler) { - lock.Lock() - defer lock.Unlock() - handlers[mime] = h -} - -func getHandler(mime string) InfoHandler { - lock.Lock() - defer lock.Unlock() - return handlers[mime] -} diff --git a/pkg/contexts/oci/ociutils/helm/art_test.go b/pkg/contexts/oci/ociutils/helm/art_test.go deleted file mode 100644 index 2d497254d..000000000 --- a/pkg/contexts/oci/ociutils/helm/art_test.go +++ /dev/null @@ -1,115 +0,0 @@ -package helm_test - -import ( - "encoding/json" - "os" - "sort" - "strings" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/mandelsoft/vfs/pkg/osfs" - "github.com/mandelsoft/vfs/pkg/vfs" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common/accessobj" - "github.com/open-component-model/ocm/pkg/contexts/oci/ociutils/helm" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/env" - "github.com/open-component-model/ocm/pkg/env/builder" - "github.com/open-component-model/ocm/pkg/helm/loader" -) - -type Files []*chart.File - -var _ sort.Interface = (Files)(nil) - -func (f Files) Len() int { - return len(f) -} - -func (f Files) Less(i, j int) bool { - return strings.Compare(f[i].Name, f[j].Name) < 0 -} - -func (f Files) Swap(i, j int) { - f[i], f[j] = f[j], f[i] -} - -func norm(chart *chart.Chart) *chart.Chart { - dir, err := os.MkdirTemp("", "helmchart-") - Expect(err).To(Succeed()) - defer os.RemoveAll(dir) - - path, err := chartutil.Save(chart, dir) - Expect(err).To(Succeed()) - chart, err = loader.Load(path, osfs.New()) - Expect(err).To(Succeed()) - // sort.Sort(Files(chart.Raw)) - // sort.Sort(Files(chart.Files)) - // sort.Sort(Files(chart.Templates)) - return chart -} - -func get(blob blobaccess.DataAccess, expected []byte) []byte { - data, err := blob.Get() - ExpectWithOffset(1, err).To(Succeed()) - if expected != nil { - ExpectWithOffset(1, string(data)).To(Equal(string(expected))) - } - return data -} - -var _ = Describe("art parsing", func() { - It("succeeds", func() { - env := builder.NewBuilder(env.TestData()) - defer vfs.Cleanup(env) - - prov, err := env.ReadFile("/testdata/testchart.prov") - Expect(err).To(Succeed()) - chart, err := loader.Load("/testdata/testchart", env) - Expect(err).To(Succeed()) - meta, err := json.Marshal(chart.Metadata) - Expect(err).To(Succeed()) - - artblob, err := helm.SynthesizeArtifactBlob(loader.VFSLoader("/testdata/testchart", env)) - Expect(err).To(Succeed()) - defer Close(artblob) - set, err := artifactset.OpenFromBlob(accessobj.ACC_READONLY, artblob) - Expect(err).To(Succeed()) - defer Close(set) - art, err := set.GetArtifact(set.GetMain().String()) - Expect(err).To(Succeed()) - defer Close(art) - - ma := art.ManifestAccess() - m := ma.GetDescriptor() - Expect(len(m.Layers)).To(Equal(2)) - - config, err := art.ManifestAccess().GetConfigBlob() - Expect(err).To(Succeed()) - get(config, meta) - - _, data, err := set.GetBlobData(m.Layers[1].Digest) - Expect(err).To(Succeed()) - get(data, prov) - - _, data, err = set.GetBlobData(m.Layers[0].Digest) - Expect(err).To(Succeed()) - r, err := data.Reader() - Expect(err).To(Succeed()) - - blob, err := ma.GetBlob(m.Layers[1].Digest) - Expect(err).To(Succeed()) - get(blob, prov) - - // unfortunately charts are not directly comparable, because of the order in the arrays AND the modified Chart.yaml - found, err := loader.LoadArchive(r) - Expect(err).To(Succeed()) - Expect(norm(found)).To(Equal(norm(chart))) - }) -}) diff --git a/pkg/contexts/oci/ociutils/helm/artifact.go b/pkg/contexts/oci/ociutils/helm/artifact.go deleted file mode 100644 index d012c0162..000000000 --- a/pkg/contexts/oci/ociutils/helm/artifact.go +++ /dev/null @@ -1,109 +0,0 @@ -package helm - -import ( - "encoding/json" - "fmt" - "os" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/vfs/pkg/osfs" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/registry" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/blobaccess/file" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/helm/loader" -) - -func SynthesizeArtifactBlob(loader loader.Loader) (artifactset.ArtifactBlob, error) { - return artifactset.SythesizeArtifactSet(func(set *artifactset.ArtifactSet) (string, error) { - chart, blob, err := TransferAsArtifact(loader, set) - if err != nil { - return "", fmt.Errorf("unable to transfer as artifact: %w", err) - } - - if chart.Metadata.Version != "" { - err = set.AddTags(blob.Digest, chart.Metadata.Version) - if err != nil { - return "", fmt.Errorf("unable to add tag: %w", err) - } - } - - set.Annotate(artifactset.MAINARTIFACT_ANNOTATION, blob.Digest.String()) - - return artdesc.MediaTypeImageManifest, nil - }) -} - -func TransferAsArtifact(loader loader.Loader, ns oci.NamespaceAccess) (*chart.Chart, *artdesc.Descriptor, error) { - chart, err := loader.Chart() - if err != nil { - return nil, nil, err - } - err = chart.Validate() - if err != nil { - return nil, nil, errors.ErrInvalidWrap(err, "helm chart") - } - - provData, err := loader.Provenance() - if err != nil { - return nil, nil, err - } - - var blob blobaccess.BlobAccess - blob, err = loader.ChartArchive() - if err != nil { - return nil, nil, err - } - if blob == nil { - dir, err := os.MkdirTemp("", "helmchart-") - if err != nil { - return chart, nil, errors.Wrapf(err, "cannot create temporary directory for helm chart") - } - defer os.RemoveAll(dir) - path, err := chartutil.Save(chart, dir) - if err != nil { - return chart, nil, err - } - blob = file.BlobAccess(registry.ChartLayerMediaType, path, osfs.New()) - } else { - defer blob.Close() - } - meta := chart.Metadata - - configData, err := json.Marshal(meta) - if err != nil { - return chart, nil, err - } - - art, err := ns.NewArtifact() - if err != nil { - return chart, nil, err - } - defer art.Close() - m := art.ManifestAccess() - - err = m.SetConfigBlob(blobaccess.ForData(registry.ConfigMediaType, configData), nil) - if err != nil { - return chart, nil, err - } - _, err = m.AddLayer(blob, nil) - if err != nil { - return chart, nil, err - } - if provData != nil { - _, err = m.AddLayer(blobaccess.ForData(registry.ProvLayerMediaType, provData), nil) - if err != nil { - return chart, nil, err - } - } - blob, err = ns.AddArtifact(art) - if err != nil { - return chart, nil, err - } - return chart, artdesc.DefaultBlobDescriptor(blob), err -} diff --git a/pkg/contexts/oci/ociutils/info.go b/pkg/contexts/oci/ociutils/info.go deleted file mode 100644 index f8dfdb175..000000000 --- a/pkg/contexts/oci/ociutils/info.go +++ /dev/null @@ -1,212 +0,0 @@ -package ociutils - -import ( - "archive/tar" - "encoding/json" - "errors" - "fmt" - "io" - - "github.com/opencontainers/go-digest" - "sigs.k8s.io/yaml" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/common/compression" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/mime" -) - -type BlobInfo struct { - Error string `json:"error,omitempty"` - Unparsed string `json:"unparsed,omitempty"` - Content json.RawMessage `json:"content,omitempty"` - Type string `json:"type,omitempty"` - Digest digest.Digest `json:"digest,omitempty"` - Size int64 `json:"size,omitempty"` - Info interface{} `json:"info,omitempty"` -} -type ArtifactInfo struct { - Digest digest.Digest `json:"digest"` - Type string `json:"type"` - Descriptor interface{} `json:"descriptor"` - Config *BlobInfo `json:"config,omitempty"` - Layers []*BlobInfo `json:"layers,omitempty"` - Manifests []*BlobInfo `json:"manifests,omitempty"` -} - -func GetArtifactInfo(art cpi.ArtifactAccess, layerFiles bool) *ArtifactInfo { - if art.IsManifest() { - return GetManifestInfo(art.ManifestAccess(), layerFiles) - } - if art.IsIndex() { - return GetIndexInfo(art.IndexAccess(), layerFiles) - } - return &ArtifactInfo{Type: "unspecific"} -} - -func GetManifestInfo(m cpi.ManifestAccess, layerFiles bool) *ArtifactInfo { - info := &ArtifactInfo{ - Type: artdesc.MediaTypeImageManifest, - Descriptor: m.GetDescriptor(), - } - b, err := m.Blob() - if err == nil { - info.Digest = b.Digest() - } - man := m.GetDescriptor() - cfg := &BlobInfo{ - Content: nil, - Type: man.Config.MediaType, - Digest: man.Config.Digest, - Size: man.Config.Size, - } - info.Config = cfg - - config, err := blobaccess.BlobData(m.GetBlob(man.Config.Digest)) - if err != nil { - cfg.Error = "error getting config blob: " + err.Error() - } else { - cfg.Content = json.RawMessage(config) - } - h := getHandler(man.Config.MediaType) - - if h != nil { - pr, buf := common.NewBufferedPrinter() - h.Description(pr, m, config) - cfg.Info = buf.String() - } - for _, l := range man.Layers { - blobinfo := &BlobInfo{ - Type: l.MediaType, - Digest: l.Digest, - Size: l.Size, - } - blob, err := m.GetBlob(l.Digest) - if err != nil { - blobinfo.Error = "error getting blob: " + err.Error() - } else { - blobinfo.Info = GetLayerInfo(blob, layerFiles) - } - info.Layers = append(info.Layers, blobinfo) - } - return info -} - -type LayerInfo struct { - Description string `json:"description,omitempty"` - Error string `json:"error,omitempty"` - Unparsed string `json:"unparsed,omitempty"` - Content interface{} `json:"content,omitempty"` -} - -func GetLayerInfo(blob blobaccess.BlobAccess, layerFiles bool) *LayerInfo { - info := &LayerInfo{} - - if mime.IsJSON(blob.MimeType()) { - info.Description = "json document" - data, err := blob.Get() - if err != nil { - info.Error = "cannot read blob: " + err.Error() - return info - } - var j interface{} - err = json.Unmarshal(data, &j) - if err != nil { - if len(data) < 10000 { - info.Unparsed = string(data) - } - info.Error = "invalid json: " + err.Error() - return info - } - info.Content = j - return info - } - if mime.IsYAML(blob.MimeType()) { - info.Description = "yaml document" - data, err := blob.Get() - if err != nil { - info.Error = "cannot read blob: " + err.Error() - return info - } - var j interface{} - err = yaml.Unmarshal(data, &j) - if err != nil { - if len(data) < 10000 { - info.Unparsed = string(data) - } - info.Error = "invalid yaml: " + err.Error() - return info - } - info.Content = j - return info - } - if !layerFiles { - return nil - } - reader, err := blob.Reader() - if err != nil { - info.Error = "cannot read blob: " + err.Error() - return info - } - defer reader.Close() - reader, _, err = compression.AutoDecompress(reader) - if err != nil { - info.Error = "cannot decompress blob: " + err.Error() - return info - } - var files []string - tr := tar.NewReader(reader) - for { - header, err := tr.Next() - if err != nil { - if errors.Is(err, io.EOF) { - info.Content = files - return info - } - if len(files) == 0 { - info.Description = "no tar" - return info - } - info.Error = fmt.Sprintf("tar error: %s", err) - return info - } - if len(files) == 0 { - info.Description = "tar file" - } - - switch header.Typeflag { - case tar.TypeDir: - files = append(files, fmt.Sprintf("dir: %s\n", header.Name)) - case tar.TypeReg: - files = append(files, fmt.Sprintf("file: %s\n", header.Name)) - } - } -} - -func GetIndexInfo(i cpi.IndexAccess, layerFiles bool) *ArtifactInfo { - info := &ArtifactInfo{ - Type: artdesc.MediaTypeImageIndex, - Descriptor: i.GetDescriptor(), - } - b, err := i.Blob() - if err == nil { - info.Digest = b.Digest() - } - for _, l := range i.GetDescriptor().Manifests { - blobinfo := &BlobInfo{ - Type: l.MediaType, - Digest: l.Digest, - Size: l.Size, - } - a, err := i.GetArtifact(l.Digest) - if err != nil { - blobinfo.Error = fmt.Sprintf("cannot get artifact: %s\n", err) - } else { - blobinfo.Info = GetArtifactInfo(a, layerFiles) - } - info.Layers = append(info.Layers, blobinfo) - } - return info -} diff --git a/pkg/contexts/oci/ref.go b/pkg/contexts/oci/ref.go deleted file mode 100644 index 553af5a8a..000000000 --- a/pkg/contexts/oci/ref.go +++ /dev/null @@ -1,306 +0,0 @@ -package oci - -import ( - "fmt" - "strings" - - "github.com/mandelsoft/goutils/errors" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/contexts/oci/grammar" -) - -// to find a suitable secret for images on Docker Hub, we need its two domains to do matching. -const ( - dockerHubDomain = "docker.io" - dockerHubLegacyDomain = "index.docker.io" - - KIND_OCI_REFERENCE = "oci reference" - KIND_ARETEFACT_REFERENCE = "artifact reference" -) - -// ParseRepo parses a standard oci repository reference into an internal representation. -func ParseRepo(ref string) (UniformRepositorySpec, error) { - create := false - if strings.HasPrefix(ref, "+") { - create = true - ref = ref[1:] - } - uspec := UniformRepositorySpec{} - match := grammar.AnchoredRegistryRegexp.FindSubmatch([]byte(ref)) - if match == nil { - match = grammar.AnchoredGenericRegistryRegexp.FindSubmatch([]byte(ref)) - if match == nil { - return uspec, errors.ErrInvalid(KIND_OCI_REFERENCE, ref) - } - uspec.SetType(string(match[1])) - uspec.Info = string(match[2]) - uspec.CreateIfMissing = create - return uspec, nil - } - uspec.SetType(string(match[1])) - uspec.Scheme = string(match[2]) - uspec.Host = string(match[3]) - uspec.CreateIfMissing = create - return uspec, nil -} - -// RefSpec is a go internal representation of an oci reference. -type RefSpec struct { - UniformRepositorySpec `json:",inline"` - ArtSpec `json:",inline"` -} - -func pointer(b []byte) *string { - if len(b) == 0 { - return nil - } - s := string(b) - return &s -} - -func dig(b []byte) *digest.Digest { - if len(b) == 0 { - return nil - } - s := digest.Digest(b) - return &s -} - -// ParseRef parses a oci reference into a internal representation. -func ParseRef(ref string) (RefSpec, error) { - create := false - if strings.HasPrefix(ref, "+") { - create = true - ref = ref[1:] - } - - spec := RefSpec{UniformRepositorySpec: UniformRepositorySpec{CreateIfMissing: create}} - match := grammar.AnchoredTypedSchemedHostPortArtifactRegexp.FindSubmatch([]byte(ref)) - if match != nil { - spec.SetType(string(match[1])) - spec.Scheme = string(match[2]) - spec.Host = string(match[3]) - spec.Repository = string(match[4]) - spec.Tag = pointer(match[5]) - spec.Digest = dig(match[6]) - return spec, nil - } - - match = grammar.AnchoredTypedOptSchemedReqHostReqPortArtifactRegexp.FindSubmatch([]byte(ref)) - if match != nil { - spec.SetType(string(match[1])) - spec.Scheme = string(match[2]) - spec.Host = string(match[3]) - spec.Repository = string(match[4]) - spec.Tag = pointer(match[5]) - spec.Digest = dig(match[6]) - return spec, nil - } - match = grammar.FileReferenceRegexp.FindSubmatch([]byte(ref)) - if match != nil { - spec.SetType(string(match[1])) - spec.Info = string(match[2]) - spec.Repository = string(match[3]) - spec.Tag = pointer(match[4]) - spec.Digest = dig(match[5]) - return spec, nil - } - match = grammar.DockerLibraryReferenceRegexp.FindSubmatch([]byte(ref)) - if match != nil { - spec.Host = dockerHubDomain - spec.Repository = "library" + grammar.RepositorySeparator + string(match[1]) - spec.Tag = pointer(match[2]) - spec.Digest = dig(match[3]) - return spec, nil - } - match = grammar.DockerReferenceRegexp.FindSubmatch([]byte(ref)) - if match != nil { - spec.Host = dockerHubDomain - spec.Repository = string(match[1]) - spec.Tag = pointer(match[2]) - spec.Digest = dig(match[3]) - return spec, nil - } - match = grammar.ReferenceRegexp.FindSubmatch([]byte(ref)) - if match != nil { - spec.Scheme = string(match[1]) - spec.Host = string(match[2]) - spec.Repository = string(match[3]) - spec.Tag = pointer(match[4]) - spec.Digest = dig(match[5]) - return spec, nil - } - match = grammar.TypedReferenceRegexp.FindSubmatch([]byte(ref)) - if match != nil { - spec.SetType(string(match[1])) - spec.Scheme = string(match[2]) - spec.Host = string(match[3]) - spec.Repository = string(match[4]) - spec.Tag = pointer(match[5]) - spec.Digest = dig(match[6]) - return spec, nil - } - match = grammar.TypedURIRegexp.FindSubmatch([]byte(ref)) - if match != nil { - spec.SetType(string(match[1])) - spec.Scheme = string(match[2]) - spec.Host = string(match[3]) - spec.Repository = string(match[4]) - spec.Tag = pointer(match[5]) - spec.Digest = dig(match[6]) - return spec, nil - } - match = grammar.TypedGenericReferenceRegexp.FindSubmatch([]byte(ref)) - if match != nil { - spec.SetType(string(match[1])) - spec.Info = string(match[2]) - spec.Repository = string(match[3]) - spec.Tag = pointer(match[4]) - spec.Digest = dig(match[5]) - return spec, nil - } - match = grammar.AnchoredRegistryRegexp.FindSubmatch([]byte(ref)) - if match != nil { - spec.SetType(string(match[1])) - spec.Info = string(match[2]) - spec.Repository = string(match[3]) - spec.Tag = pointer(match[4]) - spec.Digest = dig(match[5]) - return spec, nil - } - - match = grammar.AnchoredGenericRegistryRegexp.FindSubmatch([]byte(ref)) - if match != nil { - spec.SetType(string(match[1])) - spec.Info = string(match[2]) - - match = grammar.ErrorCheckRegexp.FindSubmatch([]byte(ref)) - if match != nil { - if len(match[3]) != 0 || len(match[4]) != 0 { - return RefSpec{}, errors.ErrInvalid(KIND_OCI_REFERENCE, ref) - } - } - return spec, nil - } - return RefSpec{}, errors.ErrInvalid(KIND_OCI_REFERENCE, ref) -} - -func (r *RefSpec) Name() string { - return r.UniformRepositorySpec.ComposeRef(r.Repository) -} - -func (r *RefSpec) String() string { - art := r.Repository - if r.Tag != nil { - art = fmt.Sprintf("%s:%s", art, *r.Tag) - } - if r.Digest != nil { - art = fmt.Sprintf("%s@%s", art, r.Digest.String()) - } - return r.UniformRepositorySpec.ComposeRef(art) -} - -// CredHost fallback to legacy docker domain if applicable -// this is how containerd translates the old domain for DockerHub to the new one, taken from containerd/reference/docker/reference.go:674. -func (r *RefSpec) CredHost() string { - if r.Host == dockerHubDomain { - return dockerHubLegacyDomain - } - return r.Host -} - -func (r RefSpec) DeepCopy() RefSpec { - if r.Tag != nil { - tag := *r.Tag - r.Tag = &tag - } - if r.Digest != nil { - dig := *r.Digest - r.Digest = &dig - } - return r -} - -//////////////////////////////////////////////////////////////////////////////// - -func ParseArt(art string) (ArtSpec, error) { - match := grammar.AnchoredArtifactVersionRegexp.FindSubmatch([]byte(art)) - - if match == nil { - return ArtSpec{}, errors.ErrInvalid(KIND_ARETEFACT_REFERENCE, art) - } - var tag *string - var dig *digest.Digest - - if match[2] != nil { - t := string(match[2]) - tag = &t - } - if match[3] != nil { - t := string(match[3]) - d, err := digest.Parse(t) - if err != nil { - return ArtSpec{}, errors.ErrInvalidWrap(err, KIND_ARETEFACT_REFERENCE, art) - } - dig = &d - } - return ArtSpec{ - Repository: string(match[1]), - Tag: tag, - Digest: dig, - }, nil -} - -// ArtSpec is a go internal representation of a oci reference. -type ArtSpec struct { - // Repository is the part of a reference without its hostname - Repository string `json:"repository"` - // +optional - Tag *string `json:"tag,omitempty"` - // +optional - Digest *digest.Digest `json:"digest,omitempty"` -} - -func (r *ArtSpec) Version() string { - if r.Tag != nil { - return *r.Tag - } - if r.Digest != nil { - return "@" + string(*r.Digest) - } - return "latest" -} - -func (r *ArtSpec) IsRegistry() bool { - return r.Repository == "" -} - -func (r *ArtSpec) IsVersion() bool { - return r.Tag != nil || r.Digest != nil -} - -func (r *ArtSpec) IsTagged() bool { - return r.Tag != nil -} - -func (r *ArtSpec) Reference() string { - if r.Tag != nil { - return *r.Tag - } - if r.Digest != nil { - return "@" + string(*r.Digest) - } - return "latest" -} - -func (r *ArtSpec) String() string { - s := r.Repository - if r.Tag != nil { - s += fmt.Sprintf(":%s", *r.Tag) - } - if r.Digest != nil { - s += fmt.Sprintf("@%s", r.Digest.String()) - } - return s -} diff --git a/pkg/contexts/oci/ref_test.go b/pkg/contexts/oci/ref_test.go deleted file mode 100644 index ba64f812a..000000000 --- a/pkg/contexts/oci/ref_test.go +++ /dev/null @@ -1,829 +0,0 @@ -package oci_test - -import ( - "strings" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - godigest "github.com/opencontainers/go-digest" - - "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/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/runtime" -) - -func Type(t string) string { - if t == "" { - return t - } - return t + "::" -} - -func FileFormat(t, f string) string { - if t == "" { - return f - } - if f == "" { - return t - } - return t + "+" + f -} - -func FileType(t, f string) string { - if t != "" { - return t - } else { - return f - } -} - -func Scheme(s string) string { - if s == "" { - return s - } - return s + "://" -} - -func Sub(t string) string { - if t == "" { - return t - } - return "/" + t -} - -func Vers(t, d string) string { - if t == "" && d == "" { - return "" - } - if t == "" { - return "@" + d - } - if d == "" { - return ":" + t - } - return ":" + t + "@" + d -} - -func Dig(b []byte) *godigest.Digest { - if len(b) == 0 { - return nil - } - s := godigest.Digest(b) - return &s -} - -func Pointer(b []byte) *string { - if len(b) == 0 { - return nil - } - s := string(b) - return &s -} - -func CheckRef(ref string, exp *oci.RefSpec) { - spec, err := oci.ParseRef(ref) - if exp == nil { - ExpectWithOffset(1, err).To(HaveOccurred()) - } else { - ExpectWithOffset(1, err).To(Succeed()) - ExpectWithOffset(1, spec).To(Equal(*exp)) - } -} - -func CheckRepo(ref string, exp *oci.UniformRepositorySpec) { - spec, err := oci.ParseRepo(ref) - if exp == nil { - ExpectWithOffset(1, err).To(HaveOccurred()) - } else { - ExpectWithOffset(1, err).To(Succeed()) - ExpectWithOffset(1, spec).To(Equal(*exp)) - } -} - -var _ = Describe("ref parsing", func() { - digest := godigest.Digest("sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a") - tag := "v1" - - ghcr := oci.UniformRepositorySpec{Host: "ghcr.io"} - docker := oci.UniformRepositorySpec{Host: "docker.io"} - - Context("parse file path refs", func() { - t := "ctf" - p := "file/path" - r := "github.com/mandelsoft/ocm" - v := "v1" - d := "sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a" - - Context("[+][::][./][//[:]", func() { - for _, cm := range []string{"", "+"} { - for _, ut := range []string{"", t} { - for _, uf := range []string{"", "directory", "tar", "tgz"} { - for _, up := range []string{p, "./" + p} { - for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + ".1.2-rc.1"} { - for _, ud := range []string{"", d} { - ref := cm + Type(FileFormat(ut, uf)) + up + "//" + r + Vers(uv, ud) - ut, uf, uv, up, ud := ut, uf, uv, up, ud - - // tests parsing of all permutations of - // [+][::][./][//[:][@] - It("parses ref "+ref, func() { - CheckRef(ref, &oci.RefSpec{ - UniformRepositorySpec: oci.UniformRepositorySpec{ - Type: FileType(ut, uf), - Scheme: "", - Host: "", - Info: up, - CreateIfMissing: ref[0] == '+', - TypeHint: FileFormat(ut, uf), - }, - ArtSpec: oci.ArtSpec{ - Repository: r, - Tag: Pointer([]byte(uv)), - Digest: Dig([]byte(ud)), - }, - }) - }) - } - } - } - } - } - } - }) - }) - - Context("parse domain refs", func() { - t := "oci" - h := "ghcr.io" - r := "github.com/mandelsoft/ocm" - v := "v1" - d := "sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a" - - // Notice that the file formats (directory, tar, tgz) CAN BE PARSED in this notation, BUT for non file based - // implementations like oci, this information is not used. - Context("[+][::][://][:][/]/[:][@]", func() { - for _, cm := range []string{"", "+"} { - for _, ut := range []string{"", t} { - for _, uf := range []string{"", "directory", "tar", "tgz"} { - for _, ush := range []string{"", "http", "https"} { - for _, uh := range []string{h, h + ":3030"} { - for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + ".1.2-rc.1"} { - for _, ud := range []string{"", d} { - for _, sep := range []string{"/", "//"} { - ref := cm + Type(FileFormat(ut, uf)) + Scheme(ush) + uh + sep + r + Vers(uv, ud) - ut, uf, ush, uh, uv, ud := ut, uf, ush, uh, uv, ud - - // tests parsing of all permutations of - // [::][://][:][/]/[:][@] - It("parses ref "+ref, func() { - CheckRef(ref, &oci.RefSpec{ - UniformRepositorySpec: oci.UniformRepositorySpec{ - Type: FileType(ut, uf), - Scheme: ush, - Host: uh, - Info: "", - CreateIfMissing: ref[0] == '+', - TypeHint: FileFormat(ut, uf), - }, - ArtSpec: oci.ArtSpec{ - Repository: r, - Tag: Pointer([]byte(uv)), - Digest: Dig([]byte(ud)), - }, - }) - }) - } - } - } - } - } - } - } - } - }) - - It("repository creation from parsed repo", func() { - ctx := oci.New() - aliasreg := ocireg.NewRepositorySpec("http://ghcr.io") - ctx.SetAlias("myalias", aliasreg) - repo := Must(oci.ParseRef("myalias//repository:1.0.0")) - spec := Must(ctx.MapUniformRepositorySpec(&repo.UniformRepositorySpec)) - Expect(spec).To(Equal(aliasreg)) - }) - }) - - Context("parse host port refs", func() { - t := "oci" - h := "localhost" - r := "github.com/mandelsoft/ocm" - v := "v1" - d := "sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a" - - // localhost (with and without port) (and other host names) are a special case since these are not formally - // valid domains - // the combination of this test and the test below test parsing of all permutations of - // [::][://]:/[:][@] - Context("[+][::][://]:/[:][@]", func() { - for _, cm := range []string{"", "+"} { - for _, ut := range []string{"", t} { - for _, uf := range []string{"", "directory", "tar", "tgz"} { - for _, ush := range []string{"", "http", "https"} { - for _, uh := range []string{h + ":3030"} { - for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + ".1.2-rc.1"} { - for _, ud := range []string{"", d} { - ref := cm + Type(FileFormat(ut, uf)) + Scheme(ush) + uh + "/" + r + Vers(uv, ud) - ut, uf, ush, uh, uv, ud := ut, uf, ush, uh, uv, ud - - // tests parsing of all permutations of - // [::][://]:/[:][@] - It("parses ref "+ref, func() { - CheckRef(ref, &oci.RefSpec{ - UniformRepositorySpec: oci.UniformRepositorySpec{ - Type: FileType(ut, uf), - Scheme: ush, - Host: uh, - Info: "", - CreateIfMissing: ref[0] == '+', - TypeHint: FileFormat(ut, uf), - }, - ArtSpec: oci.ArtSpec{ - Repository: r, - Tag: Pointer([]byte(uv)), - Digest: Dig([]byte(ud)), - }, - }) - }) - } - } - } - } - } - } - } - }) - Context("[+][::][://][:]//[:][@]", func() { - for _, cm := range []string{"", "+"} { - for _, ut := range []string{"", t} { - for _, uf := range []string{"", "directory", "tar", "tgz"} { - for _, ush := range []string{"", "http", "https"} { - for _, uh := range []string{h, h + ":3030"} { - for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + ".1.2-rc.1"} { - for _, ud := range []string{"", d} { - ref := cm + Type(FileFormat(ut, uf)) + Scheme(ush) + uh + "//" + r + Vers(uv, ud) - ut, uf, ush, uh, uv, ud := ut, uf, ush, uh, uv, ud - - // tests parsing of all permutations of - // [::][://][:]//[:][@] - It("parses ref "+ref, func() { - CheckRef(ref, &oci.RefSpec{ - UniformRepositorySpec: oci.UniformRepositorySpec{ - Type: FileType(ut, uf), - Scheme: ush, - Host: uh, - Info: "", - CreateIfMissing: ref[0] == '+', - TypeHint: FileFormat(ut, uf), - }, - ArtSpec: oci.ArtSpec{ - Repository: r, - Tag: Pointer([]byte(uv)), - Digest: Dig([]byte(ud)), - }, - }) - }) - } - } - } - } - } - } - } - }) - }) - - Context("parse json repo spec refs", func() { - t := "oci" - h := "ghcr.io" - r := "github.com/mandelsoft/ocm" - v := "v1" - d := "sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a" - - repospec := ocireg.NewRepositorySpec(h) - jsonrepospec := string(Must(runtime.DefaultJSONEncoding.Marshal(repospec))) - - // Notice that the file formats (directory, tar, tgz) CAN BE PARSED in this notation, BUT for non file based - // implementations like oci, this information is not used. - Context("[+][::][//][:][@]", func() { - for _, cm := range []string{"", "+"} { - for _, ut := range []string{"", t} { - for _, uf := range []string{"", "directory", "tar", "tgz"} { - for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + ".1.2-rc.1"} { - for _, ud := range []string{"", d} { - ref := cm + Type(FileFormat(ut, uf)) + jsonrepospec + "//" + r + Vers(uv, ud) - ut, uf, uv, ud := ut, uf, uv, ud - - // tests parsing of all permutations of - // [::][//][:][@] - It("parses ref "+ref, func() { - CheckRef(ref, &oci.RefSpec{ - UniformRepositorySpec: oci.UniformRepositorySpec{ - Type: FileType(ut, uf), - Scheme: "", - Host: "", - Info: jsonrepospec, - CreateIfMissing: ref[0] == '+', - TypeHint: FileFormat(ut, uf), - }, - ArtSpec: oci.ArtSpec{ - Repository: r, - Tag: Pointer([]byte(uv)), - Digest: Dig([]byte(ud)), - }, - }) - }) - } - } - } - } - } - }) - }) - - Context("parse docker library refs", func() { - // h := "docker.io" - r := "ubuntu" - v := "v1" - d := "sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a" - - Context("[:][@]", func() { - for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + ".1.2-rc.1"} { - for _, ud := range []string{"", d} { - ref := r + Vers(uv, ud) - uv, ud := uv, ud - - // tests parsing of all permutations of - // [:][@] - It("parses ref "+ref, func() { - CheckRef(ref, &oci.RefSpec{ - UniformRepositorySpec: oci.UniformRepositorySpec{ - Type: "", - Scheme: "", - Host: "docker.io", - Info: "", - CreateIfMissing: false, - TypeHint: "", - }, - ArtSpec: oci.ArtSpec{ - Repository: "library/" + r, - Tag: Pointer([]byte(uv)), - Digest: Dig([]byte(ud)), - }, - }) - }) - } - } - }) - }) - - Context("parse docker repository refs", func() { - // h := "docker.io" - r := "docker-repo/ubuntu" - v := "v1" - d := "sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a" - - Context("/[:][@]", func() { - for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + ".1.2-rc.1"} { - for _, ud := range []string{"", d} { - ref := r + Vers(uv, ud) - uv, ud := uv, ud - - // tests parsing of all permutations of - // [:][@] - It("parses ref "+ref, func() { - CheckRef(ref, &oci.RefSpec{ - UniformRepositorySpec: oci.UniformRepositorySpec{ - Type: "", - Scheme: "", - Host: "docker.io", - Info: "", - CreateIfMissing: false, - TypeHint: "", - }, - ArtSpec: oci.ArtSpec{ - Repository: r, - Tag: Pointer([]byte(uv)), - Digest: Dig([]byte(ud)), - }, - }) - }) - } - } - }) - }) - - Context("parse file path repos", func() { - t := "ctf" - p := "file/path" - - Context("[+][::][./][", func() { - for _, cm := range []string{"", "+"} { - for _, ut := range []string{"", t} { - for _, uf := range []string{"", "directory", "tar", "tgz"} { - for _, up := range []string{p, "./" + p} { - ref := cm + Type(FileFormat(ut, uf)) + up - ut, uf, up := ut, uf, up - // tests parsing of all permutations of - // [+][::][./][//[:][@] - It("parses ref "+ref, func() { - CheckRepo(ref, &oci.UniformRepositorySpec{ - Type: FileType(ut, uf), - Scheme: "", - Host: "", - Info: up, - CreateIfMissing: ref[0] == '+', - TypeHint: FileFormat(ut, uf), - }) - }) - } - } - } - } - }) - }) - - Context("parse domain repos", func() { - t := "oci" - h := "ghcr.io" - - Context("[+][::][://][:]", func() { - for _, cm := range []string{"", "+"} { - for _, ut := range []string{"", t} { - for _, uf := range []string{"", "directory", "tar", "tgz"} { - for _, ush := range []string{"", "http", "https"} { - for _, uh := range []string{h, h + ":3030", "localhost", "localhost:3030"} { - ref := cm + Type(FileFormat(ut, uf)) + Scheme(ush) + uh - ut, uf, ush, uh := ut, uf, ush, uh - - // tests parsing of all permutations of - // [+][::][://][:] - It("parses ref "+ref, func() { - // if you are coming from the ocm test and - // wondering why the corresponding tests if - // has an additional condition that the - // type has to be empty - this is because - // the corresponding parse method calls - // an intermediate handler based on the - // type that resolves the localhost in the - // info. - // For oci repositories, such this - // handling is done in the - // MapUniformRepositorySpec logic. - if strings.HasPrefix(uh, "localhost") { - CheckRepo(ref, &oci.UniformRepositorySpec{ - Type: FileType(ut, uf), - Scheme: "", - Host: "", - Info: Scheme(ush) + uh, - CreateIfMissing: ref[0] == '+', - TypeHint: FileFormat(ut, uf), - }) - } else { - CheckRepo(ref, &oci.UniformRepositorySpec{ - Type: FileType(ut, uf), - Scheme: ush, - Host: uh, - Info: "", - CreateIfMissing: ref[0] == '+', - TypeHint: FileFormat(ut, uf), - }) - } - }) - } - } - } - } - } - }) - It("repository creation from parsed repo with localhost", func() { - ctx := oci.New() - repo := Must(oci.ParseRepo("http://localhost")) - spec := Must(ctx.MapUniformRepositorySpec(&repo)) - Expect(spec).To(Equal(ocireg.NewRepositorySpec("http://localhost"))) - }) - It("repository creation from parsed repo with localhost", func() { - ctx := oci.New() - - aliasreg := ocireg.NewRepositorySpec("http://ghcr.io") - ctx.SetAlias("myalias", aliasreg) - repo := Must(oci.ParseRepo("myalias")) - spec := Must(ctx.MapUniformRepositorySpec(&repo)) - Expect(spec).To(Equal(aliasreg)) - }) - }) - - Context("parse json repo spec refs", func() { - t := "oci" - h := "ghcr.io" - - repospec := ocireg.NewRepositorySpec(h) - jsonrepospec := string(Must(runtime.DefaultJSONEncoding.Marshal(repospec))) - - // Notice that the file formats (directory, tar, tgz) CAN BE PARSED in this notation, BUT for non file based - // implementations like oci, this information is not used. - Context("[+][::]", func() { - for _, cm := range []string{"", "+"} { - for _, ut := range []string{"", t} { - for _, uf := range []string{"", "directory", "tar", "tgz"} { - ref := cm + Type(FileFormat(ut, uf)) + jsonrepospec - ut, uf := ut, uf - - // tests parsing of all permutations of - // [::] - It("parses ref "+ref, func() { - CheckRepo(ref, &oci.UniformRepositorySpec{ - Type: FileType(ut, uf), - Scheme: "", - Host: "", - Info: jsonrepospec, - CreateIfMissing: ref[0] == '+', - TypeHint: FileFormat(ut, uf), - }) - }) - } - } - } - }) - }) - - It("succeeds for repository", func() { - CheckRef("::ghcr.io/", &oci.RefSpec{UniformRepositorySpec: ghcr}) - }) - It("succeeds", func() { - CheckRef("ubuntu", &oci.RefSpec{UniformRepositorySpec: docker, ArtSpec: oci.ArtSpec{Repository: "library/ubuntu"}}) - CheckRef("ubuntu:v1", &oci.RefSpec{UniformRepositorySpec: docker, ArtSpec: oci.ArtSpec{Repository: "library/ubuntu", Tag: &tag}}) - CheckRef("test/ubuntu", &oci.RefSpec{UniformRepositorySpec: docker, ArtSpec: oci.ArtSpec{Repository: "test/ubuntu"}}) - CheckRef("test_test/ubuntu", &oci.RefSpec{UniformRepositorySpec: docker, ArtSpec: oci.ArtSpec{Repository: "test_test/ubuntu"}}) - CheckRef("test__test/ubuntu", &oci.RefSpec{UniformRepositorySpec: docker, ArtSpec: oci.ArtSpec{Repository: "test__test/ubuntu"}}) - CheckRef("test-test/ubuntu", &oci.RefSpec{UniformRepositorySpec: docker, ArtSpec: oci.ArtSpec{Repository: "test-test/ubuntu"}}) - CheckRef("test--test/ubuntu", &oci.RefSpec{UniformRepositorySpec: docker, ArtSpec: oci.ArtSpec{Repository: "test--test/ubuntu"}}) - CheckRef("test-----test/ubuntu", &oci.RefSpec{UniformRepositorySpec: docker, ArtSpec: oci.ArtSpec{Repository: "test-----test/ubuntu"}}) - CheckRef("test/ubuntu:v1", &oci.RefSpec{UniformRepositorySpec: docker, ArtSpec: oci.ArtSpec{Repository: "test/ubuntu", Tag: &tag}}) - CheckRef("ghcr.io/test/ubuntu", &oci.RefSpec{UniformRepositorySpec: ghcr, ArtSpec: oci.ArtSpec{Repository: "test/ubuntu"}}) - CheckRef("ghcr.io/test", &oci.RefSpec{UniformRepositorySpec: ghcr, ArtSpec: oci.ArtSpec{Repository: "test"}}) - CheckRef("ghcr.io:8080/test/ubuntu", &oci.RefSpec{UniformRepositorySpec: oci.UniformRepositorySpec{Host: "ghcr.io:8080"}, ArtSpec: oci.ArtSpec{Repository: "test/ubuntu"}}) - CheckRef("ghcr.io/test/ubuntu:v1", &oci.RefSpec{UniformRepositorySpec: ghcr, ArtSpec: oci.ArtSpec{Repository: "test/ubuntu", Tag: &tag}}) - CheckRef("ghcr.io/test/ubuntu@sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a", &oci.RefSpec{UniformRepositorySpec: ghcr, ArtSpec: oci.ArtSpec{Repository: "test/ubuntu", Digest: &digest}}) - CheckRef("ghcr.io/test/ubuntu:v1@sha256:3d05e105e350edf5be64fe356f4906dd3f9bf442a279e4142db9879bba8e677a", &oci.RefSpec{UniformRepositorySpec: ghcr, ArtSpec: oci.ArtSpec{Repository: "test/ubuntu", Tag: &tag, Digest: &digest}}) - CheckRef("test___test/ubuntu", &oci.RefSpec{ - UniformRepositorySpec: oci.UniformRepositorySpec{ - Info: "test___test/ubuntu", - }, - }) - CheckRef("type::https://ghcr.io/repo/repo:v1@"+digest.String(), &oci.RefSpec{ - UniformRepositorySpec: oci.UniformRepositorySpec{ - Type: "type", - Scheme: "https", - Host: "ghcr.io", - Info: "", - TypeHint: "type", - }, - ArtSpec: oci.ArtSpec{ - Repository: "repo/repo", - Tag: &tag, - Digest: &digest, - }, - }) - CheckRef("http://127.0.0.1:443/repo/repo:v1@"+digest.String(), &oci.RefSpec{ - UniformRepositorySpec: oci.UniformRepositorySpec{ - Type: "", - Scheme: "http", - Host: "127.0.0.1:443", - Info: "", - }, - ArtSpec: oci.ArtSpec{ - Repository: "repo/repo", - Tag: &tag, - Digest: &digest, - }, - }) - CheckRef("directory::a/b", &oci.RefSpec{ - UniformRepositorySpec: oci.UniformRepositorySpec{ - Type: "directory", - Scheme: "", - Host: "", - Info: "a/b", - TypeHint: "directory", - }, - ArtSpec: oci.ArtSpec{ - Repository: "", - }, - }) - CheckRef("ctf+directory::a/b", &oci.RefSpec{ - UniformRepositorySpec: oci.UniformRepositorySpec{ - Type: "ctf", - Scheme: "", - Host: "", - Info: "a/b", - TypeHint: "ctf+directory", - }, - ArtSpec: oci.ArtSpec{ - Repository: "", - }, - }) - CheckRef("+ctf+directory::a/b", &oci.RefSpec{ - UniformRepositorySpec: oci.UniformRepositorySpec{ - Type: "ctf", - Scheme: "", - Host: "", - Info: "a/b", - CreateIfMissing: true, - TypeHint: "ctf+directory", - }, - ArtSpec: oci.ArtSpec{ - Repository: "", - }, - }) - - CheckRef("a/b//", &oci.RefSpec{ - UniformRepositorySpec: oci.UniformRepositorySpec{ - Type: "", - Scheme: "", - Host: "", - Info: "a/b", - }, - ArtSpec: oci.ArtSpec{ - Repository: "", - }, - }) - - CheckRef("directory::a/b//c/d", &oci.RefSpec{ - UniformRepositorySpec: oci.UniformRepositorySpec{ - Type: "directory", - Scheme: "", - Host: "", - Info: "a/b", - TypeHint: "directory", - }, - ArtSpec: oci.ArtSpec{ - Repository: "c/d", - }, - }) - - CheckRef("oci::ghcr.io", &oci.RefSpec{ - UniformRepositorySpec: oci.UniformRepositorySpec{ - Type: "oci", - Scheme: "", - Host: "ghcr.io", - Info: "", - TypeHint: "oci", - }, - ArtSpec: oci.ArtSpec{ - Repository: "", - }, - }) - CheckRef("/tmp/ctf//mandelsoft/test:v1", &oci.RefSpec{ - UniformRepositorySpec: oci.UniformRepositorySpec{ - Type: "", - Scheme: "", - Host: "", - Info: "/tmp/ctf", - }, - ArtSpec: oci.ArtSpec{ - Repository: "mandelsoft/test", - Tag: &tag, - }, - }) - CheckRef("/tmp/ctf", &oci.RefSpec{ - UniformRepositorySpec: oci.UniformRepositorySpec{ - Type: "", - Scheme: "", - Host: "", - Info: "/tmp/ctf", - }, - }) - }) - - It("json spec", func() { - ctx := oci.New() - - tag := "1.0.0" - CheckRef("OCIRegistry::{\"baseUrl\": \"test.com\"}//repo:1.0.0", &oci.RefSpec{ - UniformRepositorySpec: oci.UniformRepositorySpec{ - Type: "OCIRegistry", - Scheme: "", - Host: "", - Info: "{\"baseUrl\": \"test.com\"}", - TypeHint: "OCIRegistry", - }, - ArtSpec: oci.ArtSpec{ - Repository: "repo", - Tag: &tag, - }, - }) - ref := Must(oci.ParseRef("OCIRegistry::{\"type\":\"OCIRegistry\", \"baseUrl\": \"test.com\"}//repo:1.0.0")) - spec := Must(ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec)) - repo := Must(spec.Repository(ctx, nil)) - _ = repo - }) - - It("fail for json spec with type mismatch", func() { - ctx := oci.New() - - tag := "1.0.0" - CheckRef("oci::{\"type\":\"OCIRegistry\", \"baseUrl\": \"test.com\"}//repo:1.0.0", &oci.RefSpec{ - UniformRepositorySpec: oci.UniformRepositorySpec{ - Type: "oci", - Scheme: "", - Host: "", - Info: "{\"type\":\"OCIRegistry\", \"baseUrl\": \"test.com\"}", - TypeHint: "oci", - }, - ArtSpec: oci.ArtSpec{ - Repository: "repo", - Tag: &tag, - }, - }) - ref := Must(oci.ParseRef("oci::{\"type\":\"OCIRegistry\", \"baseUrl\": \"test.com\"}//repo:1.0.0")) - spec, err := ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec) - Expect(spec).To(BeNil()) - Expect(err).ToNot(BeNil()) - }) - - It("fails", func() { - CheckRef("https://ubuntu", nil) - CheckRef("ubuntu@4711", nil) - CheckRef("test/ubuntu@4711", nil) - CheckRef("test/ubuntu:v1@4711", nil) - CheckRef("ghcr.io/test/ubuntu:v1@4711", nil) - }) - It("repo", func() { - CheckRepo("ghcr.io", &oci.UniformRepositorySpec{ - Host: "ghcr.io", - }) - CheckRepo("https://ghcr.io", &oci.UniformRepositorySpec{ - Scheme: "https", - Host: "ghcr.io", - }) - CheckRepo("alias", &oci.UniformRepositorySpec{ - Info: "alias", - }) - CheckRepo("tar::a/b.tar", &oci.UniformRepositorySpec{ - Type: "tar", - Info: "a/b.tar", - TypeHint: "tar", - }) - CheckRepo("a/b.tar", &oci.UniformRepositorySpec{ - Info: "a/b.tar", - }) - }) - It("localhost", func() { - ctx := oci.New() - // port is necessary here, otherwise it is ambiguous with dockerhub reference (localhost/test:1.0.0 could be - // an artifact stored on duckerhub) - ref := Must(oci.ParseRef("localhost:80/test:1.0.0")) - spec := Must(ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec)) - Expect(spec).To(Equal(ocireg.NewRepositorySpec("localhost:80"))) - }) - It("localhost with unambiguous separator and without port", func() { - ctx := oci.New() - ref := Must(oci.ParseRef("localhost//test:1.0.0")) - spec := Must(ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec)) - Expect(spec).To(Equal(ocireg.NewRepositorySpec("localhost"))) - }) - It("localhost with unambiguous separator", func() { - ctx := oci.New() - ref := Must(oci.ParseRef("localhost:80//test:1.0.0")) - spec := Must(ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec)) - Expect(spec).To(Equal(ocireg.NewRepositorySpec("localhost:80"))) - }) - It("scheme://localhost:port//repository:version", func() { - ctx := oci.New() - ref := Must(oci.ParseRef("http://localhost:80//test:1.0.0")) - spec := Must(ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec)) - Expect(spec).To(Equal(ocireg.NewRepositorySpec("http://localhost:80"))) - }) - It("scheme://localhost:port/repository:version", func() { - ctx := oci.New() - ref := Must(oci.ParseRef("http://localhost:80/test:1.0.0")) - spec := Must(ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec)) - Expect(spec).To(Equal(ocireg.NewRepositorySpec("http://localhost:80"))) - }) - It("ctf with create", func() { - ctx := oci.New() - ref := Must(oci.ParseRef("+ctf+directory::./file/path//github.com/mandelsoft/ocm")) - spec := Must(ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec)) - Expect(spec).To(Equal(Must(ctf.NewRepositorySpec(accessobj.ACC_CREATE, "./file/path", accessio.FormatDirectory)))) - }) - It("ctf without create", func() { - ctx := oci.New() - - ref := Must(oci.ParseRepo("ctf+directory::./file/path")) - spec := Must(ctx.MapUniformRepositorySpec(&ref)) - Expect(spec).To(Equal(Must(ctf.NewRepositorySpec(accessobj.ACC_WRITABLE, "./file/path")))) - }) -}) diff --git a/pkg/contexts/oci/repositories/artifactset/filesystemaccess.go b/pkg/contexts/oci/repositories/artifactset/filesystemaccess.go deleted file mode 100644 index baaac052f..000000000 --- a/pkg/contexts/oci/repositories/artifactset/filesystemaccess.go +++ /dev/null @@ -1,45 +0,0 @@ -package artifactset - -import ( - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common/accessobj" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi/support" -) - -type FileSystemBlobAccess struct { - *accessobj.FileSystemBlobAccess -} - -func NewFileSystemBlobAccess(access *accessobj.AccessObject) *FileSystemBlobAccess { - return &FileSystemBlobAccess{accessobj.NewFileSystemBlobAccess(access)} -} - -func (i *FileSystemBlobAccess) GetArtifact(access support.NamespaceAccessImpl, digest digest.Digest) (acc cpi.ArtifactAccess, err error) { - v, err := access.View() - if err != nil { - return nil, err - } - defer v.Close() - _, data, err := i.GetBlobData(digest) - if err == nil { - blob := blobaccess.ForDataAccess("", -1, "", data) - acc, err = support.NewArtifactForBlob(access, blob) - } - return -} - -func (i *FileSystemBlobAccess) AddArtifactBlob(artifact cpi.Artifact) (cpi.BlobAccess, error) { - blob, err := artifact.Blob() - if err != nil { - return nil, err - } - - err = i.AddBlob(blob) - if err != nil { - return nil, err - } - return blob, nil -} diff --git a/pkg/contexts/oci/repositories/artifactset/format.go b/pkg/contexts/oci/repositories/artifactset/format.go deleted file mode 100644 index 61c207f86..000000000 --- a/pkg/contexts/oci/repositories/artifactset/format.go +++ /dev/null @@ -1,269 +0,0 @@ -package artifactset - -import ( - "sync" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/common/accessobj" - mime2 "github.com/open-component-model/ocm/pkg/mime" -) - -const ( - // The artifact descriptor name for artifact format. - ArtifactSetDescriptorFileName = "artifact-descriptor.json" - BlobsDirectoryName = "blobs" - - OCIArtifactSetDescriptorFileName = "index.json" - OCILayouFileName = "oci-layout" -) - -var DefaultArtifactSetDescriptorFileName = OCIArtifactSetDescriptorFileName - -func IsOCIDefaultFormat() bool { - return DefaultArtifactSetDescriptorFileName == OCIArtifactSetDescriptorFileName -} - -func DescriptorFileName(format string) string { - switch format { - case FORMAT_OCI: - return OCIArtifactSetDescriptorFileName - case FORMAT_OCM: - return ArtifactSetDescriptorFileName - case "": - return DefaultArtifactSetDescriptorFileName - } - return "" -} - -type accessObjectInfo struct { - accessobj.DefaultAccessObjectInfo -} - -var _ accessobj.AccessObjectInfo = (*accessObjectInfo)(nil) - -func NewAccessObjectInfo(fmts ...string) accessobj.AccessObjectInfo { - a := &accessObjectInfo{ - accessobj.DefaultAccessObjectInfo{ - ObjectTypeName: "artifactset", - ElementDirectoryName: BlobsDirectoryName, - ElementTypeName: "blob", - DescriptorHandlerFactory: NewStateHandler, - }, - } - oci := IsOCIDefaultFormat() - if len(fmts) > 0 { - switch fmts[0] { - case FORMAT_OCM: - oci = false - case FORMAT_OCI: - oci = true - case "": - } - } - if oci { - a.setOCI() - } else { - a.setOCM() - } - return a -} - -func (a *accessObjectInfo) setOCI() { - a.DescriptorFileName = OCIArtifactSetDescriptorFileName - a.AdditionalFiles = []string{OCILayouFileName} -} - -func (a *accessObjectInfo) setOCM() { - a.DescriptorFileName = ArtifactSetDescriptorFileName - a.AdditionalFiles = nil -} - -func (a *accessObjectInfo) setupOCIFS(fs vfs.FileSystem, mode vfs.FileMode) error { - data := `{ - "imageLayoutVersion": "1.0.0" -} -` - return vfs.WriteFile(fs, OCILayouFileName, []byte(data), mode) -} - -func (a *accessObjectInfo) SetupFileSystem(fs vfs.FileSystem, mode vfs.FileMode) error { - if err := a.SetupFor(fs); err != nil { - return err - } - if err := a.DefaultAccessObjectInfo.SetupFileSystem(fs, mode); err != nil { - return err - } - if len(a.AdditionalFiles) > 0 { - return a.setupOCIFS(fs, mode) - } - return nil -} - -func (a *accessObjectInfo) SetupFor(fs vfs.FileSystem) error { - ok, err := vfs.FileExists(fs, OCIArtifactSetDescriptorFileName) - if err != nil { - return err - } - if ok { - a.setOCI() - return nil - } - - ok, err = vfs.FileExists(fs, ArtifactSetDescriptorFileName) - if err != nil { - return err - } - if ok { - a.setOCM() - return nil - } - - ok, err = vfs.FileExists(fs, OCILayouFileName) - if err != nil { - return err - } - if ok { - a.setOCI() - return nil - } - - // keep configured format - return nil -} - -//////////////////////////////////////////////////////////////////////////////// - -type Object = ArtifactSet - -type FormatHandler interface { - accessio.Option - - Format() accessio.FileFormat - - Open(acc accessobj.AccessMode, path string, opts accessio.Options) (*Object, error) - Create(path string, opts accessio.Options, mode vfs.FileMode) (*Object, error) - Write(obj *Object, path string, opts accessio.Options, mode vfs.FileMode) error -} - -type formatHandler struct { - accessobj.FormatHandler -} - -var ( - FormatDirectory = RegisterFormat(accessobj.FormatDirectory) - FormatTAR = RegisterFormat(accessobj.FormatTAR) - FormatTGZ = RegisterFormat(accessobj.FormatTGZ) -) - -//////////////////////////////////////////////////////////////////////////////// - -var ( - fileFormats = map[accessio.FileFormat]FormatHandler{} - lock sync.RWMutex -) - -func RegisterFormat(f accessobj.FormatHandler) FormatHandler { - lock.Lock() - defer lock.Unlock() - h := &formatHandler{f} - fileFormats[f.Format()] = h - return h -} - -func GetFormats() []string { - lock.RLock() - defer lock.RUnlock() - return accessio.GetFormatsFor(fileFormats) -} - -func GetFormat(name accessio.FileFormat) FormatHandler { - lock.RLock() - defer lock.RUnlock() - return fileFormats[name] -} - -func SupportedFormats() []accessio.FileFormat { - lock.RLock() - defer lock.RUnlock() - result := make([]accessio.FileFormat, 0, len(fileFormats)) - for f := range fileFormats { - result = append(result, f) - } - return result -} - -//////////////////////////////////////////////////////////////////////////////// - -func OpenFromBlob(acc accessobj.AccessMode, blob blobaccess.BlobAccess, opts ...accessio.Option) (*Object, error) { - return OpenFromDataAccess(acc, blob.MimeType(), blob, opts...) -} - -func OpenFromDataAccess(acc accessobj.AccessMode, mime string, data blobaccess.DataAccess, opts ...accessio.Option) (*Object, error) { - o, err := accessio.AccessOptions(nil, opts...) - if err != nil { - return nil, err - } - if o.GetFile() != nil || o.GetReader() != nil { - return nil, errors.ErrInvalid("file or reader option not possible for blob access") - } - reader, err := data.Reader() - if err != nil { - return nil, err - } - defer reader.Close() - o.SetReader(reader) - fmt := accessio.FormatTar - - if mime2.IsGZip(mime) { - fmt = accessio.FormatTGZ - } - o.SetFileFormat(fmt) - return Open(acc&accessobj.ACC_READONLY, "", 0, o) -} - -func Open(acc accessobj.AccessMode, path string, mode vfs.FileMode, olist ...accessio.Option) (*Object, error) { - o, create, err := accessobj.HandleAccessMode(acc, path, &Options{}, olist...) - if err != nil { - return nil, err - } - h, ok := fileFormats[*o.GetFileFormat()] - if !ok { - return nil, errors.ErrUnknown(accessobj.KIND_FILEFORMAT, o.GetFileFormat().String()) - } - if create { - return h.Create(path, o, mode) - } - return h.Open(acc, path, o) -} - -func Create(acc accessobj.AccessMode, path string, mode vfs.FileMode, opts ...accessio.Option) (*Object, error) { - o, err := accessio.AccessOptions(&Options{}, opts...) - if err != nil { - return nil, err - } - o.DefaultFormat(accessio.FormatDirectory) - h, ok := fileFormats[*o.GetFileFormat()] - if !ok { - return nil, errors.ErrUnknown(accessobj.KIND_FILEFORMAT, o.GetFileFormat().String()) - } - return h.Create(path, o, mode) -} - -//////////////////////////////////////////////////////////////////////////////// - -func (h *formatHandler) Open(acc accessobj.AccessMode, path string, opts accessio.Options) (*Object, error) { - return _Wrap(h.FormatHandler.Open(NewAccessObjectInfo(GetFormatVersion(opts)), acc, path, opts)) -} - -func (h *formatHandler) Create(path string, opts accessio.Options, mode vfs.FileMode) (*Object, error) { - return _Wrap(h.FormatHandler.Create(NewAccessObjectInfo(GetFormatVersion(opts)), path, opts, mode)) -} - -// WriteToFilesystem writes the current object to a filesystem. -func (h *formatHandler) Write(obj *Object, path string, opts accessio.Options, mode vfs.FileMode) error { - return h.FormatHandler.Write(obj.container.base.Access(), path, opts, mode) -} diff --git a/pkg/contexts/oci/repositories/artifactset/options.go b/pkg/contexts/oci/repositories/artifactset/options.go deleted file mode 100644 index d75ed56a7..000000000 --- a/pkg/contexts/oci/repositories/artifactset/options.go +++ /dev/null @@ -1,77 +0,0 @@ -package artifactset - -import ( - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/common/accessio" -) - -type Options struct { - accessio.StandardOptions - - FormatVersion string `json:"formatVersion,omitempty"` -} - -func NewOptions(olist ...accessio.Option) (*Options, error) { - opts := &Options{} - err := accessio.ApplyOptions(opts, olist...) - if err != nil { - return nil, err - } - return opts, nil -} - -type FormatVersionOption interface { - SetFormatVersion(string) - GetFormatVersion() string -} - -func GetFormatVersion(opts accessio.Options) string { - if o, ok := opts.(FormatVersionOption); ok { - return o.GetFormatVersion() - } - return "" -} - -var _ FormatVersionOption = (*Options)(nil) - -func (o *Options) SetFormatVersion(s string) { - o.FormatVersion = s -} - -func (o *Options) GetFormatVersion() string { - return o.FormatVersion -} - -func (o *Options) ApplyOption(opts accessio.Options) error { - err := o.StandardOptions.ApplyOption(opts) - if err != nil { - return err - } - if o.FormatVersion != "" { - if s, ok := opts.(FormatVersionOption); ok { - s.SetFormatVersion(o.FormatVersion) - } else { - return errors.ErrNotSupported("format version option") - } - } - return nil -} - -type optFmt struct { - format string -} - -var _ accessio.Option = (*optFmt)(nil) - -func StructureFormat(fmt string) accessio.Option { - return &optFmt{fmt} -} - -func (o *optFmt) ApplyOption(opts accessio.Options) error { - if s, ok := opts.(FormatVersionOption); ok { - s.SetFormatVersion(o.format) - return nil - } - return errors.ErrNotSupported("format version option") -} diff --git a/pkg/contexts/oci/repositories/artifactset/repo_test.go b/pkg/contexts/oci/repositories/artifactset/repo_test.go deleted file mode 100644 index 0d7f37400..000000000 --- a/pkg/contexts/oci/repositories/artifactset/repo_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package artifactset_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "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/oci/cpi" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/env/builder" - "github.com/open-component-model/ocm/pkg/mime" -) - -var _ = Describe("", func() { - var env *builder.Builder - - BeforeEach(func() { - env = builder.NewBuilder() - // ocmlog.Context().AddRule(logging.NewConditionRule(logging.DebugLevel, accessio.ALLOC_REALM)) - }) - - AfterEach(func() { - env.Cleanup() - }) - - It("maps artifact set to repo", func() { - env.ArtifactSet("/tmp/set", accessio.FormatDirectory, func() { - env.Manifest("v1", func() { - env.Config(func() { - env.BlobStringData(mime.MIME_JSON, "{}") - }) - env.Layer(func() { - env.BlobStringData(mime.MIME_OCTET, "testdata") - }) - }) - }) - - spec, err := artifactset.NewRepositorySpec(accessobj.ACC_READONLY, "/tmp/set", accessio.PathFileSystem(env)) - Expect(err).To(Succeed()) - - r, err := cpi.DefaultContext().RepositoryForSpec(spec) - Expect(err).To(Succeed()) - defer Close(r) - ns, err := r.LookupNamespace("") - Expect(err).To(Succeed()) - defer Close(ns) - - Expect(ns.ListTags()).To(Equal([]string{"v1"})) - - a, err := ns.GetArtifact("v1") - Expect(err).To(Succeed()) - defer Close(a) - - Expect(a.IsManifest()).To(BeTrue()) - m := a.ManifestAccess() - - cfg, err := m.GetConfigBlob() - Expect(err).To(Succeed()) - Expect(cfg.Get()).To(Equal([]byte("{}"))) - - Expect(len(m.GetDescriptor().Layers)).To(Equal(1)) - blob, err := m.GetBlob(m.GetDescriptor().Layers[0].Digest) - Expect(err).To(Succeed()) - Expect(blob.Get()).To(Equal([]byte("testdata"))) - }) -}) diff --git a/pkg/contexts/oci/repositories/artifactset/repository.go b/pkg/contexts/oci/repositories/artifactset/repository.go deleted file mode 100644 index c0167c9aa..000000000 --- a/pkg/contexts/oci/repositories/artifactset/repository.go +++ /dev/null @@ -1,111 +0,0 @@ -package artifactset - -import ( - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/vfsattr" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" -) - -type RepositoryImpl struct { - cpi.RepositoryImplBase - spec *RepositorySpec - arch *ArtifactSet -} - -var _ cpi.RepositoryImpl = (*RepositoryImpl)(nil) - -func NewRepository(ctx cpi.Context, s *RepositorySpec) (cpi.Repository, error) { - if s.PathFileSystem == nil { - s.PathFileSystem = vfsattr.Get(ctx) - } - r := &RepositoryImpl{ - RepositoryImplBase: cpi.NewRepositoryImplBase(ctx), - spec: s, - } - _, err := r.open() - if err != nil { - return nil, err - } - return cpi.NewRepository(r, "OCI artifactset"), nil -} - -func (r *RepositoryImpl) Get() *ArtifactSet { - if r.arch != nil { - return r.arch - } - return nil -} - -func (r *RepositoryImpl) open() (*ArtifactSet, error) { - a, err := Open(r.spec.AccessMode, r.spec.FilePath, 0o700, &Options{}, &r.spec.Options, accessio.PathFileSystem(r.spec.PathFileSystem)) - if err != nil { - return nil, err - } - r.arch = a - return a, nil -} - -func (r *RepositoryImpl) GetSpecification() cpi.RepositorySpec { - return r.spec -} - -func (r *RepositoryImpl) NamespaceLister() cpi.NamespaceLister { - return anonymous -} - -func (r *RepositoryImpl) ExistsArtifact(name string, ref string) (bool, error) { - if name != "" { - return false, nil - } - return r.arch.HasArtifact(ref) -} - -func (r *RepositoryImpl) LookupArtifact(name string, ref string) (cpi.ArtifactAccess, error) { - if name != "" { - return nil, cpi.ErrUnknownArtifact(name, ref) - } - return r.arch.GetArtifact(ref) -} - -func (r *RepositoryImpl) LookupNamespace(name string) (cpi.NamespaceAccess, error) { - if name != "" { - return nil, errors.ErrNotSupported("namespace", name) - } - return r.arch.Dup() -} - -func (r RepositoryImpl) Close() error { - if r.arch != nil { - return r.arch.Close() - } - return nil -} - -// NamespaceLister handles the namespaces provided by an artifact set. -// This is always single anonymous namespace, which by ddefinition -// is the empty string. -type NamespaceLister struct{} - -var anonymous cpi.NamespaceLister = &NamespaceLister{} - -// NumNamespaces returns the number of namespaces with a given prefix -// for an artifact set. This is either one (the anonymous namespace) if -// the prefix is empty (all namespaces) or zero if a prefix is given. -func (n *NamespaceLister) NumNamespaces(prefix string) (int, error) { - if prefix == "" { - return 1, nil - } - return 0, nil -} - -// GetNamespaces returns namespaces with a given prefix. -// This is the anonymous namespace ("") for an empty prefix -// or no namespace at all if a prefix is given. -func (n *NamespaceLister) GetNamespaces(prefix string, closure bool) ([]string, error) { - if prefix == "" { - return []string{""}, nil - } - return nil, nil -} diff --git a/pkg/contexts/oci/repositories/artifactset/state.go b/pkg/contexts/oci/repositories/artifactset/state.go deleted file mode 100644 index 616af4aa5..000000000 --- a/pkg/contexts/oci/repositories/artifactset/state.go +++ /dev/null @@ -1,15 +0,0 @@ -package artifactset - -import ( - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/common/accessobj" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" -) - -// NewStateHandler implements the factory interface for the artifact set -// state descriptor handling -// Basically this is an index state. -func NewStateHandler(fs vfs.FileSystem) accessobj.StateHandler { - return &cpi.IndexStateHandler{} -} diff --git a/pkg/contexts/oci/repositories/artifactset/type.go b/pkg/contexts/oci/repositories/artifactset/type.go deleted file mode 100644 index 83f4937f3..000000000 --- a/pkg/contexts/oci/repositories/artifactset/type.go +++ /dev/null @@ -1,88 +0,0 @@ -package artifactset - -import ( - "github.com/mandelsoft/vfs/pkg/vfs" - - "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/oci/cpi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - Type = "ArtifactSet" - TypeV1 = Type + runtime.VersionSeparator + "v1" -) - -func init() { - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](Type)) - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1)) -} - -const ( - FORMAT_OCI = "oci/v1" - FORMAT_OCM = "ocm/v1" -) - -type RepositorySpec struct { - runtime.ObjectVersionedType `json:",inline"` - Options `json:",inline"` - - // FileFormat is the format of the repository file - FilePath string `json:"filePath"` - // AccessMode can be set to request readonly access or creation - AccessMode accessobj.AccessMode `json:"accessMode,omitempty"` - - FormatVersion string `json:"formatVersion,omitempty"` -} - -// NewRepositorySpec creates a new RepositorySpec. -func NewRepositorySpec(acc accessobj.AccessMode, filePath string, opts ...accessio.Option) (*RepositorySpec, error) { - o, err := accessio.AccessOptions(&Options{}, opts...) - if err != nil { - return nil, err - } - return &RepositorySpec{ - ObjectVersionedType: runtime.NewVersionedTypedObject(Type), - FilePath: filePath, - Options: *o.(*Options), - AccessMode: acc, - }, nil -} - -func (s *RepositorySpec) Name() string { - return s.FilePath -} - -func (s *RepositorySpec) GetFormatVersion() string { - if s.FormatVersion == "" { - return FORMAT_OCM - } - return s.FormatVersion -} - -func (s *RepositorySpec) UniformRepositorySpec() *cpi.UniformRepositorySpec { - u := &cpi.UniformRepositorySpec{ - Type: Type, - Info: s.FilePath, - } - return u -} - -func (a *RepositorySpec) GetType() string { - return Type -} - -func (a *RepositorySpec) Repository(ctx cpi.Context, creds credentials.Credentials) (cpi.Repository, error) { - return NewRepository(ctx, a) -} - -func (a *RepositorySpec) AsUniformSpec(cpi.Context) cpi.UniformRepositorySpec { - opts, _ := NewOptions(&a.Options) // now unknown option possible (same Options type) - p, err := vfs.Canonical(opts.GetPathFileSystem(), a.FilePath, false) - if err != nil { - return cpi.UniformRepositorySpec{Type: a.GetKind(), Info: a.FilePath} - } - return cpi.UniformRepositorySpec{Type: a.GetKind(), Info: p} -} diff --git a/pkg/contexts/oci/repositories/artifactset/uniform.go b/pkg/contexts/oci/repositories/artifactset/uniform.go deleted file mode 100644 index a624c38e1..000000000 --- a/pkg/contexts/oci/repositories/artifactset/uniform.go +++ /dev/null @@ -1,49 +0,0 @@ -package artifactset - -import ( - "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/datacontext/attrs/vfsattr" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" -) - -func init() { - h := &repospechandler{} - cpi.RegisterRepositorySpecHandler(h, "") - cpi.RegisterRepositorySpecHandler(h, Type) -} - -type repospechandler struct{} - -func (h *repospechandler) MapReference(ctx cpi.Context, u *cpi.UniformRepositorySpec) (cpi.RepositorySpec, error) { - path := u.Info - if u.Info == "" { - if u.Host == "" || u.Type == "" { - return nil, nil - } - path = u.Host - } - fs := vfsattr.Get(ctx) - - hint, f := accessobj.MapType(u.TypeHint, Type, accessio.FormatDirectory, false) - if !u.CreateIfMissing { - hint = "" - } - - create, ok, err := accessobj.CheckFile(Type, hint, accessio.TypeForTypeSpec(u.Type) == Type, path, fs, ArtifactSetDescriptorFileName) - if err == nil && !ok { - create, ok, err = accessobj.CheckFile(Type, hint, accessio.TypeForTypeSpec(u.Type) == Type, path, fs, OCIArtifactSetDescriptorFileName) - } - - if !ok || err != nil { - return nil, err - } - - mode := accessobj.ACC_WRITABLE - createHint := accessio.FormatNone - if create { - mode |= accessobj.ACC_CREATE - createHint = f - } - return NewRepositorySpec(mode, path, createHint, accessio.PathFileSystem(fs)) -} diff --git a/pkg/contexts/oci/repositories/ctf/ctf_test.go b/pkg/contexts/oci/repositories/ctf/ctf_test.go deleted file mode 100644 index ac0ad430d..000000000 --- a/pkg/contexts/oci/repositories/ctf/ctf_test.go +++ /dev/null @@ -1,195 +0,0 @@ -package ctf_test - -import ( - "archive/tar" - "compress/gzip" - "io" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf/testhelper" - - "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/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/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf" - ocmlog "github.com/open-component-model/ocm/pkg/logging" - "github.com/open-component-model/ocm/pkg/refmgmt" -) - -var _ = Describe("ctf management", func() { - var tempfs vfs.FileSystem - - var spec *ctf.RepositorySpec - - ocmlog.Context().AddRule(logging.NewConditionRule(logging.TraceLevel, refmgmt.ALLOC_REALM)) - - BeforeEach(func() { - t, err := osfs.NewTempFileSystem() - Expect(err).To(Succeed()) - tempfs = t - - spec, err = ctf.NewRepositorySpec(accessobj.ACC_CREATE, "test", accessio.PathFileSystem(tempfs), accessobj.FormatDirectory) - Expect(err).To(Succeed()) - }) - - AfterEach(func() { - vfs.Cleanup(tempfs) - }) - - It("instantiate filesystem ctf", func() { - var finalize finalizer.Finalizer - defer Defer(finalize.Finalize) - - r := Must(ctf.FormatDirectory.Create(oci.DefaultContext(), "test", &spec.StandardOptions, 0o700)) - finalize.Close(r) - Expect(vfs.DirExists(tempfs, "test/"+ctf.BlobsDirectoryName)).To(BeTrue()) - - sub := finalize.Nested() - n := Must(r.LookupNamespace("mandelsoft/test")) - sub.Close(n) - DefaultManifestFill(n) - Expect(sub.Finalize()).To(Succeed()) - - Expect(r.ExistsArtifact("mandelsoft/test", TAG)).To(BeTrue()) - - art := Must(r.LookupArtifact("mandelsoft/test", TAG)) - Close(art, "art") - - Expect(finalize.Finalize()).To(Succeed()) - - Expect(vfs.FileExists(tempfs, "test/"+ctf.ArtifactIndexFileName)).To(BeTrue()) - - infos, err := vfs.ReadDir(tempfs, "test/"+artifactset.BlobsDirectoryName) - Expect(err).To(Succeed()) - blobs := []string{} - for _, fi := range infos { - blobs = append(blobs, fi.Name()) - } - Expect(blobs).To(ContainElements( - "sha256."+DIGEST_MANIFEST, - "sha256."+DIGEST_CONFIG, - "sha256."+DIGEST_LAYER)) - }) - - It("instantiate filesystem ctf", func() { - r, err := spec.Repository(nil, nil) - Expect(err).To(Succeed()) - Expect(vfs.DirExists(tempfs, "test/"+ctf.BlobsDirectoryName)).To(BeTrue()) - - n, err := r.LookupNamespace("mandelsoft/test") - Expect(err).To(Succeed()) - DefaultManifestFill(n) - - Expect(n.Close()).To(Succeed()) - Expect(r.Close()).To(Succeed()) - Expect(vfs.FileExists(tempfs, "test/"+ctf.ArtifactIndexFileName)).To(BeTrue()) - - infos, err := vfs.ReadDir(tempfs, "test/"+artifactset.BlobsDirectoryName) - Expect(err).To(Succeed()) - blobs := []string{} - for _, fi := range infos { - blobs = append(blobs, fi.Name()) - } - Expect(blobs).To(ContainElements( - "sha256."+DIGEST_MANIFEST, - "sha256."+DIGEST_CONFIG, - "sha256."+DIGEST_LAYER)) - }) - - It("instantiate tgz artifact", func() { - ctf.FormatTGZ.ApplyOption(&spec.StandardOptions) - spec.FilePath = "test.tgz" - r, err := spec.Repository(nil, nil) - Expect(err).To(Succeed()) - - n, err := r.LookupNamespace("mandelsoft/test") - Expect(err).To(Succeed()) - DefaultManifestFill(n) - - Expect(n.Close()).To(Succeed()) - Expect(r.Close()).To(Succeed()) - Expect(vfs.FileExists(tempfs, "test.tgz")).To(BeTrue()) - - file, err := tempfs.Open("test.tgz") - Expect(err).To(Succeed()) - defer file.Close() - zip, err := gzip.NewReader(file) - Expect(err).To(Succeed()) - defer zip.Close() - tr := tar.NewReader(zip) - - files := []string{} - for { - header, err := tr.Next() - if err != nil { - if err == io.EOF { - break - } - Fail(err.Error()) - } - - switch header.Typeflag { - case tar.TypeDir: - Expect(header.Name).To(Equal(artifactset.BlobsDirectoryName)) - case tar.TypeReg: - files = append(files, header.Name) - } - } - Expect(files).To(ContainElements( - ctf.ArtifactIndexFileName, - "blobs/sha256."+DIGEST_MANIFEST, - "blobs/sha256."+DIGEST_CONFIG, - "blobs/sha256."+DIGEST_LAYER)) - }) - - Context("manifest", func() { - It("read from filesystem ctf", func() { - r, err := spec.Repository(nil, nil) - Expect(err).To(Succeed()) - Expect(vfs.DirExists(tempfs, "test/"+ctf.BlobsDirectoryName)).To(BeTrue()) - n, err := r.LookupNamespace("mandelsoft/test") - Expect(err).To(Succeed()) - DefaultManifestFill(n) - Expect(n.Close()).To(Succeed()) - Expect(r.Close()).To(Succeed()) - - r, err = ctf.Open(nil, accessobj.ACC_READONLY, "test", 0, accessio.PathFileSystem(tempfs)) - Expect(err).To(Succeed()) - defer r.Close() - - n, err = r.LookupNamespace("mandelsoft/test") - Expect(err).To(Succeed()) - - art, err := n.GetArtifact("sha256:" + DIGEST_MANIFEST) - Expect(err).To(Succeed()) - CheckArtifact(art) - art, err = n.GetArtifact(TAG) - Expect(err).To(Succeed()) - b, err := art.GetDescriptor().ToBlobAccess() - Expect(err).To(Succeed()) - Expect(b.Digest()).To(Equal(digest.Digest("sha256:" + DIGEST_MANIFEST))) - - _, err = n.GetArtifact("dummy") - Expect(err).To(Equal(errors.ErrNotFound(cpi.KIND_OCIARTIFACT, "dummy", "mandelsoft/test"))) - - Expect(n.AddBlob(blobaccess.ForString("", "dummy"))).To(Equal(accessobj.ErrReadOnly)) - - n, err = r.LookupNamespace("mandelsoft/other") - Expect(err).To(Succeed()) - _, err = n.GetArtifact("sha256:" + DIGEST_MANIFEST) - Expect(err).To(Equal(errors.ErrNotFound(cpi.KIND_OCIARTIFACT, "sha256:"+DIGEST_MANIFEST, "mandelsoft/other"))) - }) - }) -}) diff --git a/pkg/contexts/oci/repositories/ctf/format.go b/pkg/contexts/oci/repositories/ctf/format.go deleted file mode 100644 index 64167460f..000000000 --- a/pkg/contexts/oci/repositories/ctf/format.go +++ /dev/null @@ -1,171 +0,0 @@ -package ctf - -import ( - "sort" - "strings" - "sync" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/blobaccess/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/oci/cpi" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf/format" -) - -const ( - ArtifactIndexFileName = format.ArtifactIndexFileName - BlobsDirectoryName = format.BlobsDirectoryName -) - -var accessObjectInfo = &accessobj.DefaultAccessObjectInfo{ - DescriptorFileName: ArtifactIndexFileName, - ObjectTypeName: "repository", - ElementDirectoryName: BlobsDirectoryName, - ElementTypeName: "blob", - DescriptorHandlerFactory: NewStateHandler, -} - -type Object = Repository - -type FormatHandler interface { - accessio.Option - - Format() accessio.FileFormat - - Open(ctx cpi.ContextProvider, acc accessobj.AccessMode, path string, opts accessio.Options) (*Object, error) - Create(ctx cpi.ContextProvider, path string, opts accessio.Options, mode vfs.FileMode) (*Object, error) - Write(obj *Object, path string, opts accessio.Options, mode vfs.FileMode) error -} - -type formatHandler struct { - accessobj.FormatHandler -} - -var ( - FormatDirectory = RegisterFormat(accessobj.FormatDirectory) - FormatTAR = RegisterFormat(accessobj.FormatTAR) - FormatTGZ = RegisterFormat(accessobj.FormatTGZ) -) - -//////////////////////////////////////////////////////////////////////////////// - -var ( - fileFormats = map[accessio.FileFormat]FormatHandler{} - lock sync.RWMutex -) - -func RegisterFormat(f accessobj.FormatHandler) FormatHandler { - lock.Lock() - defer lock.Unlock() - h := &formatHandler{f} - fileFormats[f.Format()] = h - return h -} - -func GetFormats() []string { - lock.RLock() - defer lock.RUnlock() - return accessio.GetFormatsFor(fileFormats) -} - -func GetFormat(name accessio.FileFormat) FormatHandler { - lock.RLock() - defer lock.RUnlock() - return fileFormats[name] -} - -func SupportedFormats() []accessio.FileFormat { - lock.RLock() - defer lock.RUnlock() - result := make([]accessio.FileFormat, 0, len(fileFormats)) - for f := range fileFormats { - result = append(result, f) - } - sort.Slice(result, func(i, j int) bool { return strings.Compare(string(result[i]), string(result[j])) < 0 }) - return result -} - -//////////////////////////////////////////////////////////////////////////////// - -const ( - ACC_CREATE = accessobj.ACC_CREATE - ACC_WRITABLE = accessobj.ACC_WRITABLE - ACC_READONLY = accessobj.ACC_READONLY -) - -func OpenFromBlob(ctx cpi.ContextProvider, acc accessobj.AccessMode, blob blobaccess.BlobAccess, opts ...accessio.Option) (*Object, error) { - o, err := accessio.AccessOptions(nil, opts...) - if err != nil { - return nil, err - } - if o.GetFile() != nil || o.GetReader() != nil { - return nil, errors.ErrInvalid("file or reader option nor possible for blob access") - } - reader, err := blob.Reader() - if err != nil { - return nil, err - } - defer reader.Close() - o.SetReader(reader) - fmt := accessio.FormatTar - mime := blob.MimeType() - if strings.HasSuffix(mime, "+gzip") { - fmt = accessio.FormatTGZ - } - o.SetFileFormat(fmt) - return Open(ctx, acc&accessobj.ACC_READONLY, "", 0, o) -} - -func Open(ctx cpi.ContextProvider, acc accessobj.AccessMode, path string, mode vfs.FileMode, opts ...accessio.Option) (*Object, error) { - o, create, err := accessobj.HandleAccessMode(acc, path, nil, opts...) - if err != nil { - return nil, err - } - h, ok := fileFormats[*o.GetFileFormat()] - if !ok { - return nil, errors.ErrUnknown(accessobj.KIND_FILEFORMAT, o.GetFileFormat().String()) - } - if create { - return h.Create(cpi.FromProvider(ctx), path, o, mode) - } - return h.Open(cpi.FromProvider(ctx), acc, path, o) -} - -func Create(ctx cpi.ContextProvider, acc accessobj.AccessMode, path string, mode vfs.FileMode, opts ...accessio.Option) (*Object, error) { - o, err := accessio.AccessOptions(nil, opts...) - if err != nil { - return nil, err - } - o.DefaultFormat(accessio.FormatDirectory) - h, ok := fileFormats[*o.GetFileFormat()] - if !ok { - return nil, errors.ErrUnknown(accessobj.KIND_FILEFORMAT, o.GetFileFormat().String()) - } - return h.Create(ctx.OCIContext(), path, o, mode) -} - -func (h *formatHandler) Open(ctx cpi.ContextProvider, acc accessobj.AccessMode, path string, opts accessio.Options) (*Object, error) { - obj, err := h.FormatHandler.Open(accessObjectInfo, acc, path, opts) - if err != nil { - return nil, err - } - spec, err := NewRepositorySpec(acc, path, opts) - return _Wrap(ctx, spec, obj, err) -} - -func (h *formatHandler) Create(ctx cpi.ContextProvider, path string, opts accessio.Options, mode vfs.FileMode) (*Object, error) { - obj, err := h.FormatHandler.Create(accessObjectInfo, path, opts, mode) - if err != nil { - return nil, err - } - spec, err := NewRepositorySpec(accessobj.ACC_CREATE, path, opts) - return _Wrap(ctx, spec, obj, err) -} - -// WriteToFilesystem writes the current object to a filesystem. -func (h *formatHandler) Write(obj *Object, path string, opts accessio.Options, mode vfs.FileMode) error { - return h.FormatHandler.Write(obj.impl.base.Access(), path, opts, mode) -} diff --git a/pkg/contexts/oci/repositories/ctf/format/const.go b/pkg/contexts/oci/repositories/ctf/format/const.go deleted file mode 100644 index 29abf7e66..000000000 --- a/pkg/contexts/oci/repositories/ctf/format/const.go +++ /dev/null @@ -1,20 +0,0 @@ -package format - -import ( - "github.com/open-component-model/ocm/pkg/common/accessobj" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" -) - -const ( - DirMode = accessobj.DirMode - FileMode = accessobj.FileMode -) - -var ModTime = accessobj.ModTime - -const ( - // BlobsDirectoryName is the name of the directory holding the artifact archives. - BlobsDirectoryName = artifactset.BlobsDirectoryName - // ArtifactIndexFileName is the artifact index descriptor name for CommanTransportFormat. - ArtifactIndexFileName = "artifact-index.json" -) diff --git a/pkg/contexts/oci/repositories/ctf/index/index.go b/pkg/contexts/oci/repositories/ctf/index/index.go deleted file mode 100644 index 375ff5c69..000000000 --- a/pkg/contexts/oci/repositories/ctf/index/index.go +++ /dev/null @@ -1,216 +0,0 @@ -package index - -import ( - "sort" - "strings" - "sync" - - "github.com/opencontainers/go-digest" - "github.com/opencontainers/image-spec/specs-go" - - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" -) - -type RepositoryIndex struct { - lock sync.RWMutex - byDigest map[digest.Digest][]*ArtifactMeta - byRepository map[string]map[string]*ArtifactMeta -} - -func NewMeta(repo string, tag string, digest digest.Digest) *ArtifactMeta { - return &ArtifactMeta{ - Repository: repo, - Tag: tag, - Digest: digest, - } -} - -func NewRepositoryIndex() *RepositoryIndex { - return &RepositoryIndex{ - byDigest: map[digest.Digest][]*ArtifactMeta{}, - byRepository: map[string]map[string]*ArtifactMeta{}, - } -} - -func (r *RepositoryIndex) RepositoryList() []string { - result := []string{} - for k := range r.byRepository { - result = append(result, k) - } - return result -} - -func (r *RepositoryIndex) AddTagsFor(repo string, digest digest.Digest, tags ...string) error { - r.lock.Lock() - defer r.lock.Unlock() - - a := r.getArtifactInfo(repo, digest.String()) - if a == nil { - return cpi.ErrUnknownArtifact(repo, digest.String()) - } - for _, tag := range tags { - n := *a - n.Tag = tag - r.addArtifactInfo(n) - } - return nil -} - -func (r *RepositoryIndex) AddArtifactInfo(n *ArtifactMeta) { - r.lock.Lock() - defer r.lock.Unlock() - r.addArtifactInfo(*n) -} - -func (r *RepositoryIndex) addArtifactInfo(m ArtifactMeta) { - repos := r.byRepository[m.Repository] - if len(repos) == 0 { - repos = map[string]*ArtifactMeta{} - r.byRepository[m.Repository] = repos - } - - list := r.byDigest[m.Digest] - if list == nil { - list = []*ArtifactMeta{&m} - } else { - for _, e := range list { - if m.Repository == e.Repository && m.Digest == e.Digest { - if e.Tag == "" || e.Tag == m.Tag { - e.Tag = m.Tag - if e.Tag != "" { - repos[m.Tag] = e - } - return - } - } - } - list = append(list, &m) - } - r.byDigest[m.Digest] = list - - repos["@"+m.Digest.String()] = &m - if m.Tag != "" { - repos[m.Tag] = &m - } -} - -func (r *RepositoryIndex) HasArtifact(repo, tag string) bool { - r.lock.RLock() - defer r.lock.RUnlock() - repos := r.byRepository[repo] - if repos == nil { - return false - } - m := repos[tag] - return m != nil -} - -func (r *RepositoryIndex) GetTags(repo string) []string { - r.lock.RLock() - defer r.lock.RUnlock() - - repos := r.byRepository[repo] - if repos == nil { - return nil - } - result := []string{} - digests := map[digest.Digest]bool{} - for t, a := range repos { - if !strings.HasPrefix(t, "@") { - result = append(result, t) - digests[a.Digest] = true - } else if !digests[a.Digest] { - digests[a.Digest] = false - } - } - /* TODO: how to query untagged entries at api level - for d, found := range digests { - if !found { - result = append(result, "@"+d.String()) - } - } - */ - return result -} - -func (r *RepositoryIndex) GetArtifacts(repo string) []string { - r.lock.RLock() - defer r.lock.RUnlock() - - repos := r.byRepository[repo] - if repos == nil { - return nil - } - result := []string{} - for t := range repos { - result = append(result, t) - } - return result -} - -func (r *RepositoryIndex) GetArtifactInfos(digest digest.Digest) []*ArtifactMeta { - r.lock.RLock() - defer r.lock.RUnlock() - return r.byDigest[digest] -} - -func (r *RepositoryIndex) GetArtifactInfo(repo, reference string) *ArtifactMeta { - r.lock.RLock() - defer r.lock.RUnlock() - return r.getArtifactInfo(repo, reference) -} - -func (r *RepositoryIndex) getArtifactInfo(repo, reference string) *ArtifactMeta { - repos := r.byRepository[repo] - if repos == nil { - return nil - } - m := repos[reference] - if m == nil && !strings.HasPrefix(reference, "@") { - m = repos["@"+reference] - } - if m == nil { - return nil - } - result := *m - return &result -} - -func (r *RepositoryIndex) GetDescriptor() *ArtifactIndex { - r.lock.RLock() - defer r.lock.RUnlock() - index := &ArtifactIndex{ - Versioned: specs.Versioned{SchemaVersion}, - } - - repos := make([]string, len(r.byRepository)) - i := 0 - for repo := range r.byRepository { - repos[i] = repo - i++ - } - sort.Strings(repos) - for _, name := range repos { - repo := r.byRepository[name] - versions := make([]string, len(repo)) - i := 0 - for vers := range repo { - versions[i] = vers - i++ - } - sort.Strings(versions) - - for _, name := range versions { - vers := repo[name] - if "@"+vers.Digest.String() != name || vers.Tag == "" { - d := &ArtifactMeta{ - Repository: vers.Repository, - Tag: vers.Tag, - Digest: vers.Digest, - } - index.Index = append(index.Index, *d) - } - } - } - return index -} diff --git a/pkg/contexts/oci/repositories/ctf/namespace.go b/pkg/contexts/oci/repositories/ctf/namespace.go deleted file mode 100644 index beefb173f..000000000 --- a/pkg/contexts/oci/repositories/ctf/namespace.go +++ /dev/null @@ -1,100 +0,0 @@ -package ctf - -import ( - "github.com/mandelsoft/goutils/errors" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi/support" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf/index" -) - -func NewNamespace(repo *RepositoryImpl, name string) (cpi.NamespaceAccess, error) { - return support.NewNamespaceAccess(name, newNamespaceContainer(repo), repo, "CTF namespace") -} - -type namespaceContainer struct { - impl support.NamespaceAccessImpl - repo *RepositoryImpl -} - -var _ support.NamespaceContainer = (*namespaceContainer)(nil) - -func newNamespaceContainer(repo *RepositoryImpl) support.NamespaceContainer { - return &namespaceContainer{ - repo: repo, - } -} - -func (n *namespaceContainer) SetImplementation(impl support.NamespaceAccessImpl) { - n.impl = impl -} - -func (n *namespaceContainer) IsReadOnly() bool { - return n.repo.IsReadOnly() -} - -func (n *namespaceContainer) Close() error { - return nil -} - -func (n *namespaceContainer) GetBlobDescriptor(digest digest.Digest) *cpi.Descriptor { - return nil -} - -func (n *namespaceContainer) ListTags() ([]string, error) { - return n.repo.getIndex().GetTags(n.impl.GetNamespace()), nil // return digests as tags, also -} - -func (n *namespaceContainer) GetBlobData(digest digest.Digest) (int64, cpi.DataAccess, error) { - return n.repo.base.GetBlobData(digest) -} - -func (n *namespaceContainer) AddBlob(blob cpi.BlobAccess) error { - n.repo.base.Lock() - defer n.repo.base.Unlock() - - return n.repo.base.AddBlob(blob) -} - -func (n *namespaceContainer) GetArtifact(i support.NamespaceAccessImpl, vers string) (cpi.ArtifactAccess, error) { - meta := n.repo.getIndex().GetArtifactInfo(n.impl.GetNamespace(), vers) - if meta == nil { - return nil, errors.ErrNotFound(cpi.KIND_OCIARTIFACT, vers, n.impl.GetNamespace()) - } - return n.repo.base.GetArtifact(i, meta.Digest) -} - -func (n *namespaceContainer) HasArtifact(vers string) (bool, error) { - meta := n.repo.getIndex().GetArtifactInfo(n.impl.GetNamespace(), vers) - return meta != nil, nil -} - -func (n *namespaceContainer) AddArtifact(artifact cpi.Artifact, tags ...string) (access blobaccess.BlobAccess, err error) { - n.repo.base.Lock() - defer n.repo.base.Unlock() - - blob, err := n.repo.base.AddArtifactBlob(artifact) - if err != nil { - return nil, err - } - n.repo.getIndex().AddArtifactInfo(&index.ArtifactMeta{ - Repository: n.impl.GetNamespace(), - Tag: "", - Digest: blob.Digest(), - }) - return blob, n.AddTags(blob.Digest(), tags...) -} - -func (n *namespaceContainer) AddTags(digest digest.Digest, tags ...string) error { - return n.repo.getIndex().AddTagsFor(n.impl.GetNamespace(), digest, tags...) -} - -func (n *namespaceContainer) NewArtifact(i support.NamespaceAccessImpl, art ...cpi.Artifact) (cpi.ArtifactAccess, error) { - if n.IsReadOnly() { - return nil, accessio.ErrReadOnly - } - return support.NewArtifact(i, art...) -} diff --git a/pkg/contexts/oci/repositories/ctf/repository.go b/pkg/contexts/oci/repositories/ctf/repository.go deleted file mode 100644 index d65dfe3e8..000000000 --- a/pkg/contexts/oci/repositories/ctf/repository.go +++ /dev/null @@ -1,143 +0,0 @@ -package ctf - -import ( - "github.com/mandelsoft/vfs/pkg/vfs" - - "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/datacontext/attrs/vfsattr" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf/index" - "github.com/open-component-model/ocm/pkg/refmgmt" -) - -/* - A common transport archive is just a folder with artifact archives. - in tar format and an index.json file. The name of the archive - is the digest of the artifact descriptor. - - The artifact archive is a filesystem structure with a file - artifact-descriptor.json and a folder blobs containing - the flat blob files with the name according to the blob digest. - - Digests used as filename will replace the ":" by a "." -*/ - -type Repository struct { - cpi.Repository - impl *RepositoryImpl -} - -func (r *Repository) Write(path string, mode vfs.FileMode, opts ...accessio.Option) error { - if r.IsClosed() { - return cpi.ErrClosed - } - return r.impl.Write(path, mode, opts...) -} - -func (r *Repository) Close() error { // why ??? - return r.Repository.Close() -} - -//////////////////////////////////////////////////////////////////////////////// - -// RepositoryImpl is closed, if all views are released. -type RepositoryImpl struct { - cpi.RepositoryImplBase - - spec *RepositorySpec - base *artifactset.FileSystemBlobAccess -} - -var _ cpi.RepositoryImpl = (*RepositoryImpl)(nil) - -// New returns a new representation based repository. -func New(ctx cpi.Context, spec *RepositorySpec, setup accessobj.Setup, closer accessobj.Closer, mode vfs.FileMode) (*Repository, error) { - if spec.GetPathFileSystem() == nil { - spec.SetPathFileSystem(vfsattr.Get(ctx)) - } - base, err := accessobj.NewAccessObject(accessObjectInfo, spec.AccessMode, spec.GetRepresentation(), setup, closer, mode) - return _Wrap(ctx, spec, base, err) -} - -func _Wrap(ctx cpi.ContextProvider, spec *RepositorySpec, obj *accessobj.AccessObject, err error) (*Repository, error) { - if err != nil { - return nil, err - } - impl := &RepositoryImpl{ - RepositoryImplBase: cpi.NewRepositoryImplBase(cpi.FromProvider(ctx)), - spec: spec, - base: artifactset.NewFileSystemBlobAccess(obj), - } - r := cpi.NewRepository(impl, "OCI CTF") - return &Repository{r, impl}, nil -} - -func (r *RepositoryImpl) GetSpecification() cpi.RepositorySpec { - return r.spec -} - -func (r *RepositoryImpl) NamespaceLister() cpi.NamespaceLister { - return r -} - -func (r *RepositoryImpl) NumNamespaces(prefix string) (int, error) { - return len(cpi.FilterByNamespacePrefix(prefix, r.getIndex().RepositoryList())), nil -} - -func (r *RepositoryImpl) GetNamespaces(prefix string, closure bool) ([]string, error) { - return cpi.FilterChildren(closure, prefix, r.getIndex().RepositoryList()), nil -} - -//////////////////////////////////////////////////////////////////////////////// -// forward - -func (r *RepositoryImpl) IsReadOnly() bool { - return r.base.IsReadOnly() -} - -func (r *RepositoryImpl) Write(path string, mode vfs.FileMode, opts ...accessio.Option) error { - return r.base.Write(path, mode, opts...) -} - -func (r *RepositoryImpl) Update() error { - return r.base.Update() -} - -func (r *RepositoryImpl) Close() error { - return r.base.Close() -} - -func (a *RepositoryImpl) getIndex() *index.RepositoryIndex { - if a.IsReadOnly() { - return a.base.GetState().GetOriginalState().(*index.RepositoryIndex) - } - return a.base.GetState().GetState().(*index.RepositoryIndex) -} - -//////////////////////////////////////////////////////////////////////////////// -// cpi.Repository methods - -func (r *RepositoryImpl) ExistsArtifact(name string, tag string) (bool, error) { - return r.getIndex().HasArtifact(name, tag), nil -} - -func (r *RepositoryImpl) LookupArtifact(name string, ref string) (acc cpi.ArtifactAccess, err error) { - ns, err := NewNamespace(r, name) - if err != nil { - return nil, err - } - - defer refmgmt.PropagateCloseTemporary(&err, ns) // temporary namespace object not exposed. - - a := r.getIndex().GetArtifactInfo(name, ref) - if a == nil { - return nil, cpi.ErrUnknownArtifact(name, ref) - } - return ns.GetArtifact(ref) -} - -func (r *RepositoryImpl) LookupNamespace(name string) (cpi.NamespaceAccess, error) { - return NewNamespace(r, name) -} diff --git a/pkg/contexts/oci/repositories/ctf/state.go b/pkg/contexts/oci/repositories/ctf/state.go deleted file mode 100644 index 938d4fe8b..000000000 --- a/pkg/contexts/oci/repositories/ctf/state.go +++ /dev/null @@ -1,47 +0,0 @@ -package ctf - -import ( - "fmt" - "reflect" - - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/common/accessobj" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf/index" -) - -type StateHandler struct{} - -var _ accessobj.StateHandler = &StateHandler{} - -func NewStateHandler(fs vfs.FileSystem) accessobj.StateHandler { - return &StateHandler{} -} - -func (i StateHandler) Initial() interface{} { - return index.NewRepositoryIndex() -} - -func (i StateHandler) Encode(d interface{}) ([]byte, error) { - return index.Encode(d.(*index.RepositoryIndex).GetDescriptor()) -} - -func (i StateHandler) Decode(data []byte) (interface{}, error) { - idx, err := index.Decode(data) - if err != nil { - return nil, fmt.Errorf("unable to parse artifact index read from %s: %w", ArtifactIndexFileName, err) - } - if idx.SchemaVersion != index.SchemaVersion { - return nil, fmt.Errorf("unknown schema version %d for artifact index %s", index.SchemaVersion, ArtifactIndexFileName) - } - - artifacts := index.NewRepositoryIndex() - for i := range idx.Index { - artifacts.AddArtifactInfo(&idx.Index[i]) - } - return artifacts, nil -} - -func (i StateHandler) Equivalent(a, b interface{}) bool { - return reflect.DeepEqual(a, b) -} diff --git a/pkg/contexts/oci/repositories/ctf/type.go b/pkg/contexts/oci/repositories/ctf/type.go deleted file mode 100644 index 779170fe9..000000000 --- a/pkg/contexts/oci/repositories/ctf/type.go +++ /dev/null @@ -1,83 +0,0 @@ -package ctf - -import ( - "strings" - - "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/oci/cpi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - Type = cpi.CommonTransportFormat - TypeV1 = Type + runtime.VersionSeparator + "v1" -) - -func init() { - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](Type)) - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1)) -} - -// RepositorySpec describes an OCI registry interface backed by an oci registry. -type RepositorySpec struct { - runtime.ObjectVersionedType `json:",inline"` - accessio.StandardOptions `json:",inline"` - - // FilePath is the file for the repository in the filesystem.. - FilePath string `json:"filePath"` - // AccessMode can be set to request readonly access or creation - AccessMode accessobj.AccessMode `json:"accessMode,omitempty"` -} - -var _ cpi.RepositorySpec = (*RepositorySpec)(nil) - -var _ cpi.IntermediateRepositorySpecAspect = (*RepositorySpec)(nil) - -// NewRepositorySpec creates a new RepositorySpec. -func NewRepositorySpec(mode accessobj.AccessMode, filePath string, opts ...accessio.Option) (*RepositorySpec, error) { - o, err := accessio.AccessOptions(nil, opts...) - if err != nil { - return nil, err - } - if o.GetFileFormat() == nil { - for _, v := range SupportedFormats() { - if strings.HasSuffix(filePath, "."+v.String()) { - o.SetFileFormat(v) - break - } - } - } - o.Default() - return &RepositorySpec{ - ObjectVersionedType: runtime.NewVersionedTypedObject(Type), - FilePath: filePath, - StandardOptions: *o.(*accessio.StandardOptions), - AccessMode: mode, - }, nil -} - -func (a *RepositorySpec) IsIntermediate() bool { - return true -} - -func (a *RepositorySpec) GetType() string { - return Type -} - -func (s *RepositorySpec) Name() string { - return s.FilePath -} - -func (s *RepositorySpec) UniformRepositorySpec() *cpi.UniformRepositorySpec { - u := &cpi.UniformRepositorySpec{ - Type: Type, - Info: s.FilePath, - } - return u -} - -func (a *RepositorySpec) Repository(ctx cpi.Context, creds credentials.Credentials) (cpi.Repository, error) { - return Open(ctx, a.AccessMode, a.FilePath, 0o700, &a.StandardOptions) -} diff --git a/pkg/contexts/oci/repositories/ctf/uniform.go b/pkg/contexts/oci/repositories/ctf/uniform.go deleted file mode 100644 index 04bd5a1f6..000000000 --- a/pkg/contexts/oci/repositories/ctf/uniform.go +++ /dev/null @@ -1,68 +0,0 @@ -package ctf - -import ( - "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/datacontext/attrs/vfsattr" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" -) - -const AltType = "ctf" - -func init() { - h := &repospechandler{} - cpi.RegisterRepositorySpecHandler(h, "") - cpi.RegisterRepositorySpecHandler(h, Type) - cpi.RegisterRepositorySpecHandler(h, AltType) - for _, f := range SupportedFormats() { - cpi.RegisterRepositorySpecHandler(h, string(f)) - } -} - -type repospechandler struct{} - -func (h *repospechandler) MapReference(ctx cpi.Context, u *cpi.UniformRepositorySpec) (cpi.RepositorySpec, error) { - return MapReference(ctx, u) -} - -func explicit(t string) bool { - for _, f := range SupportedFormats() { - if t == string(f) { - return true - } - } - return t == Type || t == AltType -} - -func MapReference(ctx cpi.Context, u *cpi.UniformRepositorySpec) (cpi.RepositorySpec, error) { - path := u.Info - if u.Info == "" { - if u.Host == "" || u.Type == "" { - return nil, nil - } - path = u.Host - } - fs := vfsattr.Get(ctx) - - typ, _ := accessobj.MapType(u.Type, Type, accessio.FormatNone, true, AltType) - hint, f := accessobj.MapType(u.TypeHint, Type, accessio.FormatDirectory, true, AltType) - if !u.CreateIfMissing { - hint = "" - } - create, ok, err := accessobj.CheckFile(Type, hint, explicit(accessio.TypeForTypeSpec(u.Type)), path, fs, ArtifactIndexFileName) - if !ok || (err != nil && typ == "") { - if err != nil { - return nil, err - } - if !ok { - return nil, nil - } - } - mode := accessobj.ACC_WRITABLE - createHint := accessio.FormatNone - if create { - mode |= accessobj.ACC_CREATE - createHint = f - } - return NewRepositorySpec(mode, path, createHint, accessio.PathFileSystem(fs)) -} diff --git a/pkg/contexts/oci/repositories/docker/artifact.go b/pkg/contexts/oci/repositories/docker/artifact.go deleted file mode 100644 index 5a01ffb0b..000000000 --- a/pkg/contexts/oci/repositories/docker/artifact.go +++ /dev/null @@ -1,69 +0,0 @@ -package docker - -import ( - "sync" - - "github.com/containers/image/v5/types" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" -) - -type dockerSource struct { - lock sync.RWMutex - src types.ImageSource - img types.Image - refcount int -} - -var _ accessio.BlobSource = (*dockerSource)(nil) - -func newDockerSource(img types.Image, src types.ImageSource) *dockerSource { - return &dockerSource{ - src: src, - img: img, - refcount: 1, - } -} - -func (c *dockerSource) Ref() error { - c.lock.Lock() - defer c.lock.Unlock() - if c.refcount == 0 { - return accessio.ErrClosed - } - c.refcount++ - return nil -} - -func (c *dockerSource) Unref() error { - c.lock.Lock() - defer c.lock.Unlock() - if c.refcount == 0 { - return accessio.ErrClosed - } - c.refcount-- - return c.src.Close() -} - -func (d *dockerSource) GetBlobData(digest digest.Digest) (int64, blobaccess.DataAccess, error) { - info := d.img.ConfigInfo() - if info.Digest == digest { - data, err := d.img.ConfigBlob(dummyContext) - if err != nil { - return -1, nil, err - } - return info.Size, blobaccess.DataAccessForData(data), nil - } - info.Digest = "" - for _, l := range d.img.LayerInfos() { - if l.Digest == digest { - info = l - acc, err := NewDataAccess(d.src, info, false) - return l.Size, acc, err - } - } - return -1, nil, cpi.ErrBlobNotFound(digest) -} diff --git a/pkg/contexts/oci/repositories/docker/convert.go b/pkg/contexts/oci/repositories/docker/convert.go deleted file mode 100644 index 54247e271..000000000 --- a/pkg/contexts/oci/repositories/docker/convert.go +++ /dev/null @@ -1,203 +0,0 @@ -package docker - -import ( - "bytes" - "context" - "fmt" - "io" - - "github.com/containers/image/v5/image" - "github.com/containers/image/v5/manifest" - "github.com/containers/image/v5/types" - "github.com/opencontainers/go-digest" - "github.com/sirupsen/logrus" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" -) - -// fakeSource implements required methods to call the manifest conversion. -type fakeSource struct { - types.ImageSource - art cpi.BlobAccess - blobs cpi.BlobSource - ref types.ImageReference -} - -func (f *fakeSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) { - if instanceDigest != nil { - return nil, "", fmt.Errorf("manifest lists are not supported") - } - data, err := f.art.Get() - if err != nil { - return nil, "", err - } - return data, f.art.MimeType(), nil -} - -func (f *fakeSource) GetBlob(ctx context.Context, bi types.BlobInfo, bc types.BlobInfoCache) (io.ReadCloser, int64, error) { - _, blob, err := f.blobs.GetBlobData(bi.Digest) - if err != nil { - return nil, blobaccess.BLOB_UNKNOWN_SIZE, err - } - - r, err := blob.Reader() - return r, bi.Size, err -} - -func (f *fakeSource) Reference() types.ImageReference { - return f.ref -} - -//////////////////////////////////////////////////////////////////////////////// - -type artBlobCache struct { - access cpi.ArtifactAccess -} - -var _ accessio.BlobCache = (*artBlobCache)(nil) - -func ArtifactAsBlobCache(access cpi.ArtifactAccess) accessio.BlobCache { - return &artBlobCache{access} -} - -func (a *artBlobCache) Ref() error { - return nil -} - -func (a *artBlobCache) Unref() error { - return nil -} - -func (a *artBlobCache) GetBlobData(digest digest.Digest) (int64, blobaccess.DataAccess, error) { - blob, err := a.access.GetBlob(digest) - if err != nil { - return -1, nil, err - } - return blob.Size(), blob, err -} - -func (a *artBlobCache) AddBlob(blob blobaccess.BlobAccess) (int64, digest.Digest, error) { - err := a.access.AddBlob(blob) - if err != nil { - return -1, "", err - } - return blob.Size(), blob.Digest(), err -} - -func (c *artBlobCache) AddData(data blobaccess.DataAccess) (int64, digest.Digest, error) { - return c.AddBlob(blobaccess.ForDataAccess(blobaccess.BLOB_UNKNOWN_DIGEST, blobaccess.BLOB_UNKNOWN_SIZE, "", data)) -} - -//////////////////////////////////////////////////////////////////////////////// - -func blobSource(art cpi.Artifact, blobs accessio.BlobSource) (accessio.BlobSource, error) { - var err error - if blobs == nil { - if t, ok := art.(cpi.ArtifactAccess); !ok { - return nil, fmt.Errorf("blob source required") - } else { - blobs = ArtifactAsBlobCache(t) - } - } else { - if t, ok := art.(cpi.ArtifactAccess); ok { - blobs, err = accessio.NewCascadedBlobCacheForSource(blobs, ArtifactAsBlobCache(t)) - if err != nil { - return nil, err - } - } - } - return blobs, nil -} - -func Convert(art cpi.Artifact, blobs accessio.BlobSource, dst types.ImageDestination) (cpi.BlobAccess, error) { - blobs, err := blobSource(art, blobs) - if err != nil { - return nil, err - } - artblob, err := art.Blob() - if err != nil { - return nil, err - } - ociImage := &fakeSource{ - art: artblob, - blobs: blobs, - ref: dst.Reference(), - } - - m, err := art.Manifest() - if err != nil { - return nil, err - } - for i, l := range m.Layers { - size, blob, err := blobs.GetBlobData(l.Digest) - if err != nil { - return nil, err - } - r, err := blob.Reader() - if err != nil { - return nil, err - } - defer r.Close() - bi := types.BlobInfo{ - Digest: l.Digest, - Size: size, - URLs: l.URLs, - Annotations: l.Annotations, - MediaType: l.MediaType, - } - logrus.Infof("put blob for layer %d", i) - _, err = dst.PutBlob(dummyContext, r, bi, nil, false) - if err != nil { - return nil, err - } - } - - un := image.UnparsedInstance(ociImage, nil) - img, err := image.FromUnparsedImage(dummyContext, nil, un) - if err != nil { - return nil, err - } - - opts := types.ManifestUpdateOptions{ - ManifestMIMEType: manifest.DockerV2Schema2MediaType, - InformationOnly: types.ManifestUpdateInformation{ - Destination: dst, - }, - } - - img, err = img.UpdatedImage(dummyContext, opts) - if err != nil { - return nil, err - } - - bi := img.ConfigInfo() - blob, err := img.ConfigBlob(dummyContext) - if err != nil { - return nil, err - } - var reader io.ReadCloser - if blob == nil { - _, orig, err := blobs.GetBlobData(bi.Digest) - if err != nil { - return nil, err - } - reader, err = orig.Reader() - if err != nil { - return nil, err - } - } else { - reader = io.NopCloser(bytes.NewReader(blob)) - } - _, err = dst.PutBlob(dummyContext, reader, bi, nil, true) - if err != nil { - return nil, err - } - man, _, err := img.Manifest(dummyContext) - if err != nil { - return nil, err - } - - return artblob, dst.PutManifest(dummyContext, man, nil) -} diff --git a/pkg/contexts/oci/repositories/docker/namespace.go b/pkg/contexts/oci/repositories/docker/namespace.go deleted file mode 100644 index 0f6739bab..000000000 --- a/pkg/contexts/oci/repositories/docker/namespace.go +++ /dev/null @@ -1,278 +0,0 @@ -package docker - -import ( - "fmt" - "strings" - "sync" - - "github.com/containers/image/v5/image" - "github.com/containers/image/v5/types" - dockertypes "github.com/docker/docker/api/types/image" - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/logging" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi/support" - "github.com/open-component-model/ocm/pkg/contexts/oci/internal" -) - -type blobHandler struct { - accessio.BlobCache -} - -var _ support.BlobProvider = (*blobHandler)(nil) - -func newBlobHandler(cache accessio.BlobCache) support.BlobProvider { - return &blobHandler{cache} -} - -func (b blobHandler) AddBlob(access internal.BlobAccess) error { - _, _, err := b.BlobCache.AddBlob(access) - return err -} - -//////////////////////////////////////////////////////////////////////////////// - -// namespaceContainer delegates functionality but blob access to an underlying -// handler. -// blob access is handled locally. -type namespaceContainer struct { - *namespaceHandler - blobs support.BlobProvider -} - -var _ support.NamespaceContainer = (*namespaceContainer)(nil) - -func newNamespaceContainer(handler *namespaceHandler, blobs support.BlobProvider) *namespaceContainer { - return &namespaceContainer{ - namespaceHandler: handler, - blobs: blobs, - } -} - -func NewNamespace(repo *RepositoryImpl, name string) (cpi.NamespaceAccess, error) { - h, err := newNamespaceHandler(repo) - if err != nil { - return nil, err - } - // initial container wrapper releases base cache with close of namespace - // container on last namespace ref. - // base cache has initial user count of 1. - return support.NewNamespaceAccess(name, newNamespaceContainer(h, h.blobs), repo, "docker namespace") -} - -func (n *namespaceContainer) Close() error { - n.lock.Lock() - defer n.lock.Unlock() - - if n.blobs != nil { - err := n.blobs.Unref() - n.blobs = nil - if err != nil { - return fmt.Errorf("failed to unref: %w", err) - } - } - return nil -} - -func (n *namespaceContainer) GetBlobData(digest digest.Digest) (int64, cpi.DataAccess, error) { - return n.blobs.GetBlobData(digest) -} - -func (n *namespaceContainer) AddBlob(blob cpi.BlobAccess) error { - if err := n.blobs.AddBlob(blob); err != nil { - return fmt.Errorf("failed to add blob to cache: %w", err) - } - - return nil -} - -//////////////////////////////////////////////////////////////////////////////// - -type namespaceHandler struct { - impl support.NamespaceAccessImpl - lock sync.RWMutex - repo *RepositoryImpl - blobs support.BlobProvider - log logging.Logger -} - -func newNamespaceHandler(repo *RepositoryImpl) (*namespaceHandler, error) { - cache, err := accessio.NewCascadedBlobCache(nil) - if err != nil { - return nil, err - } - - return &namespaceHandler{ - repo: repo, - blobs: newBlobHandler(cache), - log: repo.GetContext().Logger(), - }, nil -} - -func (n *namespaceHandler) SetImplementation(impl support.NamespaceAccessImpl) { - n.impl = impl -} - -func (n *namespaceHandler) IsReadOnly() bool { - return n.repo.IsReadOnly() -} - -func (n *namespaceContainer) GetBlobDescriptor(digest digest.Digest) *cpi.Descriptor { - return nil -} - -func (n *namespaceHandler) ListTags() ([]string, error) { - opts := dockertypes.ListOptions{} - list, err := n.repo.client.ImageList(dummyContext, opts) - if err != nil { - return nil, err - } - var result []string - if n.impl.GetNamespace() == "" { - for _, e := range list { - // ID is always the config digest - // filter images without a repo tag for empty namespace - if len(e.RepoTags) == 0 { - d, err := digest.Parse(e.ID) - if err == nil { - result = append(result, d.String()[:12]) - } - } - } - } else { - prefix := n.impl.GetNamespace() + ":" - for _, e := range list { - for _, t := range e.RepoTags { - if strings.HasPrefix(t, prefix) { - result = append(result, t[len(prefix):]) - } - } - } - } - return result, nil -} - -func (n *namespaceHandler) GetArtifact(i support.NamespaceAccessImpl, vers string) (cpi.ArtifactAccess, error) { - ref, err := ParseRef(n.impl.GetNamespace(), vers) - if err != nil { - return nil, err - } - src, err := ref.NewImageSource(dummyContext, n.repo.sysctx) - if err != nil { - return nil, err - } - - opts := types.ManifestUpdateOptions{ - ManifestMIMEType: artdesc.MediaTypeImageManifest, - } - un := image.UnparsedInstance(src, nil) - img, err := image.FromUnparsedImage(dummyContext, n.repo.sysctx, un) - if err != nil { - src.Close() - return nil, err - } - - img, err = img.UpdatedImage(dummyContext, opts) - if err != nil { - src.Close() - return nil, err - } - - data, mime, err := img.Manifest(dummyContext) - if err != nil { - src.Close() - return nil, err - } - - cache, err := accessio.NewCascadedBlobCacheForSource(n.blobs, newDockerSource(img, src)) - if err != nil { - return nil, err - } - - priv := i.WithContainer(newNamespaceContainer(n, newBlobHandler(cache))) - // assure explicit close of wrapper container for artifact close - return support.NewArtifactForBlob(priv, blobaccess.ForData(mime, data), priv) -} - -func (n *namespaceHandler) HasArtifact(vers string) (bool, error) { - list, err := n.ListTags() - if err != nil { - return false, err - } - for _, e := range list { - if e == vers { - return true, nil - } - } - return false, nil -} - -func (n *namespaceContainer) AddArtifact(artifact cpi.Artifact, tags ...string) (access blobaccess.BlobAccess, err error) { - tag := "latest" - if len(tags) > 0 { - tag = tags[0] - } - ref, err := ParseRef(n.impl.GetNamespace(), tag) - if err != nil { - return nil, err - } - dst, err := ref.NewImageDestination(dummyContext, nil) - if err != nil { - return nil, err - } - defer dst.Close() - - blob, err := Convert(artifact, n.blobs, dst) - if err != nil { - return nil, err - } - err = dst.Commit(dummyContext, nil) - if err != nil { - return nil, err - } - - return blob, nil -} - -func (n *namespaceContainer) AddTags(digest digest.Digest, tags ...string) error { - if ok, _ := artdesc.IsDigest(digest.String()); ok { - return errors.ErrNotSupported("image access by digest") - } - - src := n.impl.GetNamespace() + ":" + digest.String() - - if pattern.MatchString(digest.String()) { - // this definitely no digest, but the library expects it this way - src = digest.String() - } - - for _, tag := range tags { - err := n.repo.client.ImageTag(dummyContext, src, n.impl.GetNamespace()+":"+tag) - if err != nil { - return fmt.Errorf("failed to add image tag: %w", err) - } - } - - return nil -} - -func (n *namespaceContainer) NewArtifact(i support.NamespaceAccessImpl, art ...cpi.Artifact) (cpi.ArtifactAccess, error) { - if n.IsReadOnly() { - return nil, accessio.ErrReadOnly - } - var m cpi.Artifact - if len(art) == 0 || art[0] == nil { - m = artdesc.NewManifest() - } else { - m = art[0] - if !m.IsValid() { - m = artdesc.NewManifest() - } - } - return support.NewArtifact(i, m) -} diff --git a/pkg/contexts/oci/repositories/docker/repository.go b/pkg/contexts/oci/repositories/docker/repository.go deleted file mode 100644 index 59b43b5db..000000000 --- a/pkg/contexts/oci/repositories/docker/repository.go +++ /dev/null @@ -1,121 +0,0 @@ -package docker - -import ( - "strings" - - "github.com/containers/image/v5/types" - dockertypes "github.com/docker/docker/api/types/image" - "github.com/docker/docker/client" - - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" -) - -type RepositoryImpl struct { - cpi.RepositoryImplBase - spec *RepositorySpec - sysctx *types.SystemContext - client *client.Client -} - -var _ cpi.RepositoryImpl = (*RepositoryImpl)(nil) - -func NewRepository(ctx cpi.Context, spec *RepositorySpec) (cpi.Repository, error) { - client, err := newDockerClient(spec.DockerHost) - if err != nil { - return nil, err - } - - sysctx := &types.SystemContext{ - DockerDaemonHost: client.DaemonHost(), - } - - i := &RepositoryImpl{ - RepositoryImplBase: cpi.NewRepositoryImplBase(ctx), - spec: spec, - sysctx: sysctx, - client: client, - } - return cpi.NewRepository(i, "docker"), nil -} - -func (r *RepositoryImpl) Close() error { - return nil -} - -func (r *RepositoryImpl) IsReadOnly() bool { - return true -} - -func (r *RepositoryImpl) GetSpecification() cpi.RepositorySpec { - return r.spec -} - -func (r *RepositoryImpl) NamespaceLister() cpi.NamespaceLister { - return r -} - -func (r *RepositoryImpl) NumNamespaces(prefix string) (int, error) { - repos, err := r.GetRepositories() - if err != nil { - return -1, err - } - return len(cpi.FilterByNamespacePrefix(prefix, repos)), nil -} - -func (r *RepositoryImpl) GetNamespaces(prefix string, closure bool) ([]string, error) { - repos, err := r.GetRepositories() - if err != nil { - return nil, err - } - return cpi.FilterChildren(closure, prefix, repos), nil -} - -func (r *RepositoryImpl) GetRepositories() ([]string, error) { - opts := dockertypes.ListOptions{} - list, err := r.client.ImageList(dummyContext, opts) - if err != nil { - return nil, err - } - var result cpi.StringList - for _, e := range list { - if len(e.RepoTags) > 0 { - for _, t := range e.RepoTags { - i := strings.Index(t, ":") - if i > 0 { - if t[:i] != "" { - result.Add(t[:i]) - } - } - } - } else { - result.Add("") - } - } - return result, nil -} - -func (r *RepositoryImpl) ExistsArtifact(name string, version string) (bool, error) { - ref, err := ParseRef(name, version) - if err != nil { - return false, err - } - opts := dockertypes.ListOptions{} - opts.Filters.Add("reference", ref.StringWithinTransport()) - list, err := r.client.ImageList(dummyContext, opts) - if err != nil { - return false, err - } - return len(list) > 0, nil -} - -func (r *RepositoryImpl) LookupArtifact(name string, version string) (cpi.ArtifactAccess, error) { - n, err := r.LookupNamespace(name) - if err != nil { - return nil, err - } - return n.GetArtifact(version) -} - -func (r *RepositoryImpl) LookupNamespace(name string) (cpi.NamespaceAccess, error) { - return NewNamespace(r, name) -} diff --git a/pkg/contexts/oci/repositories/docker/type.go b/pkg/contexts/oci/repositories/docker/type.go deleted file mode 100644 index b9572b7c7..000000000 --- a/pkg/contexts/oci/repositories/docker/type.go +++ /dev/null @@ -1,48 +0,0 @@ -package docker - -import ( - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -const ( - Type = "DockerDaemon" - TypeV1 = Type + runtime.VersionSeparator + "v1" -) - -func init() { - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](Type)) - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1)) -} - -// RepositorySpec describes an OCI registry interface backed by an oci registry. -type RepositorySpec struct { - runtime.ObjectVersionedType `json:",inline"` - DockerHost string `json:"dockerHost,omitempty"` -} - -// NewRepositorySpec creates a new RepositorySpec for an optional host. -func NewRepositorySpec(host ...string) *RepositorySpec { - return &RepositorySpec{ - ObjectVersionedType: runtime.NewVersionedTypedObject(Type), - DockerHost: utils.Optional(host...), - } -} - -func (a *RepositorySpec) GetType() string { - return Type -} - -func (a *RepositorySpec) Name() string { - return Type -} - -func (a *RepositorySpec) UniformRepositorySpec() *cpi.UniformRepositorySpec { - return cpi.UniformRepositorySpecForHostURL(Type, a.DockerHost) -} - -func (a *RepositorySpec) Repository(ctx cpi.Context, creds credentials.Credentials) (cpi.Repository, error) { - return NewRepository(ctx, a) -} diff --git a/pkg/contexts/oci/repositories/docker/uniform.go b/pkg/contexts/oci/repositories/docker/uniform.go deleted file mode 100644 index 9d4edd4bb..000000000 --- a/pkg/contexts/oci/repositories/docker/uniform.go +++ /dev/null @@ -1,26 +0,0 @@ -package docker - -import ( - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" -) - -func init() { - cpi.RegisterRepositorySpecHandler(&repospechandler{}, Type) -} - -type repospechandler struct{} - -func (h *repospechandler) MapReference(ctx cpi.Context, u *cpi.UniformRepositorySpec) (cpi.RepositorySpec, error) { - host := u.Host - if u.Scheme != "" && host != "" { - host = u.Scheme + "://" + u.Host - } - if u.Info != "" { - if u.Info == "default" { - host = "" - } else if host == "" { - host = u.Info - } - } - return NewRepositorySpec(host), nil -} diff --git a/pkg/contexts/oci/repositories/docker/utils.go b/pkg/contexts/oci/repositories/docker/utils.go deleted file mode 100644 index 336fd231a..000000000 --- a/pkg/contexts/oci/repositories/docker/utils.go +++ /dev/null @@ -1,124 +0,0 @@ -package docker - -import ( - "context" - "fmt" - "io" - "regexp" - "strings" - "sync" - - "github.com/containers/image/v5/docker/daemon" - "github.com/containers/image/v5/types" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" -) - -var dummyContext = context.Background() - -var pattern = regexp.MustCompile("^[0-9a-f]{12}$") - -func ParseGenericRef(ref string) (string, string, error) { - if strings.TrimSpace(ref) == "" { - return "", "", fmt.Errorf("invalid docker reference %q", ref) - } - parts := strings.Split(ref, ":") - if len(parts) > 2 { - return "", "", fmt.Errorf("invalid docker reference %q", ref) - } - if len(parts) == 1 { - // expect docker id - if pattern.MatchString(parts[0]) { - return "", parts[0], nil - } - } - _, err := daemon.ParseReference(ref) - if err != nil { - return "", "", err - } - return parts[0], parts[1], nil -} - -func ParseRef(name, version string) (types.ImageReference, error) { - if version == "" || name == "" { - id := version - if id == "" { - id = name - } - // check for docker daemon image id - if pattern.MatchString(id) { - // this definitely no digest, but the library expects it this way - return daemon.NewReference(digest.Digest(id), nil) - } - return nil, fmt.Errorf("no docker daemon image id") - } - return daemon.ParseReference(name + ":" + version) -} - -func ImageId(art cpi.Artifact) digest.Digest { - m, err := art.Manifest() - if err != nil { - return "" - } - return digest.Digest(m.Config.Digest.Hex()[:12]) -} - -// TODO add cache - -type dataAccess struct { - accessio.NopCloser - lock sync.Mutex - info types.BlobInfo - src types.ImageSource - reader io.ReadCloser -} - -var _ cpi.DataAccess = (*dataAccess)(nil) - -func NewDataAccess(src types.ImageSource, info types.BlobInfo, delayed bool) (*dataAccess, error) { - var reader io.ReadCloser - var err error - - if !delayed { - reader, _, err = src.GetBlob(context.Background(), info, nil) - if err != nil { - return nil, err - } - } - return &dataAccess{ - info: info, - src: src, - reader: reader, - }, nil -} - -func (d *dataAccess) Get() ([]byte, error) { - return readAll(d.Reader()) -} - -func (d *dataAccess) Reader() (io.ReadCloser, error) { - d.lock.Lock() - reader := d.reader - d.reader = nil - d.lock.Unlock() - if reader != nil { - return reader, nil - } - reader, _, err := d.src.GetBlob(context.Background(), d.info, nil) - return reader, err -} - -func readAll(reader io.ReadCloser, err error) ([]byte, error) { - if err != nil { - return nil, err - } - defer reader.Close() - - data, err := io.ReadAll(reader) - if err != nil { - return nil, err - } - return data, nil -} diff --git a/pkg/contexts/oci/repositories/empty/repository.go b/pkg/contexts/oci/repositories/empty/repository.go deleted file mode 100644 index 8ccbba5b2..000000000 --- a/pkg/contexts/oci/repositories/empty/repository.go +++ /dev/null @@ -1,61 +0,0 @@ -package empty - -import ( - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" -) - -type Repository struct { - ctx cpi.Context -} - -var _ cpi.Repository = (*Repository)(nil) - -func NewRepository(ctx cpi.Context) *Repository { - return &Repository{ctx} -} - -func (r *Repository) GetContext() cpi.Context { - return r.ctx -} - -func (r *Repository) IsClosed() bool { - return false -} - -func (r *Repository) Dup() (cpi.Repository, error) { - return r, nil -} - -func (r Repository) GetSpecification() cpi.RepositorySpec { - return NewRepositorySpec() -} - -func (r *Repository) NamespaceLister() cpi.NamespaceLister { - return r -} - -func (r *Repository) NumNamespaces(prefix string) (int, error) { - return 0, nil -} - -func (r *Repository) GetNamespaces(prefix string, closure bool) ([]string, error) { - return nil, nil -} - -func (r Repository) ExistsArtifact(name string, version string) (bool, error) { - return false, nil -} - -func (r Repository) LookupArtifact(name string, version string) (cpi.ArtifactAccess, error) { - return nil, cpi.ErrUnknownArtifact(name, version) -} - -func (r Repository) LookupNamespace(name string) (cpi.NamespaceAccess, error) { - return nil, errors.ErrNotSupported("write access") -} - -func (r Repository) Close() error { - return nil -} diff --git a/pkg/contexts/oci/repositories/empty/type.go b/pkg/contexts/oci/repositories/empty/type.go deleted file mode 100644 index 910084e53..000000000 --- a/pkg/contexts/oci/repositories/empty/type.go +++ /dev/null @@ -1,51 +0,0 @@ -package empty - -import ( - "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/cpi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - Type = "Empty" - TypeV1 = Type + runtime.VersionSeparator + "v1" -) - -const ATTR_REPOS = "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/empty" - -func init() { - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](Type)) - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1)) -} - -// RepositorySpec describes an OCI registry interface backed by an oci registry. -type RepositorySpec struct { - runtime.ObjectVersionedType `json:",inline"` -} - -// NewRepositorySpec creates a new RepositorySpec. -func NewRepositorySpec() *RepositorySpec { - return &RepositorySpec{ - ObjectVersionedType: runtime.NewVersionedTypedObject(Type), - } -} - -func (a *RepositorySpec) GetType() string { - return Type -} - -func (a *RepositorySpec) Name() string { - return Type -} - -func (a *RepositorySpec) UniformRepositorySpec() *cpi.UniformRepositorySpec { - u := &cpi.UniformRepositorySpec{ - Type: Type, - } - return u -} - -func (a *RepositorySpec) Repository(ctx cpi.Context, creds credentials.Credentials) (cpi.Repository, error) { - return ctx.GetAttributes().GetOrCreateAttribute(ATTR_REPOS, func(datacontext.Context) interface{} { return NewRepository(ctx) }).(cpi.Repository), nil -} diff --git a/pkg/contexts/oci/repositories/empty/uniform.go b/pkg/contexts/oci/repositories/empty/uniform.go deleted file mode 100644 index 78d78a0cc..000000000 --- a/pkg/contexts/oci/repositories/empty/uniform.go +++ /dev/null @@ -1,19 +0,0 @@ -package empty - -import ( - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" -) - -func init() { - cpi.RegisterRepositorySpecHandler(&repospechandler{}, Type) -} - -type repospechandler struct{} - -func (h *repospechandler) MapReference(ctx cpi.Context, u *cpi.UniformRepositorySpec) (cpi.RepositorySpec, error) { - if u.Info != "" || u.Host == "" { - return nil, nil - } - - return NewRepositorySpec(), nil -} diff --git a/pkg/contexts/oci/repositories/init.go b/pkg/contexts/oci/repositories/init.go deleted file mode 100644 index 4ca954066..000000000 --- a/pkg/contexts/oci/repositories/init.go +++ /dev/null @@ -1,9 +0,0 @@ -package repositories - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - _ "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf" - _ "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/docker" - _ "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/empty" - _ "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ocireg" -) diff --git a/pkg/contexts/oci/repositories/ocireg/logging.go b/pkg/contexts/oci/repositories/ocireg/logging.go deleted file mode 100644 index b3a4743df..000000000 --- a/pkg/contexts/oci/repositories/ocireg/logging.go +++ /dev/null @@ -1,7 +0,0 @@ -package ocireg - -import ( - ocmlog "github.com/open-component-model/ocm/pkg/logging" -) - -var REALM = ocmlog.DefineSubRealm("OCI repository handling", "oci", "ocireg") diff --git a/pkg/contexts/oci/repositories/ocireg/namespace.go b/pkg/contexts/oci/repositories/ocireg/namespace.go deleted file mode 100644 index 537a7803c..000000000 --- a/pkg/contexts/oci/repositories/ocireg/namespace.go +++ /dev/null @@ -1,259 +0,0 @@ -package ocireg - -import ( - "context" - "fmt" - - "github.com/containerd/errdefs" - "github.com/mandelsoft/goutils/errors" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/oci/actions/oci-repository-prepare" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi/support" - "github.com/open-component-model/ocm/pkg/docker/resolve" - "github.com/open-component-model/ocm/pkg/logging" -) - -type NamespaceContainer struct { - impl support.NamespaceAccessImpl - repo *RepositoryImpl - resolver resolve.Resolver - lister resolve.Lister - fetcher resolve.Fetcher - pusher resolve.Pusher - blobs *BlobContainers - checked bool -} - -var _ support.NamespaceContainer = (*NamespaceContainer)(nil) - -func NewNamespace(repo *RepositoryImpl, name string) (cpi.NamespaceAccess, error) { - ref := repo.GetRef(name, "") - resolver, err := repo.getResolver(name) - if err != nil { - return nil, err - } - fetcher, err := resolver.Fetcher(context.Background(), ref) - if err != nil { - return nil, err - } - pusher, err := resolver.Pusher(context.Background(), ref) - if err != nil { - return nil, err - } - lister, err := resolver.Lister(context.Background(), ref) - if err != nil { - return nil, err - } - c := &NamespaceContainer{ - repo: repo, - resolver: resolver, - lister: lister, - fetcher: fetcher, - pusher: pusher, - blobs: NewBlobContainers(repo.GetContext(), fetcher, pusher), - } - return support.NewNamespaceAccess(name, c, repo) -} - -func (n *NamespaceContainer) Close() error { - return n.blobs.Release() -} - -func (n *NamespaceContainer) SetImplementation(impl support.NamespaceAccessImpl) { - n.impl = impl -} - -func (n *NamespaceContainer) getPusher(vers string) (resolve.Pusher, error) { - err := n.assureCreated() - if err != nil { - return nil, err - } - - ref := n.repo.GetRef(n.impl.GetNamespace(), vers) - resolver := n.resolver - - n.repo.GetContext().Logger().Trace("get pusher", "ref", ref) - if ok, _ := artdesc.IsDigest(vers); !ok { - var err error - - resolver, err = n.repo.getResolver(n.impl.GetNamespace()) - if err != nil { - return nil, fmt.Errorf("unable get resolver: %w", err) - } - } - - return resolver.Pusher(dummyContext, ref) -} - -func (n *NamespaceContainer) push(vers string, blob cpi.BlobAccess) error { - p, err := n.getPusher(vers) - if err != nil { - return fmt.Errorf("unable to get pusher: %w", err) - } - n.repo.GetContext().Logger().Trace("pushing", "version", vers) - return push(dummyContext, p, blob) -} - -func (n *NamespaceContainer) IsReadOnly() bool { - return n.repo.IsReadOnly() -} - -func (n *NamespaceContainer) GetBlobDescriptor(digest digest.Digest) *cpi.Descriptor { - return nil -} - -func (n *NamespaceContainer) GetBlobData(digest digest.Digest) (int64, cpi.DataAccess, error) { - n.repo.GetContext().Logger().Debug("getting blob", "digest", digest) - blob, err := n.blobs.Get("") - if err != nil { - return -1, nil, fmt.Errorf("failed to retrieve blob data: %w", err) - } - size, acc, err := blob.GetBlobData(digest) - n.repo.GetContext().Logger().Debug("getting blob done", "digest", digest, "size", size, "error", logging.ErrorMessage(err)) - return size, acc, err -} - -func (n *NamespaceContainer) AddBlob(blob cpi.BlobAccess) error { - log := n.repo.GetContext().Logger() - log.Debug("adding blob", "digest", blob.Digest()) - blobData, err := n.blobs.Get("") - if err != nil { - return fmt.Errorf("failed to retrieve blob data: %w", err) - } - err = n.assureCreated() - if err != nil { - return err - } - if _, _, err := blobData.AddBlob(blob); err != nil { - log.Debug("adding blob failed", "digest", blob.Digest(), "error", err.Error()) - return fmt.Errorf("unable to add blob (OCI repository %s): %w", n.impl.GetNamespace(), err) - } - log.Debug("adding blob done", "digest", blob.Digest()) - return nil -} - -func (n *NamespaceContainer) ListTags() ([]string, error) { - return n.lister.List(dummyContext) -} - -func (n *NamespaceContainer) GetArtifact(i support.NamespaceAccessImpl, vers string) (cpi.ArtifactAccess, error) { - ref := n.repo.GetRef(n.impl.GetNamespace(), vers) - n.repo.GetContext().Logger().Debug("get artifact", "ref", ref) - _, desc, err := n.resolver.Resolve(context.Background(), ref) - n.repo.GetContext().Logger().Debug("done", "digest", desc.Digest, "size", desc.Size, "mimetype", desc.MediaType, "error", logging.ErrorMessage(err)) - if err != nil { - if errdefs.IsNotFound(err) { - return nil, errors.ErrNotFound(cpi.KIND_OCIARTIFACT, ref, n.impl.GetNamespace()) - } - return nil, err - } - blobData, err := n.blobs.Get(desc.MediaType) - if err != nil { - return nil, fmt.Errorf("failed to retrieve blob data, blob data was empty: %w", err) - } - _, acc, err := blobData.GetBlobData(desc.Digest) - if err != nil { - return nil, err - } - return support.NewArtifactForBlob(i, blobaccess.ForDataAccess(desc.Digest, desc.Size, desc.MediaType, acc)) -} - -func (n *NamespaceContainer) HasArtifact(vers string) (bool, error) { - ref := n.repo.GetRef(n.impl.GetNamespace(), vers) - n.repo.GetContext().Logger().Debug("check artifact", "ref", ref) - _, desc, err := n.resolver.Resolve(context.Background(), ref) - n.repo.GetContext().Logger().Debug("done", "digest", desc.Digest, "size", desc.Size, "mimetype", desc.MediaType, "error", logging.ErrorMessage(err)) - if err != nil { - if errdefs.IsNotFound(err) { - return false, nil - } - return false, err - } - return true, nil -} - -func (n *NamespaceContainer) assureCreated() error { - if n.checked { - return nil - } - var props common.Properties - if creds, err := n.repo.getCreds(n.impl.GetNamespace()); err == nil && creds != nil { - props = creds.Properties() - } - r, err := oci_repository_prepare.Execute(n.repo.GetContext().GetActions(), n.repo.info.HostPort(), n.impl.GetNamespace(), props) - n.checked = true - if err != nil { - return err - } - if r != nil { - n.repo.GetContext().Logger().Debug("prepare action executed", "message", r.Message) - } - return nil -} - -func (n *NamespaceContainer) AddArtifact(artifact cpi.Artifact, tags ...string) (access blobaccess.BlobAccess, err error) { - blob, err := artifact.Blob() - if err != nil { - return nil, err - } - - if n.repo.info.Legacy { - blob = artdesc.MapArtifactBlobMimeType(blob, true) - } - - n.repo.GetContext().Logger().Debug("adding artifact", "digest", blob.Digest(), "mimetype", blob.MimeType()) - blobData, err := n.blobs.Get(blob.MimeType()) - if err != nil { - return nil, fmt.Errorf("failed to retrieve blob data: %w", err) - } - - _, _, err = blobData.AddBlob(blob) - if err != nil { - return nil, err - } - - if len(tags) > 0 { - for _, tag := range tags { - if err := n.push(tag, blob); err != nil { - return nil, err - } - } - } - - return blob, err -} - -func (n *NamespaceContainer) AddTags(digest digest.Digest, tags ...string) error { - _, desc, err := n.resolver.Resolve(context.Background(), n.repo.GetRef(n.impl.GetNamespace(), digest.String())) - if err != nil { - return fmt.Errorf("unable to resolve: %w", err) - } - - acc, err := NewDataAccess(n.fetcher, desc.Digest, desc.MediaType, false) - if err != nil { - return fmt.Errorf("error creating new data access: %w", err) - } - - blob := blobaccess.ForDataAccess(desc.Digest, desc.Size, desc.MediaType, acc) - for _, tag := range tags { - err := n.push(tag, blob) - if err != nil { - return fmt.Errorf("unable to push: %w", err) - } - } - - return nil -} - -func (n *NamespaceContainer) NewArtifact(i support.NamespaceAccessImpl, art ...cpi.Artifact) (cpi.ArtifactAccess, error) { - if n.IsReadOnly() { - return nil, accessio.ErrReadOnly - } - return support.NewArtifact(i, art...) -} diff --git a/pkg/contexts/oci/repositories/ocireg/repository.go b/pkg/contexts/oci/repositories/ocireg/repository.go deleted file mode 100644 index 93e3f767b..000000000 --- a/pkg/contexts/oci/repositories/ocireg/repository.go +++ /dev/null @@ -1,218 +0,0 @@ -package ocireg - -import ( - "context" - "crypto/tls" - "crypto/x509" - "path" - "strings" - - "github.com/containerd/containerd/remotes/docker/config" - "github.com/containerd/errdefs" - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/logging" - - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/rootcertsattr" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/docker" - "github.com/open-component-model/ocm/pkg/docker/resolve" - ocmlog "github.com/open-component-model/ocm/pkg/logging" - "github.com/open-component-model/ocm/pkg/refmgmt" - "github.com/open-component-model/ocm/pkg/utils" -) - -type RepositoryInfo struct { - Scheme string - Locator string - Creds credentials.Credentials - Legacy bool -} - -func (r *RepositoryInfo) HostPort() string { - i := strings.Index(r.Locator, "/") - if i < 0 { - return r.Locator - } else { - return r.Locator[:i] - } -} - -func (r *RepositoryInfo) HostInfo() (string, string, string) { - return utils.SplitLocator(r.Locator) -} - -type RepositoryImpl struct { - cpi.RepositoryImplBase - logger logging.UnboundLogger - spec *RepositorySpec - info *RepositoryInfo -} - -var ( - _ cpi.RepositoryImpl = (*RepositoryImpl)(nil) - _ credentials.ConsumerIdentityProvider = &RepositoryImpl{} -) - -func NewRepository(ctx cpi.Context, spec *RepositorySpec, info *RepositoryInfo) (cpi.Repository, error) { - urs := spec.UniformRepositorySpec() - if urs.Scheme == "http" { - ocmlog.Logger(REALM).Warn("using insecure http for oci registry {{host}}", "host", urs.Host) - } - i := &RepositoryImpl{ - RepositoryImplBase: cpi.NewRepositoryImplBase(ctx), - logger: logging.DynamicLogger(ctx, REALM, logging.NewAttribute(ocmlog.ATTR_HOST, urs.Host)), - spec: spec, - info: info, - } - return cpi.NewRepository(i), nil -} - -func GetRepositoryImplementation(r cpi.Repository) (*RepositoryImpl, error) { - i, err := cpi.GetRepositoryImplementation(r) - if err != nil { - return nil, err - } - return i.(*RepositoryImpl), nil -} - -func (r *RepositoryImpl) GetSpecification() cpi.RepositorySpec { - return r.spec -} - -func (r *RepositoryImpl) Close() error { - return nil -} - -func (r *RepositoryImpl) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { - if c, ok := utils.Optional(uctx...).(credentials.StringUsageContext); ok { - return identity.GetConsumerId(r.info.Locator, c.String()) - } - return identity.GetConsumerId(r.info.Locator, "") -} - -func (r *RepositoryImpl) GetIdentityMatcher() string { - return identity.CONSUMER_TYPE -} - -func (r *RepositoryImpl) NamespaceLister() cpi.NamespaceLister { - return nil -} - -func (r *RepositoryImpl) IsReadOnly() bool { - return false -} - -func (r *RepositoryImpl) getCreds(comp string) (credentials.Credentials, error) { - if r.info.Creds != nil { - return r.info.Creds, nil - } - return identity.GetCredentials(r.GetContext(), r.info.Locator, comp) -} - -func (r *RepositoryImpl) getResolver(comp string) (resolve.Resolver, error) { - creds, err := r.getCreds(comp) - if err != nil { - if !errors.IsErrUnknownKind(err, credentials.KIND_CONSUMER) { - return nil, err - } - } - logger := r.logger.BoundLogger().WithValues(ocmlog.ATTR_NAMESPACE, comp) - if creds == nil { - logger.Trace("no credentials") - } - - opts := docker.ResolverOptions{ - Hosts: docker.ConvertHosts(config.ConfigureHosts(context.Background(), config.HostOptions{ - Credentials: func(host string) (string, string, error) { - if creds != nil { - p := creds.GetProperty(credentials.ATTR_IDENTITY_TOKEN) - if p == "" { - p = creds.GetProperty(credentials.ATTR_PASSWORD) - } - pw := "" - if p != "" { - pw = "***" - } - logger.Trace("query credentials", ocmlog.ATTR_USER, creds.GetProperty(credentials.ATTR_USERNAME), "pass", pw) - return creds.GetProperty(credentials.ATTR_USERNAME), p, nil - } - logger.Trace("no credentials") - return "", "", nil - }, - DefaultScheme: r.info.Scheme, - //nolint:gosec // used like the default, there are OCI servers (quay.io) not working with min version. - DefaultTLS: func() *tls.Config { - if r.info.Scheme == "http" { - return nil - } - return &tls.Config{ - // MinVersion: tls.VersionTLS13, - RootCAs: func() *x509.CertPool { - var rootCAs *x509.CertPool - if creds != nil { - c := creds.GetProperty(credentials.ATTR_CERTIFICATE_AUTHORITY) - if c != "" { - rootCAs = x509.NewCertPool() - rootCAs.AppendCertsFromPEM([]byte(c)) - } - } - if rootCAs == nil { - rootCAs = rootcertsattr.Get(r.GetContext()).GetRootCertPool(true) - } - return rootCAs - }(), - } - }(), - })), - } - - return docker.NewResolver(opts), nil -} - -func (r *RepositoryImpl) GetRef(comp, vers string) string { - base := path.Join(r.info.Locator, comp) - if vers == "" { - return base - } - if ok, d := artdesc.IsDigest(vers); ok { - return base + "@" + d.String() - } - return base + ":" + vers -} - -func (r *RepositoryImpl) GetBaseURL() string { - return r.spec.BaseURL -} - -func (r *RepositoryImpl) ExistsArtifact(name string, version string) (bool, error) { - res, err := r.getResolver(name) - if err != nil { - return false, err - } - ref := r.GetRef(name, version) - _, _, err = res.Resolve(context.Background(), ref) - if err != nil { - if errdefs.IsNotFound(err) { - return false, nil - } - return false, err - } - return true, nil -} - -func (r *RepositoryImpl) LookupArtifact(name string, version string) (acc cpi.ArtifactAccess, err error) { - ns, err := NewNamespace(r, name) - if err != nil { - return nil, err - } - defer refmgmt.PropagateCloseTemporary(&err, ns) // temporary namespace object not exposed. - - return ns.GetArtifact(version) -} - -func (r *RepositoryImpl) LookupNamespace(name string) (cpi.NamespaceAccess, error) { - return NewNamespace(r, name) -} diff --git a/pkg/contexts/oci/repositories/ocireg/type.go b/pkg/contexts/oci/repositories/ocireg/type.go deleted file mode 100644 index 53002ae1f..000000000 --- a/pkg/contexts/oci/repositories/ocireg/type.go +++ /dev/null @@ -1,144 +0,0 @@ -package ocireg - -import ( - "fmt" - "net/url" - "strings" - - "github.com/containerd/containerd/reference" - - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -const ( - LegacyType = "ociRegistry" - Type = "OCIRegistry" - TypeV1 = Type + runtime.VersionSeparator + "v1" - - ShortType = "oci" - ShortTypeV1 = ShortType + runtime.VersionSeparator + "v1" -) - -func init() { - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](LegacyType)) - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](Type)) - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](TypeV1)) - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](ShortType)) - cpi.RegisterRepositoryType(cpi.NewRepositoryType[*RepositorySpec](ShortTypeV1)) -} - -// Is checks the kind. -func Is(spec cpi.RepositorySpec) bool { - return spec != nil && spec.GetKind() == Type || spec.GetKind() == LegacyType -} - -func IsKind(k string) bool { - return k == Type || k == LegacyType -} - -// RepositorySpec describes an OCI registry interface backed by an oci registry. -type RepositorySpec struct { - runtime.ObjectVersionedType `json:",inline"` - // BaseURL is the base url of the repository to resolve artifacts. - BaseURL string `json:"baseUrl"` - LegacyTypes *bool `json:"legacyTypes,omitempty"` -} - -var ( - _ cpi.RepositorySpec = (*RepositorySpec)(nil) - _ credentials.ConsumerIdentityProvider = (*RepositorySpec)(nil) -) - -// NewRepositorySpec creates a new RepositorySpec. -func NewRepositorySpec(baseURL string) *RepositorySpec { - return &RepositorySpec{ - ObjectVersionedType: runtime.NewVersionedTypedObject(Type), - BaseURL: baseURL, - } -} - -func NewLegacyRepositorySpec(baseURL string) *RepositorySpec { - return &RepositorySpec{ - ObjectVersionedType: runtime.NewVersionedTypedObject(LegacyType), - BaseURL: baseURL, - } -} - -func (a *RepositorySpec) GetType() string { - return Type -} - -func (a *RepositorySpec) Name() string { - return a.BaseURL -} - -func (a *RepositorySpec) UniformRepositorySpec() *cpi.UniformRepositorySpec { - return cpi.UniformRepositorySpecForHostURL(Type, a.BaseURL) -} - -func (a *RepositorySpec) getInfo(creds credentials.Credentials) (*RepositoryInfo, error) { - var u *url.URL - info := &RepositoryInfo{} - legacy := false - ref, err := reference.Parse(a.BaseURL) - if err == nil { - u, err = url.Parse("https://" + ref.Locator) - if err != nil { - return nil, err - } - info.Locator = ref.Locator - if ref.Object != "" { - return nil, fmt.Errorf("invalid repository locator %q", a.BaseURL) - } - } else { - u, err = url.Parse(a.BaseURL) - if err != nil { - return nil, err - } - info.Locator = u.Host - } - if a.LegacyTypes != nil { - legacy = *a.LegacyTypes - } else { - idx := strings.Index(info.Locator, "/") - host := info.Locator - if idx > 0 { - host = info.Locator[:idx] - } - if host == "docker.io" { - legacy = true - } - } - info.Scheme = u.Scheme - info.Creds = creds - info.Legacy = legacy - - return info, nil -} - -func (a *RepositorySpec) Repository(ctx cpi.Context, creds credentials.Credentials) (cpi.Repository, error) { - info, err := a.getInfo(creds) - if err != nil { - return nil, err - } - return NewRepository(ctx, a, info) -} - -func (a *RepositorySpec) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { - info, err := a.getInfo(nil) - if err != nil { - return nil - } - if c, ok := utils.Optional(uctx...).(credentials.StringUsageContext); ok { - return identity.GetConsumerId(info.Locator, c.String()) - } - return identity.GetConsumerId(info.Locator, "") -} - -func (a *RepositorySpec) GetIdentityMatcher() string { - return identity.CONSUMER_TYPE -} diff --git a/pkg/contexts/oci/repositories/ocireg/uniform.go b/pkg/contexts/oci/repositories/ocireg/uniform.go deleted file mode 100644 index 5a85243e3..000000000 --- a/pkg/contexts/oci/repositories/ocireg/uniform.go +++ /dev/null @@ -1,37 +0,0 @@ -package ocireg - -import ( - regex "github.com/mandelsoft/goutils/regexutils" - - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/contexts/oci/grammar" -) - -func init() { - cpi.RegisterRepositorySpecHandler(&repospechandler{}, Type, "") -} - -type repospechandler struct{} - -func (h *repospechandler) MapReference(ctx cpi.Context, u *cpi.UniformRepositorySpec) (cpi.RepositorySpec, error) { - scheme := u.Scheme - host := u.Host - if u.Host == "" && u.Scheme == "" && u.Info != "" { - host = u.Info - match := grammar.AnchoredSchemedRegexp.FindStringSubmatch(host) - if match != nil { - scheme = match[1] - host = match[2] - } - if !(regex.Anchored(grammar.HostPortRegexp).MatchString(host) || regex.Anchored(grammar.DomainPortRegexp).MatchString(host)) { - return nil, nil - } - } else if u.Info != "" || u.Host == "" { - return nil, nil - } - - if scheme != "" { - host = scheme + "://" + host - } - return NewRepositorySpec(host), nil -} diff --git a/pkg/contexts/oci/repositories/ocireg/utils.go b/pkg/contexts/oci/repositories/ocireg/utils.go deleted file mode 100644 index 6b3b5aaac..000000000 --- a/pkg/contexts/oci/repositories/ocireg/utils.go +++ /dev/null @@ -1,115 +0,0 @@ -package ocireg - -import ( - "context" - "fmt" - "io" - "sync" - - "github.com/containerd/containerd/remotes" - "github.com/containerd/errdefs" - "github.com/containerd/log" - "github.com/opencontainers/go-digest" - "github.com/sirupsen/logrus" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/docker/resolve" - "github.com/open-component-model/ocm/pkg/logging" -) - -// TODO: add cache - -type dataAccess struct { - accessio.NopCloser - lock sync.Mutex - fetcher remotes.Fetcher - desc artdesc.Descriptor - reader io.ReadCloser -} - -var _ cpi.DataAccess = (*dataAccess)(nil) - -func NewDataAccess(fetcher remotes.Fetcher, digest digest.Digest, mimeType string, delayed bool) (*dataAccess, error) { - var reader io.ReadCloser - var err error - desc := artdesc.Descriptor{ - MediaType: mimeType, - Digest: digest, - Size: blobaccess.BLOB_UNKNOWN_SIZE, - } - if !delayed { - reader, err = fetcher.Fetch(dummyContext, desc) - if err != nil { - return nil, err - } - } - return &dataAccess{ - fetcher: fetcher, - desc: desc, - reader: reader, - }, nil -} - -func (d *dataAccess) Get() ([]byte, error) { - return readAll(d.Reader()) -} - -func (d *dataAccess) Reader() (io.ReadCloser, error) { - d.lock.Lock() - reader := d.reader - d.reader = nil - d.lock.Unlock() - if reader != nil { - return reader, nil - } - return d.fetcher.Fetch(dummyContext, d.desc) -} - -func readAll(reader io.ReadCloser, err error) ([]byte, error) { - if err != nil { - return nil, err - } - defer reader.Close() - - data, err := io.ReadAll(reader) - if err != nil { - return nil, err - } - return data, nil -} - -func push(ctx context.Context, p resolve.Pusher, blob blobaccess.BlobAccess) error { - desc := *artdesc.DefaultBlobDescriptor(blob) - return pushData(ctx, p, desc, blob) -} - -func pushData(ctx context.Context, p resolve.Pusher, desc artdesc.Descriptor, data blobaccess.DataAccess) error { - key := remotes.MakeRefKey(ctx, desc) - if desc.Size == 0 { - desc.Size = -1 - } - - logging.Logger().Debug("*** push blob", "mediatype", desc.MediaType, "digest", desc.Digest, "key", key) - req, err := p.Push(ctx, desc, data) - if err != nil { - if errdefs.IsAlreadyExists(err) { - logging.Logger().Debug("blob already exists", "mediatype", desc.MediaType, "digest", desc.Digest) - - return nil - } - return fmt.Errorf("failed to push: %w", err) - } - return req.Commit(ctx, desc.Size, desc.Digest) -} - -var dummyContext = nologger() - -func nologger() context.Context { - ctx := context.Background() - logger := logrus.New() - logger.Level = logrus.ErrorLevel - return log.WithLogger(ctx, logrus.NewEntry(logger)) -} diff --git a/pkg/contexts/oci/session.go b/pkg/contexts/oci/session.go deleted file mode 100644 index 1ef40a75c..000000000 --- a/pkg/contexts/oci/session.go +++ /dev/null @@ -1,180 +0,0 @@ -package oci - -import ( - "encoding/json" - "reflect" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/datacontext" -) - -type NamespaceContainer interface { - LookupNamespace(name string) (NamespaceAccess, error) -} -type ArtifactContainer interface { - GetArtifact(version string) (ArtifactAccess, error) -} - -type EvaluationResult struct { - Ref RefSpec - Repository Repository - Namespace NamespaceAccess - Artifact ArtifactAccess -} - -type Session interface { - datacontext.Session - - LookupRepository(Context, RepositorySpec) (Repository, error) - LookupNamespace(NamespaceContainer, string) (NamespaceAccess, error) - GetArtifact(ArtifactContainer, string) (ArtifactAccess, error) - EvaluateRef(ctx Context, ref string) (*EvaluationResult, error) - DetermineRepository(ctx Context, ref string) (Repository, UniformRepositorySpec, error) - DetermineRepositoryBySpec(ctx Context, spec *UniformRepositorySpec) (Repository, error) -} - -type session struct { - datacontext.Session - base datacontext.SessionBase - repositories map[datacontext.ObjectKey]Repository - namespaces map[datacontext.ObjectKey]NamespaceAccess - artifacts map[datacontext.ObjectKey]ArtifactAccess -} - -var key = reflect.TypeOf(session{}) - -func NewSession(s datacontext.Session) Session { - return datacontext.GetOrCreateSubSession(s, key, newSession).(Session) -} - -func newSession(s datacontext.SessionBase) datacontext.Session { - return &session{ - Session: s.Session(), - base: s, - repositories: map[datacontext.ObjectKey]Repository{}, - namespaces: map[datacontext.ObjectKey]NamespaceAccess{}, - artifacts: map[datacontext.ObjectKey]ArtifactAccess{}, - } -} - -func (s *session) Close() error { - return s.Session.Close() - // TODO: cleanup cache -} - -func (s *session) LookupRepository(ctx Context, spec RepositorySpec) (Repository, error) { - spec, err := ctx.RepositoryTypes().Convert(spec) - if err != nil { - return nil, err - } - data, err := json.Marshal(spec) - if err != nil { - return nil, err - } - key := datacontext.ObjectKey{ - Object: ctx, - Name: string(data), - } - - s.base.Lock() - defer s.base.Unlock() - if s.base.IsClosed() { - return nil, errors.ErrClosed("session") - } - - if r := s.repositories[key]; r != nil { - return r, nil - } - repo, err := ctx.RepositoryForSpec(spec) - if err != nil { - return nil, err - } - s.repositories[key] = repo - s.base.AddCloser(repo) - return repo, err -} - -func (s *session) LookupNamespace(c NamespaceContainer, name string) (NamespaceAccess, error) { - key := datacontext.ObjectKey{ - Object: c, - Name: name, - } - s.base.Lock() - defer s.base.Unlock() - if s.base.IsClosed() { - return nil, errors.ErrClosed("session") - } - if ns := s.namespaces[key]; ns != nil { - return ns, nil - } - ns, err := c.LookupNamespace(name) - if err != nil { - return nil, err - } - s.namespaces[key] = ns - s.base.AddCloser(ns) - return ns, err -} - -func (s *session) GetArtifact(c ArtifactContainer, version string) (ArtifactAccess, error) { - key := datacontext.ObjectKey{ - Object: c, - Name: version, - } - s.base.Lock() - defer s.base.Unlock() - if s.base.IsClosed() { - return nil, errors.ErrClosed("session") - } - if obj := s.artifacts[key]; obj != nil { - return obj, nil - } - obj, err := c.GetArtifact(version) - if err != nil { - return nil, err - } - s.artifacts[key] = obj - s.base.AddCloser(obj) - return obj, err -} - -func (s *session) EvaluateRef(ctx Context, ref string) (*EvaluationResult, error) { - var err error - result := &EvaluationResult{} - result.Ref, err = ParseRef(ref) - if err != nil { - return nil, err - } - result.Repository, err = s.DetermineRepositoryBySpec(ctx, &result.Ref.UniformRepositorySpec) - if err != nil { - return nil, err - } - if result.Ref.Repository == "" { - return result, nil - } - result.Namespace, err = s.LookupNamespace(result.Repository, result.Ref.Repository) - - if !result.Ref.IsVersion() { - return result, err - } - result.Artifact, err = s.GetArtifact(result.Namespace, result.Ref.Version()) - return result, err -} - -func (s *session) DetermineRepository(ctx Context, ref string) (Repository, UniformRepositorySpec, error) { - spec, err := ParseRepo(ref) - if err != nil { - return nil, spec, err - } - r, err := s.DetermineRepositoryBySpec(ctx, &spec) - return r, spec, err -} - -func (s *session) DetermineRepositoryBySpec(ctx Context, spec *UniformRepositorySpec) (Repository, error) { - rspec, err := ctx.MapUniformRepositorySpec(spec) - if err != nil { - return nil, err - } - return s.LookupRepository(ctx, rspec) -} diff --git a/pkg/contexts/oci/testhelper/oci.go b/pkg/contexts/oci/testhelper/oci.go deleted file mode 100644 index caa0d58f2..000000000 --- a/pkg/contexts/oci/testhelper/oci.go +++ /dev/null @@ -1,17 +0,0 @@ -package testhelper - -import ( - . "github.com/onsi/gomega" - - "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/oci/repositories/ctf" - "github.com/open-component-model/ocm/pkg/env/builder" -) - -func FakeOCIRepo(env *builder.Builder, path string, host string) string { - spec, err := ctf.NewRepositorySpec(accessobj.ACC_READONLY, path, accessio.PathFileSystem(env.FileSystem())) - ExpectWithOffset(1, err).To(Succeed()) - env.OCIContext().SetAlias(host, spec) - return host + ".alias" -} diff --git a/pkg/contexts/oci/transfer/transfer.go b/pkg/contexts/oci/transfer/transfer.go deleted file mode 100644 index de7d1bf60..000000000 --- a/pkg/contexts/oci/transfer/transfer.go +++ /dev/null @@ -1,142 +0,0 @@ -package transfer - -import ( - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/finalizer" - "github.com/mandelsoft/goutils/generics" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/contexts/oci/transfer/filters" - "github.com/open-component-model/ocm/pkg/logging" -) - -func TransferArtifact(art cpi.ArtifactAccess, set cpi.ArtifactSink, tags ...string) error { - _, err := TransferArtifactWithFilter(art, set, nil, tags...) - return err -} - -func TransferArtifactWithFilter(art cpi.ArtifactAccess, set cpi.ArtifactSink, filter filters.Filter, tags ...string) (*digest.Digest, error) { - if art.GetDescriptor().IsIndex() { - return TransferIndexWithFilter(art.IndexAccess(), set, filter, tags...) - } else { - if filter != nil && !filter.Accept(art, nil) { - return nil, errors.ErrNoMatch(cpi.KIND_OCIARTIFACT, art.Digest().String()) - } - return generics.Pointer(art.Digest()), TransferManifest(art.ManifestAccess(), set, tags...) - } -} - -func TransferIndex(art cpi.IndexAccess, set cpi.ArtifactSink, tags ...string) error { - _, err := TransferIndexWithFilter(art, set, nil, tags...) - return err -} - -func TransferIndexWithFilter(art cpi.IndexAccess, set cpi.ArtifactSink, filter filters.Filter, tags ...string) (dig *digest.Digest, err error) { - logging.Logger().Debug("transfer OCI index", "digest", art.Digest()) - defer func() { - logging.Logger().Debug("transfer OCI index done", "error", logging.ErrorMessage(err)) - }() - - var finalize finalizer.Finalizer - defer finalize.FinalizeWithErrorPropagation(&err) - - // provide a local copy of the inbound artifact (index) - // which keeps track of the original serialized form, - // which is used if the manifest is left unchanged after - // filtering. - modified, err := cpi.NewArtifact(art) - if err != nil { - return nil, err - } - - // don't add this (index) object later. - // it implements the same interface, but would - // provide a new serialization, which might differ - // from the original one even if it is unchanged, - // which would change the digest. - index, err := modified.Index() - if err != nil { - return nil, err - } - - ign := 0 - for i, l := range art.GetDescriptor().Manifests { - loop := finalize.Nested() - logging.Logger().Debug("indexed manifest", "digest", l.Digest, "size", l.Size) - art, err := art.GetArtifact(l.Digest) - if err != nil { - return nil, errors.Wrapf(err, "getting indexed artifact %s", l.Digest) - } - loop.Close(art) - if filter == nil || filter.Accept(art, l.Platform) { - err = TransferArtifact(art, set) - if err != nil { - return nil, errors.Wrapf(err, "transferring indexed artifact %s", l.Digest) - } - dig = generics.Pointer(l.Digest) - } else { - index.Manifests = append(index.Manifests[:i-ign], index.Manifests[i-ign+1:]...) - ign++ - } - err = loop.Finalize() - if err != nil { - return nil, err - } - } - - if filter != nil { - switch len(art.GetDescriptor().Manifests) - ign { - case 0: - return nil, errors.ErrNoMatch(cpi.KIND_OCIARTIFACT, art.Digest().String()) - case 1: - if len(tags) > 0 { - err := set.AddTags(*dig, tags...) - if err != nil { - return nil, err - } - } - return dig, nil - } - } - - _, err = set.AddArtifact(modified, tags...) - if err != nil { - return nil, errors.Wrapf(err, "transferring index artifact") - } - return generics.Pointer(modified.Digest()), err -} - -func TransferManifest(art cpi.ManifestAccess, set cpi.ArtifactSink, tags ...string) (err error) { - logging.Logger().Debug("transfer OCI manifest", "digest", art.Digest()) - defer func() { - logging.Logger().Debug("transfer OCI manifest done", "error", logging.ErrorMessage(err)) - }() - - blob, err := art.GetConfigBlob() - if err != nil { - return errors.Wrapf(err, "getting config blob") - } - err = set.AddBlob(blob) - blob.Close() - if err != nil { - return errors.Wrapf(err, "transferring config blob") - } - for i, l := range art.GetDescriptor().Layers { - logging.Logger().Debug("layer", "digest", "digest", l.Digest, "size", l.Size, "index", i) - blob, err = art.GetBlob(l.Digest) - if err != nil { - return errors.Wrapf(err, "getting layer blob %s", l.Digest) - } - err = set.AddBlob(blob) - blob.Close() - if err != nil { - return errors.Wrapf(err, "transferring layer blob %s", l.Digest) - } - } - blob, err = set.AddArtifact(art, tags...) - if err != nil { - return errors.Wrapf(err, "transferring image artifact") - } - return blob.Close() -} diff --git a/pkg/contexts/oci/transfer/transfer_test.go b/pkg/contexts/oci/transfer/transfer_test.go deleted file mode 100644 index 9fdd16564..000000000 --- a/pkg/contexts/oci/transfer/transfer_test.go +++ /dev/null @@ -1,185 +0,0 @@ -package transfer_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/oci/testhelper" - . "github.com/open-component-model/ocm/pkg/env/builder" - - "github.com/mandelsoft/goutils/finalizer" - - "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/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/oci/transfer" - "github.com/open-component-model/ocm/pkg/contexts/oci/transfer/filters" -) - -const ( - OUT = "/tmp/res" - OCIPATH = "/tmp/oci" -) - -var _ = Describe("transfer OCI artifacts", func() { - var env *Builder - var idesc *artdesc.Descriptor - - BeforeEach(func() { - env = NewBuilder() - env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { - idesc = OCIIndex1(env) - }) - }) - - AfterEach(func() { - env.Cleanup() - }) - - It("transfers index", func() { - // index implicitly tests transfer of simple manifest, also - var finalize finalizer.Finalizer - defer Defer(finalize.Finalize) - - src := Must(ctf.Open(env.OCIContext(), accessobj.ACC_READONLY, OCIPATH, 0, env)) - finalize.Close(src, "source") - art := Must(src.LookupArtifact(OCINAMESPACE3, OCIINDEXVERSION)) - finalize.Close(art, "source artifact") - - tgt := Must(ctf.Create(env.OCIContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) - defer Close(tgt, "target") - ns := Must(tgt.LookupNamespace(OCINAMESPACE3)) - defer Close(ns, "target namespace") - - MustBeSuccessful(transfer.TransferArtifact(art, ns, OCIINDEXVERSION)) - - MustBeSuccessful(finalize.Finalize()) - - tart := Must(ns.GetArtifact(idesc.Digest.String())) - defer Close(tart, "target index artifact") - - Expect(tart.IsIndex()) - manifests := tart.IndexAccess().GetDescriptor().Manifests - Expect(len(manifests)).To(Equal(3)) - Expect(manifests[0].Digest.Encoded()).To(Equal(D_OCIMANIFEST1)) - Expect(manifests[1].Digest.Encoded()).To(Equal(D_OCIMANIFEST2)) - Expect(manifests[2].Digest.Encoded()).To(Equal(D_OCIMANIFEST2)) - - nart1 := Must(ns.GetArtifact(manifests[0].Digest.String())) - defer Close(nart1, "nested artifact 1") - Expect(nart1.IsManifest()).To(BeTrue()) - Expect(len(nart1.ManifestAccess().GetDescriptor().Layers)).To(Equal(1)) - blob := Must(nart1.ManifestAccess().GetBlob(nart1.ManifestAccess().GetDescriptor().Layers[0].Digest)) - defer Close(blob, "layer 0 of nested artifact 1") - data := Must(blob.Get()) - Expect(string(data)).To(Equal(OCILAYER)) - Expect(manifests[0].Platform).To(Equal(&artdesc.Platform{OS: "linux", Architecture: "amd64"})) - - nart2 := Must(ns.GetArtifact(manifests[1].Digest.String())) - defer Close(nart2, "nested artifact 2") - Expect(nart2.IsManifest()).To(BeTrue()) - Expect(len(nart2.ManifestAccess().GetDescriptor().Layers)).To(Equal(1)) - blob = Must(nart2.ManifestAccess().GetBlob(nart2.ManifestAccess().GetDescriptor().Layers[0].Digest)) - defer Close(blob, "layer 0 of nested artifact 2") - data = Must(blob.Get()) - Expect(string(data)).To(Equal(OCILAYER2)) - Expect(manifests[1].Platform).To(Equal(&artdesc.Platform{OS: "linux", Architecture: "arm64"})) - - nart3 := Must(ns.GetArtifact(manifests[2].Digest.String())) - defer Close(nart3, "nested artifact 3") - Expect(nart2.IsManifest()).To(BeTrue()) - Expect(len(nart3.ManifestAccess().GetDescriptor().Layers)).To(Equal(1)) - blob = Must(nart3.ManifestAccess().GetBlob(nart2.ManifestAccess().GetDescriptor().Layers[0].Digest)) - defer Close(blob, "layer 0 of nested artifact 3") - data = Must(blob.Get()) - Expect(string(data)).To(Equal(OCILAYER2)) - Expect(manifests[2].Platform).To(Equal(&artdesc.Platform{OS: "darwin", Architecture: "arm64"})) - }) - - Context("with filter", func() { - It("transfers index", func() { - // index implicitly tests transfer of simple manifest, also - var finalize finalizer.Finalizer - defer Defer(finalize.Finalize) - - src := Must(ctf.Open(env.OCIContext(), accessobj.ACC_READONLY, OCIPATH, 0, env)) - finalize.Close(src, "source") - art := Must(src.LookupArtifact(OCINAMESPACE3, OCIINDEXVERSION)) - finalize.Close(art, "source artifact") - - tgt := Must(ctf.Create(env.OCIContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) - defer Close(tgt, "target") - ns := Must(tgt.LookupNamespace(OCINAMESPACE3)) - defer Close(ns, "target namespace") - - filter := filters.Platform("linux", "") - MustBeSuccessful(transfer.TransferArtifactWithFilter(art, ns, filter, OCIINDEXVERSION)) - - MustBeSuccessful(finalize.Finalize()) - - tart := Must(ns.GetArtifact(OCIINDEXVERSION)) - defer Close(tart, "target index artifact") - - Expect(tart.IsIndex()) - manifests := tart.IndexAccess().GetDescriptor().Manifests - Expect(len(manifests)).To(Equal(2)) - Expect(manifests[0].Digest.Encoded()).To(Equal(D_OCIMANIFEST1)) - Expect(manifests[1].Digest.Encoded()).To(Equal(D_OCIMANIFEST2)) - - nart1 := Must(ns.GetArtifact(manifests[0].Digest.String())) - defer Close(nart1, "nested artifact 1") - Expect(nart1.IsManifest()).To(BeTrue()) - Expect(len(nart1.ManifestAccess().GetDescriptor().Layers)).To(Equal(1)) - blob := Must(nart1.ManifestAccess().GetBlob(nart1.ManifestAccess().GetDescriptor().Layers[0].Digest)) - defer Close(blob, "layer 0 of nested artifact 1") - data := Must(blob.Get()) - Expect(string(data)).To(Equal(OCILAYER)) - Expect(manifests[0].Platform).To(Equal(&artdesc.Platform{OS: "linux", Architecture: "amd64"})) - - nart2 := Must(ns.GetArtifact(manifests[1].Digest.String())) - defer Close(nart2, "nested artifact 2") - Expect(nart2.IsManifest()).To(BeTrue()) - Expect(len(nart2.ManifestAccess().GetDescriptor().Layers)).To(Equal(1)) - blob = Must(nart2.ManifestAccess().GetBlob(nart2.ManifestAccess().GetDescriptor().Layers[0].Digest)) - defer Close(blob, "layer 0 of nested artifact 2") - data = Must(blob.Get()) - Expect(string(data)).To(Equal(OCILAYER2)) - Expect(manifests[1].Platform).To(Equal(&artdesc.Platform{OS: "linux", Architecture: "arm64"})) - }) - - It("transfers index to manifest", func() { - // index implicitly tests transfer of simple manifest, also - var finalize finalizer.Finalizer - defer Defer(finalize.Finalize) - - src := Must(ctf.Open(env.OCIContext(), accessobj.ACC_READONLY, OCIPATH, 0, env)) - finalize.Close(src, "source") - art := Must(src.LookupArtifact(OCINAMESPACE3, OCIINDEXVERSION)) - finalize.Close(art, "source artifact") - - tgt := Must(ctf.Create(env.OCIContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) - defer Close(tgt, "target") - ns := Must(tgt.LookupNamespace(OCINAMESPACE3)) - defer Close(ns, "target namespace") - - filter := filters.Platform("linux", "amd64") - MustBeSuccessful(transfer.TransferArtifactWithFilter(art, ns, filter, OCIINDEXVERSION)) - - MustBeSuccessful(finalize.Finalize()) - - tart := Must(ns.GetArtifact(OCIINDEXVERSION)) - defer Close(tart, "target index artifact") - - Expect(tart.IsManifest()) - - nart1 := tart - Expect(nart1.IsManifest()).To(BeTrue()) - Expect(len(nart1.ManifestAccess().GetDescriptor().Layers)).To(Equal(1)) - blob := Must(nart1.ManifestAccess().GetBlob(nart1.ManifestAccess().GetDescriptor().Layers[0].Digest)) - defer Close(blob, "layer 0 of nested artifact 1") - data := Must(blob.Get()) - Expect(string(data)).To(Equal(OCILAYER)) - }) - }) -}) diff --git a/pkg/contexts/oci/types/interface.go b/pkg/contexts/oci/types/interface.go deleted file mode 100644 index 8df345f0d..000000000 --- a/pkg/contexts/oci/types/interface.go +++ /dev/null @@ -1,87 +0,0 @@ -package cpi - -// This is the Context Provider Interface for credential providers - -import ( - "github.com/opencontainers/go-digest" - ociv1 "github.com/opencontainers/image-spec/specs-go/v1" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/oci/internal" -) - -const CONTEXT_TYPE = internal.CONTEXT_TYPE - -const CommonTransportFormat = internal.CommonTransportFormat - -type ( - Context = internal.Context - ContextProvider = internal.ContextProvider - Repository = internal.Repository - RepositorySpecHandlers = internal.RepositorySpecHandlers - RepositorySpecHandler = internal.RepositorySpecHandler - UniformRepositorySpec = internal.UniformRepositorySpec - RepositoryType = internal.RepositoryType - RepositoryTypeProvider = internal.RepositoryTypeProvider - RepositoryTypeScheme = internal.RepositoryTypeScheme - RepositorySpec = internal.RepositorySpec - IntermediateRepositorySpecAspect = internal.IntermediateRepositorySpecAspect - GenericRepositorySpec = internal.GenericRepositorySpec - ArtifactAccess = internal.ArtifactAccess - Artifact = internal.Artifact - ArtifactSource = internal.ArtifactSource - ArtifactSink = internal.ArtifactSink - BlobSource = internal.BlobSource - BlobSink = internal.BlobSink - NamespaceLister = internal.NamespaceLister - NamespaceAccess = internal.NamespaceAccess - ManifestAccess = internal.ManifestAccess - IndexAccess = internal.IndexAccess - RepositorySource = internal.RepositorySource - ConsumerIdentityProvider = internal.ConsumerIdentityProvider -) - -type Descriptor = ociv1.Descriptor - -func DefaultContext() Context { - return internal.DefaultContext -} - -func New(m ...datacontext.BuilderMode) Context { - return internal.Builder{}.New(m...) -} - -func FromProvider(p ContextProvider) Context { - return internal.FromProvider(p) -} - -func RegisterRepositorySpecHandler(handler RepositorySpecHandler, types ...string) { - internal.RegisterRepositorySpecHandler(handler, types...) -} - -func ToGenericRepositorySpec(spec RepositorySpec) (*GenericRepositorySpec, error) { - return internal.ToGenericRepositorySpec(spec) -} - -func UniformRepositorySpecForHostURL(typ string, host string) *UniformRepositorySpec { - return internal.UniformRepositorySpecForHostURL(typ, host) -} - -const ( - KIND_OCIARTIFACT = internal.KIND_OCIARTIFACT - KIND_MEDIATYPE = blobaccess.KIND_MEDIATYPE - KIND_BLOB = blobaccess.KIND_BLOB -) - -func ErrUnknownArtifact(name, version string) error { - return internal.ErrUnknownArtifact(name, version) -} - -func ErrBlobNotFound(digest digest.Digest) error { - return blobaccess.ErrBlobNotFound(digest) -} - -func IsErrBlobNotFound(err error) bool { - return blobaccess.IsErrBlobNotFound(err) -} diff --git a/pkg/contexts/oci/utils.go b/pkg/contexts/oci/utils.go deleted file mode 100644 index 5ddefced1..000000000 --- a/pkg/contexts/oci/utils.go +++ /dev/null @@ -1,45 +0,0 @@ -package oci - -import ( - "fmt" - - ociidentity "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/grammar" - "github.com/open-component-model/ocm/pkg/runtime" -) - -func AsTags(tag string) []string { - if tag != "" { - return []string{tag} - } - return nil -} - -func StandardOCIRef(host, repository, version string) string { - sep := grammar.TagSeparator - if ok, _ := artdesc.IsDigest(version); ok { - sep = grammar.DigestSeparator - } - return fmt.Sprintf("%s%s%s%s%s", host, grammar.RepositorySeparator, repository, sep, version) -} - -func IsIntermediate(spec RepositorySpec) bool { - if s, ok := spec.(IntermediateRepositorySpecAspect); ok { - return s.IsIntermediate() - } - return false -} - -func IsUnknown(r RepositorySpec) bool { - return runtime.IsUnknown(r) -} - -func GetConsumerIdForRef(ref string) (cpi.ConsumerIdentity, error) { - r, err := ParseRef(ref) - if err != nil { - return nil, err - } - return ociidentity.GetConsumerId(r.Host, r.Repository), nil -} diff --git a/pkg/contexts/ocm/accessmethods/compose/method.go b/pkg/contexts/ocm/accessmethods/compose/method.go deleted file mode 100644 index e32063f38..000000000 --- a/pkg/contexts/ocm/accessmethods/compose/method.go +++ /dev/null @@ -1,145 +0,0 @@ -package compose - -import ( - "fmt" - "io" - "sync/atomic" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -// Type is the access type of GitHub registry. -const ( - Type = "compose" - TypeV1 = Type + runtime.VersionSeparator + "v1" -) - -func Is(spec accspeccpi.AccessSpec) bool { - return spec != nil && spec.GetKind() == Type -} - -// AccessSpec describes the access for a GitHub registry. -type AccessSpec struct { - runtime.ObjectVersionedType `json:",inline"` - - // Id is the internal id to identify the content - Id string `json:"id"` - - // MediaType is the media type of the object represented by the blob - MediaType string `json:"mediaType"` - - // GlobalAccess is an optional field describing a possibility - // for a global access. If given, it MUST describe a global access method. - GlobalAccess *accspeccpi.AccessSpecRef `json:"globalAccess,omitempty"` - // ReferenceName is an optional static name the object should be - // use in a local repository context. It is use by a repository - // to optionally determine a globally referencable access according - // to the OCI distribution spec. The result will be stored - // by the repository in the field ImageReference. - // The value is typically an OCI repository name optionally - // followed by a colon ':' and a tag - ReferenceName string `json:"referenceName,omitempty"` -} - -var ( - _ accspeccpi.AccessSpec = (*AccessSpec)(nil) - _ accspeccpi.HintProvider = (*AccessSpec)(nil) - _ accspeccpi.GlobalAccessProvider = (*AccessSpec)(nil) -) - -// New creates a new GitHub registry access spec version v1. -func New(hint string, mediaType string, global accspeccpi.AccessSpec) *AccessSpec { - id := fmt.Sprintf("compose-%d", number.Add(1)) - s := &AccessSpec{ - ObjectVersionedType: runtime.NewVersionedTypedObject(Type), - Id: id, - ReferenceName: hint, - MediaType: mediaType, - GlobalAccess: accspeccpi.NewAccessSpecRef(global), - } - return s -} - -var number atomic.Int64 - -func (a *AccessSpec) Describe(ctx accspeccpi.Context) string { - return fmt.Sprintf("Composition blob %s", a.Id) -} - -func (_ *AccessSpec) IsLocal(accspeccpi.Context) bool { - return true -} - -func (a *AccessSpec) GetReferenceHint(cv accspeccpi.ComponentVersionAccess) string { - return a.ReferenceName -} - -func (a *AccessSpec) GlobalAccessSpec(ctx accspeccpi.Context) accspeccpi.AccessSpec { - if g, err := ctx.AccessSpecForSpec(a.GlobalAccess); err == nil { - return g - } - return a.GlobalAccess.Unwrap() -} - -func (_ *AccessSpec) GetType() string { - return Type -} - -func (a *AccessSpec) AccessMethod(cv accspeccpi.ComponentVersionAccess) (accspeccpi.AccessMethod, error) { - return cv.AccessMethod(a) -} - -type accessMethod struct { - access blobaccess.BlobAccess - - spec *AccessSpec -} - -var _ accspeccpi.AccessMethodImpl = (*accessMethod)(nil) - -func NewMethod(spec *AccessSpec, blob blobaccess.BlobAccess) (accspeccpi.AccessMethod, error) { - if blob.MimeType() != spec.MediaType { - return nil, fmt.Errorf("mimetype mismatch (spec=%s, blob=%s)", spec.MediaType, blob.MimeType()) - } - b, err := blob.Dup() - if err != nil { - return nil, err - } - return accspeccpi.AccessMethodForImplementation(&accessMethod{ - access: b, - spec: spec, - }, nil) -} - -func (_ *accessMethod) IsLocal() bool { - return true -} - -func (m *accessMethod) GetKind() string { - return Type -} - -func (m *accessMethod) MimeType() string { - return m.access.MimeType() -} - -func (m *accessMethod) AccessSpec() accspeccpi.AccessSpec { - return m.spec -} - -func (m *accessMethod) Get() ([]byte, error) { - return m.access.Get() -} - -func (m *accessMethod) Reader() (io.ReadCloser, error) { - return m.access.Reader() -} - -func (m *accessMethod) Close() error { - if m.access == nil { - return nil - } - return m.access.Close() -} diff --git a/pkg/contexts/ocm/accessmethods/github/cli.go b/pkg/contexts/ocm/accessmethods/github/cli.go deleted file mode 100644 index 22e9f538e..000000000 --- a/pkg/contexts/ocm/accessmethods/github/cli.go +++ /dev/null @@ -1,43 +0,0 @@ -package github - -import ( - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/options" -) - -func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { - return flagsets.NewConfigOptionTypeSetHandler( - Type, AddConfig, - options.RepositoryOption, - options.HostnameOption, - options.CommitOption, - ) -} - -func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { - flagsets.AddFieldByOptionP(opts, options.RepositoryOption, config, "repoUrl") - flagsets.AddFieldByOptionP(opts, options.CommitOption, config, "commit") - flagsets.AddFieldByOptionP(opts, options.HostnameOption, config, "apiHostname") - return nil -} - -var usage = ` -This method implements the access of the content of a git commit stored in a -GitHub repository. -` - -var formatV1 = ` -The type specific specification fields are: - -- **repoUrl** *string* - - Repository URL with or without scheme. - -- **ref** (optional) *string* - - Original ref used to get the commit from - -- **commit** *string* - - The sha/id of the git commit -` diff --git a/pkg/contexts/ocm/accessmethods/github/method.go b/pkg/contexts/ocm/accessmethods/github/method.go deleted file mode 100644 index c944e7f52..000000000 --- a/pkg/contexts/ocm/accessmethods/github/method.go +++ /dev/null @@ -1,314 +0,0 @@ -package github - -import ( - "context" - "fmt" - "io" - "net/http" - "net/url" - "strings" - "sync" - "unicode" - - "github.com/google/go-github/v45/github" - "github.com/mandelsoft/goutils/errors" - "golang.org/x/oauth2" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/common/accessio/downloader" - hd "github.com/open-component-model/ocm/pkg/common/accessio/downloader/http" - "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/github/identity" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/runtime" -) - -// Type is the access type of GitHub registry. -const ( - Type = "gitHub" - TypeV1 = Type + runtime.VersionSeparator + "v1" -) - -const ( - LegacyType = "github" - LegacyTypeV1 = LegacyType + runtime.VersionSeparator + "v1" -) - -const ShaLength = 40 - -func init() { - accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](Type, accspeccpi.WithDescription(usage))) - accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](TypeV1, accspeccpi.WithFormatSpec(formatV1), accspeccpi.WithConfigHandler(ConfigHandler()))) - accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](LegacyType)) - accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](LegacyTypeV1)) -} - -func Is(spec accspeccpi.AccessSpec) bool { - return spec != nil && spec.GetKind() == Type || spec.GetKind() == LegacyType -} - -// AccessSpec describes the access for a GitHub registry. -type AccessSpec struct { - runtime.ObjectVersionedType `json:",inline"` - - // RepoUrl is the repository URL, with host, owner and repository - RepoURL string `json:"repoUrl"` - - // APIHostname is an optional different hostname for accessing the GitHub REST API - // for enterprise installations - APIHostname string `json:"apiHostname,omitempty"` - - // Commit defines the hash of the commit - Commit string `json:"commit"` - - client *http.Client - downloader downloader.Downloader -} - -var _ accspeccpi.AccessSpec = (*AccessSpec)(nil) - -// AccessSpecOptions defines a set of options which can be applied to the access spec. -type AccessSpecOptions func(s *AccessSpec) - -// WithClient creates an access spec with a custom http client. -func WithClient(client *http.Client) AccessSpecOptions { - return func(s *AccessSpec) { - s.client = client - } -} - -// WithDownloader defines a client with a custom downloader. -func WithDownloader(downloader downloader.Downloader) AccessSpecOptions { - return func(s *AccessSpec) { - s.downloader = downloader - } -} - -// New creates a new GitHub registry access spec version v1. -func New(repoURL, apiHostname, commit string, opts ...AccessSpecOptions) *AccessSpec { - s := &AccessSpec{ - ObjectVersionedType: runtime.NewVersionedTypedObject(Type), - RepoURL: repoURL, - APIHostname: apiHostname, - Commit: commit, - } - for _, o := range opts { - o(s) - } - return s -} - -func (a *AccessSpec) Describe(ctx accspeccpi.Context) string { - return fmt.Sprintf("GitHub commit %s[%s]", a.RepoURL, a.Commit) -} - -func (_ *AccessSpec) IsLocal(accspeccpi.Context) bool { - return false -} - -func (a *AccessSpec) GlobalAccessSpec(ctx accspeccpi.Context) accspeccpi.AccessSpec { - return a -} - -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) createHTTPClient(token string) *http.Client { - if token != "" { - ts := oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: token}, - ) - ctx := context.Background() - // set up the test client if we have one - if a.client != nil { - ctx = context.WithValue(ctx, oauth2.HTTPClient, a.client) - } - return oauth2.NewClient(ctx, ts) - } - return a.client -} - -// RepositoryService defines capabilities of a GitHub repository. -type RepositoryService interface { - GetArchiveLink(ctx context.Context, owner, repo string, archiveformat github.ArchiveFormat, opts *github.RepositoryContentGetOptions, followRedirects bool) (*url.URL, *github.Response, error) -} - -type accessMethod struct { - lock sync.Mutex - access blobaccess.BlobAccess - - compvers accspeccpi.ComponentVersionAccess - spec *AccessSpec - repositoryService RepositoryService - owner string - repo string - cid credentials.ConsumerIdentity -} - -var _ accspeccpi.AccessMethodImpl = (*accessMethod)(nil) - -func newMethod(c accspeccpi.ComponentVersionAccess, a *AccessSpec) (*accessMethod, error) { - if err := validateCommit(a.Commit); err != nil { - return nil, fmt.Errorf("failed to validate commit: %w", err) - } - - unparsed := a.RepoURL - if !strings.HasPrefix(unparsed, "https://") && !strings.HasPrefix(unparsed, "http://") { - unparsed = "https://" + unparsed - } - u, err := url.Parse(unparsed) - if err != nil { - return nil, errors.ErrInvalidWrap(err, "repository url", a.RepoURL) - } - - path := strings.Trim(u.Path, "/") - pathcomps := strings.Split(path, "/") - if len(pathcomps) != 2 { - return nil, errors.ErrInvalid("repository path", path, a.RepoURL) - } - - token, cid, err := getCreds(unparsed, path, c.GetContext().CredentialsContext()) - if err != nil { - return nil, fmt.Errorf("failed to get creds: %w", err) - } - - var client *github.Client - httpclient := a.createHTTPClient(token) - - if u.Hostname() == "github.com" { - client = github.NewClient(httpclient) - } else { - t := *u - t.Path = "" - if a.APIHostname != "" { - t.Host = a.APIHostname - } - - client, err = github.NewEnterpriseClient(t.String(), t.String(), httpclient) - if err != nil { - return nil, err - } - } - - return &accessMethod{ - spec: a, - compvers: c, - owner: pathcomps[0], - repo: pathcomps[1], - cid: cid, - repositoryService: client.Repositories, - }, nil -} - -func validateCommit(commit string) error { - if len(commit) != ShaLength { - return fmt.Errorf("commit is not a SHA") - } - for _, c := range commit { - if !unicode.IsOneOf([]*unicode.RangeTable{unicode.Letter, unicode.Digit}, c) { - return fmt.Errorf("commit contains invalid characters for a SHA") - } - } - return nil -} - -func getCreds(serverurl, path string, cctx credentials.Context) (string, credentials.ConsumerIdentity, error) { - id := identity.GetConsumerId(serverurl, path) - creds, err := credentials.CredentialsForConsumer(cctx.CredentialsContext(), id, identity.IdentityMatcher) - if creds == nil || err != nil { - return "", id, err - } - return creds.GetProperty(credentials.ATTR_TOKEN), id, nil -} - -func (_ *accessMethod) IsLocal() bool { - return false -} - -func (m *accessMethod) GetKind() string { - return Type -} - -func (m *accessMethod) MimeType() string { - return mime.MIME_TGZ -} - -func (m *accessMethod) AccessSpec() accspeccpi.AccessSpec { - return m.spec -} - -func (m *accessMethod) Get() ([]byte, error) { - if err := m.setup(); err != nil { - return nil, err - } - return m.access.Get() -} - -func (m *accessMethod) Reader() (io.ReadCloser, error) { - if err := m.setup(); err != nil { - return nil, err - } - return m.access.Reader() -} - -func (m *accessMethod) Close() error { - if m.access == nil { - return nil - } - return m.access.Close() -} - -func (m *accessMethod) setup() error { - m.lock.Lock() - defer m.lock.Unlock() - - if m.access != nil { - return nil - } - - // to get the download link technical access to the github repo is required. - // therefore, this part has to be delayed until the effective access and cannot - // be done during creation of the access method object. - link, err := m.getDownloadLink() - if err != nil { - return fmt.Errorf("failed to get download link: %w", err) - } - - d := hd.NewDownloader(link) - if m.spec.downloader != nil { - d = m.spec.downloader - } - - w := accessio.NewWriteAtWriter(d.Download) - cacheBlobAccess := accessobj.CachedBlobAccessForWriter(m.compvers.GetContext(), m.MimeType(), w) - m.access = cacheBlobAccess - return nil -} - -func (m *accessMethod) getDownloadLink() (string, error) { - link, resp, err := m.repositoryService.GetArchiveLink(context.Background(), m.owner, m.repo, github.Tarball, &github.RepositoryContentGetOptions{ - Ref: m.spec.Commit, - }, true) - if err != nil { - return "", err - } - defer resp.Body.Close() - - return link.String(), nil -} - -func (m *accessMethod) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { - return m.cid -} - -func (m *accessMethod) GetIdentityMatcher() string { - return identity.CONSUMER_TYPE -} diff --git a/pkg/contexts/ocm/accessmethods/github/method_test.go b/pkg/contexts/ocm/accessmethods/github/method_test.go deleted file mode 100644 index e7b23c1a9..000000000 --- a/pkg/contexts/ocm/accessmethods/github/method_test.go +++ /dev/null @@ -1,280 +0,0 @@ -package github_test - -import ( - "bytes" - "fmt" - "io" - "net/http" - "os" - - _ "github.com/open-component-model/ocm/pkg/contexts/datacontext/config" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/mandelsoft/filepath/pkg/filepath" - "github.com/mandelsoft/vfs/pkg/osfs" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/github/identity" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/tmpcache" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/vfsattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - me "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/github" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" -) - -type mockDownloader struct { - expected []byte - err error -} - -func (m *mockDownloader) Download(w io.WriterAt) error { - if _, err := w.WriteAt(m.expected, 0); err != nil { - return fmt.Errorf("failed to write to mock writer: %w", err) - } - return m.err -} - -// RoundTripFunc . -type RoundTripFunc func(req *http.Request) *http.Response - -// RoundTrip . -func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { - return f(req), nil -} - -// NewTestClient returns *http.Client with Transport replaced to avoid making real calls -func NewTestClient(fn RoundTripFunc) *http.Client { - return &http.Client{ - Transport: fn, - } -} - -var _ = Describe("Method", func() { - var ( - ctx ocm.Context - expectedBlobContent []byte - err error - defaultLink string - accessSpec *me.AccessSpec - fs vfs.FileSystem - expectedURL string - clientFn func(url string) *http.Client - ) - - BeforeEach(func() { - ctx = ocm.New() - expectedBlobContent, err = os.ReadFile(filepath.Join("testdata", "repo.tar.gz")) - Expect(err).ToNot(HaveOccurred()) - defaultLink = "https://github.com/test/test/sha?token=token" - expectedURL = "https://api.github.com/repos/test/test/tarball/7b1445755ee2527f0bf80ef9eeb59a5d2e6e3e1f" - - clientFn = func(url string) *http.Client { - return NewTestClient(func(req *http.Request) *http.Response { - if req.URL.String() != url { - Fail(fmt.Sprintf("failed to match url to expected url. want: %s; got: %s", expectedURL, req.URL.String())) - } - return &http.Response{ - StatusCode: http.StatusFound, - Status: http.StatusText(http.StatusFound), - Body: io.NopCloser(bytes.NewBufferString(`{}`)), - Header: http.Header{ - "Location": []string{defaultLink}, - }, - } - }) - } - - accessSpec = me.New( - "https://github.com/test/test", - "", - "7b1445755ee2527f0bf80ef9eeb59a5d2e6e3e1f", - me.WithClient(clientFn(expectedURL)), - me.WithDownloader(&mockDownloader{ - expected: expectedBlobContent, - }), - ) - fs, err = osfs.NewTempFileSystem() - Expect(err).To(Succeed()) - vfsattr.Set(ctx, fs) - tmpcache.Set(ctx, &tmpcache.Attribute{Path: "/tmp", Filesystem: fs}) - }) - - AfterEach(func() { - vfs.Cleanup(fs) - }) - - It("provides comsumer id", func() { - m, err := accessSpec.AccessMethod(&cpi.DummyComponentVersionAccess{Context: ctx}) - Expect(err).ToNot(HaveOccurred()) - Expect(credentials.GetProvidedConsumerId(m)).To(Equal(credentials.NewConsumerIdentity(identity.CONSUMER_TYPE, - identity.ID_HOSTNAME, "github.com", - identity.ID_PATHPREFIX, "test/test"))) - }) - - It("downloads artifacts", func() { - m, err := accessSpec.AccessMethod(&cpi.DummyComponentVersionAccess{Context: ctx}) - Expect(err).ToNot(HaveOccurred()) - content, err := m.Get() - Expect(err).ToNot(HaveOccurred()) - Expect(content).To(Equal(expectedBlobContent)) - }) - - When("the commit sha is of an invalid length", func() { - It("errors", func() { - accessSpec := me.New( - "hostname", - "", - "not-a-sha", - me.WithClient(clientFn(expectedURL)), - me.WithDownloader(&mockDownloader{ - expected: expectedBlobContent, - }), - ) - m, err := accessSpec.AccessMethod(&cpi.DummyComponentVersionAccess{Context: ctx}) - Expect(err).To(MatchError(ContainSubstring("commit is not a SHA"))) - if m != nil { - m.Close() - } - }) - }) - - When("the commit sha is of the right length but contains invalid characters", func() { - It("errors", func() { - accessSpec := me.New( - "hostname", - "1234", - "refs/heads/veryinteresting_branch_namess", - me.WithClient(clientFn(expectedURL)), - me.WithDownloader(&mockDownloader{ - expected: expectedBlobContent, - }), - ) - m, err := accessSpec.AccessMethod(&cpi.DummyComponentVersionAccess{Context: ctx}) - Expect(err).To(MatchError(ContainSubstring("commit contains invalid characters for a SHA"))) - if m != nil { - m.Close() - } - }) - }) - - When("credentials are provided", func() { - BeforeEach(func() { - clientFn = func(url string) *http.Client { - return NewTestClient(func(req *http.Request) *http.Response { - if v, ok := req.Header["Authorization"]; ok { - Expect(v).To(ContainElement("Bearer test")) - } else { - Fail("Authorization header not found in request") - } - if req.URL.String() != url { - Fail(fmt.Sprintf("failed to match url to expected url. want: %s; got: %s", expectedURL, req.URL.String())) - } - return &http.Response{ - StatusCode: http.StatusFound, - Status: http.StatusText(http.StatusFound), - // Must be set to non-nil value or it panics - Body: io.NopCloser(bytes.NewBufferString(`{}`)), - Header: http.Header{ - "Location": []string{defaultLink}, - }, - } - }) - } - accessSpec = me.New( - "https://github.com/test/test", - "", - "7b1445755ee2527f0bf80ef9eeb59a5d2e6e3e1f", - me.WithClient(clientFn(expectedURL)), - me.WithDownloader(&mockDownloader{ - expected: expectedBlobContent, - }), - ) - }) - It("can use those to access private repos", func() { - mcc := ocm.New(datacontext.MODE_INITIAL) - src := &mockCredSource{ - Context: mcc.CredentialsContext(), - cred: credentials.DirectCredentials{ - credentials.ATTR_TOKEN: "test", - }, - } - mcc.CredentialsContext().SetCredentialsForConsumer(credentials.NewConsumerIdentity(identity.CONSUMER_TYPE), src) - m, err := accessSpec.AccessMethod(&mockComponentVersionAccess{ - ocmContext: mcc, - }) - Expect(err).ToNot(HaveOccurred()) - _, err = m.Get() - Expect(err).ToNot(HaveOccurred()) - m.Close() - Expect(src.called).To(BeTrue()) - }) - }) - - When("GetCredentialsForConsumer returns an error", func() { - It("errors", func() { - mcc := ocm.New(datacontext.MODE_INITIAL) - src := &mockCredSource{ - Context: mcc.CredentialsContext(), - err: fmt.Errorf("danger will robinson"), - } - mcc.CredentialsContext().SetCredentialsForConsumer(credentials.NewConsumerIdentity(identity.CONSUMER_TYPE), src) - _, err := accessSpec.AccessMethod(&mockComponentVersionAccess{ - ocmContext: mcc, - }) - Expect(err).To(MatchError(ContainSubstring("danger will robinson"))) - Expect(src.called).To(BeTrue()) - }) - }) - - When("an enterprise repo URL is provided", func() { - It("uses that domain and includes api/v3 in the request URL", func() { - expectedURL = "https://github.tools.sap/api/v3/repos/test/test/tarball/25d9a3f0031c0b42e9ef7ab0117c35378040ef82" - spec := me.New("https://github.tools.sap/test/test", "", "25d9a3f0031c0b42e9ef7ab0117c35378040ef82", me.WithClient(clientFn(expectedURL))) - _, err := spec.AccessMethod(&cpi.DummyComponentVersionAccess{Context: ctx}) - Expect(err).ToNot(HaveOccurred()) - }) - }) - - When("hostname is different from github.com", func() { - It("will use an enterprise client", func() { - expectedURL = "https://custom/api/v3/repos/test/test/tarball/25d9a3f0031c0b42e9ef7ab0117c35378040ef82" - spec := me.New("https://github.tools.sap/test/test", "custom", "25d9a3f0031c0b42e9ef7ab0117c35378040ef82", me.WithClient(clientFn(expectedURL))) - _, err := spec.AccessMethod(&cpi.DummyComponentVersionAccess{Context: ctx}) - Expect(err).ToNot(HaveOccurred()) - }) - }) - - When("repoURL doesn't have an https prefix", func() { - It("will add one", func() { - expectedURL = "https://api.github.com/repos/test/test/tarball/25d9a3f0031c0b42e9ef7ab0117c35378040ef82" - spec := me.New("github.com/test/test", "", "25d9a3f0031c0b42e9ef7ab0117c35378040ef82", me.WithClient(clientFn(expectedURL))) - _, err := spec.AccessMethod(&cpi.DummyComponentVersionAccess{Context: ctx}) - Expect(err).ToNot(HaveOccurred()) - }) - }) -}) - -type mockComponentVersionAccess struct { - ocm.ComponentVersionAccess - ocmContext ocm.Context -} - -func (m *mockComponentVersionAccess) GetContext() ocm.Context { - return m.ocmContext -} - -type mockCredSource struct { - credentials.Context - cred credentials.Credentials - called bool - err error -} - -func (m *mockCredSource) Credentials(credentials.Context, ...credentials.CredentialsSource) (credentials.Credentials, error) { - m.called = true - return m.cred, m.err -} diff --git a/pkg/contexts/ocm/accessmethods/helm/cli.go b/pkg/contexts/ocm/accessmethods/helm/cli.go deleted file mode 100644 index 16c4a1f1b..000000000 --- a/pkg/contexts/ocm/accessmethods/helm/cli.go +++ /dev/null @@ -1,53 +0,0 @@ -package helm - -import ( - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/helm/identity" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/options" -) - -func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { - return flagsets.NewConfigOptionTypeSetHandler( - Type, AddConfig, - options.RepositoryOption, - options.PackageOption, - options.VersionOption, - ) -} - -func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { - flagsets.AddFieldByOptionP(opts, options.RepositoryOption, config, "helmRepository") - flagsets.AddFieldByOptionP(opts, options.PackageOption, config, "helmChart") - flagsets.AddFieldByOptionP(opts, options.VersionOption, config, "version") - return nil -} - -var usage = ` -This method implements the access of a Helm chart stored in a Helm repository. -` - -var formatV1 = ` -The type specific specification fields are: - -- **helmRepository** *string* - - Helm repository URL. - -- **helmChart** *string* - - The name of the Helm chart and its version separated by a colon. - -- **version** *string* - - The version of the Helm chart if not specified as part of the chart name. - -- **caCert** *string* - - An optional TLS root certificate. - -- **keyring** *string* - - An optional keyring used to verify the chart. - -It uses the consumer identity type ` + identity.CONSUMER_TYPE + ` with the fields -for a hostpath identity matcher (see ocm get credentials).` diff --git a/pkg/contexts/ocm/accessmethods/helm/method.go b/pkg/contexts/ocm/accessmethods/helm/method.go deleted file mode 100644 index 6bb79e234..000000000 --- a/pkg/contexts/ocm/accessmethods/helm/method.go +++ /dev/null @@ -1,191 +0,0 @@ -package helm - -import ( - "fmt" - "io" - "strings" - "sync" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/helm/identity" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/helm" - "github.com/open-component-model/ocm/pkg/runtime" -) - -// Type is the access type for a blob in an OCI repository. -const ( - Type = "helm" - 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()))) -} - -// New creates a new Helm Chart accessor for helm repositories. -func New(chart string, repourl string) *AccessSpec { - return &AccessSpec{ - ObjectVersionedType: runtime.NewVersionedTypedObject(Type), - HelmChart: chart, - HelmRepository: repourl, - } -} - -// AccessSpec describes the access for a helm repository. -type AccessSpec struct { - runtime.ObjectVersionedType `json:",inline"` - - // HelmRepository is the URL og the helm repository to load the chart from. - HelmRepository string `json:"helmRepository"` - - // HelmChart if the name of the helm chart and its version separated by a colon. - HelmChart string `json:"helmChart"` - - // Version can either be specified as part of the chart name or separately. - Version string `json:"version,omitempty"` - - // CACert is an optional root TLS certificate - CACert string `json:"caCert,omitempty"` - - // Keyring is an optional keyring to verify the chart. - Keyring string `json:"keyring,omitempty"` -} - -var _ accspeccpi.AccessSpec = (*AccessSpec)(nil) - -func (a *AccessSpec) Describe(ctx accspeccpi.Context) string { - return fmt.Sprintf("Helm chart %s:%s in repository %s", a.GetChartName(), a.GetVersion(), a.HelmRepository) -} - -func (a *AccessSpec) IsLocal(ctx accspeccpi.Context) bool { - return false -} - -func (a *AccessSpec) GlobalAccessSpec(ctx accspeccpi.Context) accspeccpi.AccessSpec { - return a -} - -func (a *AccessSpec) GetMimeType() string { - return helm.ChartMediaType -} - -func (a *AccessSpec) AccessMethod(access accspeccpi.ComponentVersionAccess) (accspeccpi.AccessMethod, error) { - return accspeccpi.AccessMethodForImplementation(&accessMethod{comp: access, spec: a}, nil) -} - -/////////////////// - -func (a *AccessSpec) GetVersion() string { - parts := strings.Split(a.HelmChart, ":") - if len(parts) > 1 { - return parts[1] - } - return a.Version -} - -func (a *AccessSpec) GetChartName() string { - parts := strings.Split(a.HelmChart, ":") - return parts[0] -} - -//////////////////////////////////////////////////////////////////////////////// - -type accessMethod struct { - lock sync.Mutex - blob blobaccess.BlobAccess - comp accspeccpi.ComponentVersionAccess - spec *AccessSpec - - acc helm.ChartAccess -} - -var _ accspeccpi.AccessMethodImpl = (*accessMethod)(nil) - -func (_ *accessMethod) IsLocal() bool { - return false -} - -func (m *accessMethod) GetKind() string { - return Type -} - -func (m *accessMethod) AccessSpec() accspeccpi.AccessSpec { - return m.spec -} - -func (m *accessMethod) Close() error { - m.lock.Lock() - defer m.lock.Unlock() - if m.blob != nil { - m.blob.Close() - m.acc.Close() - m.blob = nil - } - return nil -} - -func (m *accessMethod) Get() ([]byte, error) { - return blobaccess.BlobData(m.getBlob()) -} - -func (m *accessMethod) Reader() (io.ReadCloser, error) { - return blobaccess.BlobReader(m.getBlob()) -} - -func (m *accessMethod) MimeType() string { - return helm.ChartMediaType -} - -func (m *accessMethod) getBlob() (blobaccess.BlobAccess, error) { - m.lock.Lock() - defer m.lock.Unlock() - - if m.blob != nil { - return m.blob, nil - } - - vers := m.spec.GetVersion() - name := m.spec.GetChartName() - - parts := strings.Split(m.spec.HelmChart, ":") - switch len(parts) { - case 1: - if vers == "" { - return nil, errors.ErrInvalid("helm chart", m.spec.HelmChart) - } - case 2: - if vers != parts[1] { - return nil, errors.ErrInvalid("helm chart", m.spec.HelmChart+"["+vers+"]") - } - default: - return nil, errors.ErrInvalid("helm chart", m.spec.HelmChart) - } - - acc, err := helm.DownloadChart(common.NonePrinter, m.comp.GetContext(), name, vers, m.spec.HelmRepository, - helm.WithCredentials(identity.GetCredentials(m.comp.GetContext(), m.spec.HelmRepository, m.spec.GetChartName())), - helm.WithKeyring([]byte(m.spec.Keyring)), - helm.WithRootCert([]byte(m.spec.CACert))) - if err != nil { - return nil, err - } - m.blob, err = acc.Chart() - if err != nil { - acc.Close() - } - m.acc = acc - return m.blob, nil -} - -func (m *accessMethod) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { - return identity.GetConsumerId(m.spec.HelmRepository, m.spec.GetChartName()) -} - -func (m *accessMethod) GetIdentityMatcher() string { - return identity.CONSUMER_TYPE -} diff --git a/pkg/contexts/ocm/accessmethods/helm/method_test.go b/pkg/contexts/ocm/accessmethods/helm/method_test.go deleted file mode 100644 index 7356af5a2..000000000 --- a/pkg/contexts/ocm/accessmethods/helm/method_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package helm_test - -import ( - "fmt" - "net/http" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/env/builder" - - "helm.sh/helm/v3/pkg/chart/loader" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/helm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - helm2 "github.com/open-component-model/ocm/pkg/helm" -) - -var _ = Describe("Method", func() { - var env *Builder - - BeforeEach(func() { - env = NewBuilder() - }) - - AfterEach(func() { - env.Cleanup() - }) - - It("accesses artifact", func() { - resp, err := http.Get("https://charts.helm.sh/stable") - if err == nil { // only if connected to internet - resp.Body.Close() - fmt.Fprintf(GinkgoWriter, "helm executed\n") - spec := helm.New("cockroachdb:3.0.8", "https://charts.helm.sh/stable") - - m := Must(spec.AccessMethod(&cpi.DummyComponentVersionAccess{env.OCMContext()})) - Expect(m.MimeType()).To(Equal(helm2.ChartMediaType)) - defer Close(m) - blob := Must(m.Reader()) - defer Close(blob) - - chart := Must(loader.LoadArchive(blob)) - Expect(chart.Name()).To(Equal("cockroachdb")) - Expect(chart.Metadata.Version).To(Equal("3.0.8")) - } else { - fmt.Fprintf(GinkgoWriter, "helm test skipped\n") - } - }) -}) diff --git a/pkg/contexts/ocm/accessmethods/init.go b/pkg/contexts/ocm/accessmethods/init.go deleted file mode 100644 index 55ffe41be..000000000 --- a/pkg/contexts/ocm/accessmethods/init.go +++ /dev/null @@ -1,17 +0,0 @@ -package accessmethods - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/github" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/helm" - _ "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/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" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociblob" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/relativeociref" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/s3" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/wget" -) diff --git a/pkg/contexts/ocm/accessmethods/localblob/cli.go b/pkg/contexts/ocm/accessmethods/localblob/cli.go deleted file mode 100644 index 9a1d5a090..000000000 --- a/pkg/contexts/ocm/accessmethods/localblob/cli.go +++ /dev/null @@ -1,76 +0,0 @@ -package localblob - -import ( - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/options" -) - -func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { - return flagsets.NewConfigOptionTypeSetHandler( - Type, AddConfig, - options.ReferenceOption, - options.MediatypeOption, - options.HintOption, - options.GlobalAccessOption, - ) -} - -func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { - flagsets.AddFieldByOptionP(opts, options.ReferenceOption, config, "localReference") - flagsets.AddFieldByOptionP(opts, options.HintOption, config, "referenceName") - flagsets.AddFieldByOptionP(opts, options.MediatypeOption, config, "mediaType") - flagsets.AddFieldByOptionP(opts, options.GlobalAccessOption, config, "globalAccess") - return nil -} - -var usage = ` -This method is used to store a resource blob along with the component descriptor -on behalf of the hosting OCM repository. - -Its implementation is specific to the implementation of OCM -repository used to read the component descriptor. Every repository -implementation may decide how and where local blobs are stored, -but it MUST provide an implementation for this method. - -Regardless of the chosen implementation the attribute specification is -defined globally the same. -` - -var formatV1 = ` -The type specific specification fields are: - -- **localReference** *string* - - Repository type specific location information as string. The value - may encode any deep structure, but typically just an access path is sufficient. - -- **mediaType** *string* - - The media type of the blob used to store the resource. It may add - format information like +tar or +gzip. - -- **referenceName** (optional) *string* - - This optional attribute may contain identity information used by - other repositories to restore some global access with an identity - related to the original source. - - For example, if an OCI artifact originally referenced using the - access method ociArtifact is stored during - some transport step as local artifact, the reference name can be set - to its original repository name. An import step into an OCI based OCM - repository may then decide to make this artifact available again as - regular OCI artifact. - -- **globalAccess** (optional) *access method specification* - - If a resource blob is stored locally, the repository implementation - may decide to provide an external access information (independent - of the OCM model). - - For example, an OCI artifact stored as local blob - can be additionally stored as regular OCI artifact in an OCI registry. - - This additional external access information can be added using - a second external access method specification. -` diff --git a/pkg/contexts/ocm/accessmethods/localblob/method.go b/pkg/contexts/ocm/accessmethods/localblob/method.go deleted file mode 100644 index 7dabb2c8f..000000000 --- a/pkg/contexts/ocm/accessmethods/localblob/method.go +++ /dev/null @@ -1,188 +0,0 @@ -package localblob - -import ( - "encoding/json" - "fmt" - - . "github.com/mandelsoft/goutils/exception" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/runtime" -) - -// Type is the access type of a blob local to a component. -const ( - Type = "localBlob" - TypeV1 = Type + runtime.VersionSeparator + "v1" -) - -// this package shows how to implement access types with multiple serialization versions. -// So far, only one is implemented, but it shows how to add other ones. -// -// Specifications using multiple format versions allways provide a single common -// *internal* Go representation, intended to be used by library users. Only this -// internal version should be used outside this package. Additionally, there -// are Go types representing the various format versions, which will be used -// for the de-/serialization process (here AccessSpecV1). -// -// The supported versions are gathered in a dedicated scheme object (variable versions), -// which is then used to register all available versions at the default scheme (see -// init method). -// The *internal* specification Go type (here AccessSpec) must be based on -// runtime.InternalVersionedObjectType. -// It is initialized with the effective type/version name and the versions scheme -// and represents the Go representation used by API users, the format versions -// are never used outside this package. -// -// Additionally, this *internal* type must implement the MarshalJSON method, which -// can be implemented by delegating to the runtime.MarshalVersionedTypedObject -// method, which evaluated the versions scheme to finds the applicable conversion -// provided by the runtime.InternalVersionedObjectType. -// -// For every format version runtime.FormatVersion is required, which can be created -// with cpi.NewAccessSpecVersion, which takes the prototype and a converter, -// which converts between the internal go representation and the external formats, -// given by a dedicated go Type with serialization annotations. - -var versions = accspeccpi.NewAccessTypeVersionScheme(Type) - -func init() { - Must(versions.Register(accspeccpi.NewAccessSpecTypeByConverter[*AccessSpec, *AccessSpecV1](Type, &converterV1{}, accspeccpi.WithDescription(usage)))) - Must(versions.Register(accspeccpi.NewAccessSpecTypeByConverter[*AccessSpec, *AccessSpecV1](TypeV1, &converterV1{}, accspeccpi.WithFormatSpec(formatV1), accspeccpi.WithConfigHandler(ConfigHandler())))) - accspeccpi.RegisterAccessTypeVersions(versions) -} - -func Is(spec accspeccpi.AccessSpec) bool { - return spec != nil && spec.GetKind() == Type -} - -// New creates a new localFilesystemBlob accessor. -func New(local, hint string, mediaType string, global accspeccpi.AccessSpec) *AccessSpec { - return &AccessSpec{ - InternalVersionedTypedObject: runtime.NewInternalVersionedTypedObject[accspeccpi.AccessSpec](versions, Type), - LocalReference: local, - ReferenceName: hint, - MediaType: mediaType, - GlobalAccess: accspeccpi.NewAccessSpecRef(global), - } -} - -func Decode(data []byte) (*AccessSpec, error) { - spec, err := versions.Decode(data, runtime.DefaultYAMLEncoding) - if err != nil { - return nil, err - } - return spec.(*AccessSpec), nil -} - -// AccessSpec describes the access for a local blob. -type AccessSpec struct { - runtime.InternalVersionedTypedObject[accspeccpi.AccessSpec] - // LocalReference is the repository local identity of the blob. - // it is used by the repository implementation to get access - // to the blob and if therefore specific to a dedicated repository type. - LocalReference string `json:"localReference"` - // MediaType is the media type of the object represented by the blob - MediaType string `json:"mediaType"` - - // GlobalAccess is an optional field describing a possibility - // for a global access. If given, it MUST describe a global access method. - GlobalAccess *accspeccpi.AccessSpecRef `json:"globalAccess,omitempty"` - // ReferenceName is an optional static name the object should be - // use in a local repository context. It is use by a repository - // to optionally determine a globally referencable access according - // to the OCI distribution spec. The result will be stored - // by the repository in the field ImageReference. - // The value is typically an OCI repository name optionally - // followed by a colon ':' and a tag - ReferenceName string `json:"referenceName,omitempty"` -} - -var ( - _ json.Marshaler = (*AccessSpec)(nil) - _ accspeccpi.HintProvider = (*AccessSpec)(nil) - _ accspeccpi.GlobalAccessProvider = (*AccessSpec)(nil) - _ accspeccpi.AccessSpec = (*AccessSpec)(nil) -) - -func (a AccessSpec) MarshalJSON() ([]byte, error) { - return runtime.MarshalVersionedTypedObject(&a) - // return cpi.MarshalConvertedAccessSpec(cpi.DefaultContext(), &a) -} - -func (a *AccessSpec) Describe(ctx accspeccpi.Context) string { - return fmt.Sprintf("Local blob %s[%s]", a.LocalReference, a.ReferenceName) -} - -func (a *AccessSpec) IsLocal(accspeccpi.Context) bool { - return true -} - -func (a *AccessSpec) GlobalAccessSpec(ctx accspeccpi.Context) accspeccpi.AccessSpec { - if g, err := ctx.AccessSpecForSpec(a.GlobalAccess); err == nil { - return g - } - return a.GlobalAccess.Unwrap() -} - -func (a *AccessSpec) GetMimeType() string { - if a.MediaType == "" { - return mime.MIME_OCTET - } - return a.MediaType -} - -func (a *AccessSpec) GetReferenceHint(cv accspeccpi.ComponentVersionAccess) string { - return a.ReferenceName -} - -func (a *AccessSpec) AccessMethod(cv accspeccpi.ComponentVersionAccess) (accspeccpi.AccessMethod, error) { - return cv.AccessMethod(a) -} - -//////////////////////////////////////////////////////////////////////////////// - -type AccessSpecV1 struct { - runtime.ObjectVersionedType `json:",inline"` - // LocalReference is the repository local identity of the blob. - // it is used by the repository implementation to get access - // to the blob and if therefore specific to a dedicated repository type. - LocalReference string `json:"localReference"` - // MediaType is the media type of the object represented by the blob - MediaType string `json:"mediaType"` - - // GlobalAccess is an optional field describing a possibility - // for a global access. If given, it MUST describe a global access method. - GlobalAccess *accspeccpi.AccessSpecRef `json:"globalAccess,omitempty"` - // ReferenceName is an optional static name the object should be - // use in a local repository context. It is use by a repository - // to optionally determine a globally referencable access according - // to the OCI distribution spec. The result will be stored - // by the repository in the field ImageReference. - // The value is typically an OCI repository name optionally - // followed by a colon ':' and a tag - ReferenceName string `json:"referenceName,omitempty"` -} - -type converterV1 struct{} - -func (_ converterV1) ConvertFrom(in *AccessSpec) (*AccessSpecV1, error) { - return &AccessSpecV1{ - ObjectVersionedType: runtime.NewVersionedTypedObject(in.Type), - LocalReference: in.LocalReference, - ReferenceName: in.ReferenceName, - GlobalAccess: accspeccpi.NewAccessSpecRef(in.GlobalAccess), - MediaType: in.MediaType, - }, nil -} - -func (_ converterV1) ConvertTo(in *AccessSpecV1) (*AccessSpec, error) { - return &AccessSpec{ - InternalVersionedTypedObject: runtime.NewInternalVersionedTypedObject[accspeccpi.AccessSpec](versions, in.Type), - LocalReference: in.LocalReference, - ReferenceName: in.ReferenceName, - GlobalAccess: in.GlobalAccess, - MediaType: in.MediaType, - }, nil -} diff --git a/pkg/contexts/ocm/accessmethods/localblob/method_test.go b/pkg/contexts/ocm/accessmethods/localblob/method_test.go deleted file mode 100644 index 346b6b9cd..000000000 --- a/pkg/contexts/ocm/accessmethods/localblob/method_test.go +++ /dev/null @@ -1,93 +0,0 @@ -package localblob_test - -import ( - "encoding/json" - - . "github.com/mandelsoft/goutils/testutils" - . "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/common/accessio" - "github.com/open-component-model/ocm/pkg/common/accessobj" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociblob" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - CTF = "ctf" - COMPONENT = "fabianburth.org/component" - VERSION = "v1.0" - ARTIFACT_NAME = "artifact" - ARTIFACT_VERSION = "v1.0" -) - -var _ = Describe("Method", func() { - data := `globalAccess: - digest: sha256:1bf729fa00e355199e711933ccfa27467ee3d2de1343aef2a7c1ecbdf885e63a - mediaType: application/tar+gzip - ref: ghcr.io/vasu1124/ocm/component-descriptors/github.com/vasu1124/introspect-delivery - size: 11287 - type: ociBlob -localReference: sha256:1bf729fa00e355199e711933ccfa27467ee3d2de1343aef2a7c1ecbdf885e63a -mediaType: application/tar+gzip -type: localBlob -` - _ = data - - It("marshal/unmarshal simple", func() { - spec := localblob.New("path", "hint", mime.MIME_TEXT, nil) - data := Must(json.Marshal(spec)) - Expect(string(data)).To(Equal("{\"type\":\"localBlob\",\"localReference\":\"path\",\"mediaType\":\"text/plain\",\"referenceName\":\"hint\"}")) - r := Must(localblob.Decode(data)) - Expect(r).To(Equal(spec)) - }) - - It("marshal/unmarshal with global", func() { - spec := localblob.New("", "", "", nil) - Expect(runtime.DefaultYAMLEncoding.Unmarshal([]byte(data), spec)).To(Succeed()) - - r := Must(runtime.DefaultYAMLEncoding.Marshal(spec)) - Expect(string(r)).To(Equal(data)) - - global := ociblob.New( - "ghcr.io/vasu1124/ocm/component-descriptors/github.com/vasu1124/introspect-delivery", - "sha256:1bf729fa00e355199e711933ccfa27467ee3d2de1343aef2a7c1ecbdf885e63a", - "application/tar+gzip", - 11287, - ) - Expect(spec.GlobalAccess.Evaluate(ocm.DefaultContext())).To(Equal(global)) - - r = Must(runtime.DefaultYAMLEncoding.Marshal(spec)) - Expect(string(r)).To(Equal(data)) - }) - - It("check get inexpensive content version identity method", func() { - var env *Builder - - env = NewBuilder() - defer env.Cleanup() - - env.OCMCommonTransport(CTF, accessio.FormatDirectory, func() { - env.ComponentVersion(COMPONENT, VERSION, func() { - env.Resource(ARTIFACT_NAME, ARTIFACT_VERSION, resourcetypes.BLOB, metav1.LocalRelation, func() { - env.BlobData(mime.MIME_TEXT, []byte("testdata")) - }) - }) - }) - - repo := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, CTF, 0, env)) - defer Close(repo) - cv := Must(repo.LookupComponentVersion(COMPONENT, VERSION)) - defer Close(cv) - access := cv.GetDescriptor().Resources[0].Access - spec := Must(env.OCMContext().AccessSpecForSpec(access)) - Expect(spec.GetVersion()).To(Equal("v1")) - }) -}) diff --git a/pkg/contexts/ocm/accessmethods/localfsblob/method.go b/pkg/contexts/ocm/accessmethods/localfsblob/method.go deleted file mode 100644 index ae0bdf838..000000000 --- a/pkg/contexts/ocm/accessmethods/localfsblob/method.go +++ /dev/null @@ -1,76 +0,0 @@ -package localfsblob - -import ( - . "github.com/mandelsoft/goutils/exception" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -// Type is the access type of a blob in a local filesystem. -const ( - Type = "localFilesystemBlob" - TypeV1 = Type + runtime.VersionSeparator + "v1" -) - -// Keep old access method and map generic one to this implementation for component archives - -// This method uses the localblob internal format and converts it to/from the -// appropriate serialization version. -// The attributes referenceName and globalAccess are NOT supported. - -var versions = accspeccpi.NewAccessTypeVersionScheme(Type) - -func init() { - Must(versions.Register(accspeccpi.NewAccessSpecTypeByConverter[*localblob.AccessSpec, *AccessSpec](Type, &converterV1{}))) - Must(versions.Register(accspeccpi.NewAccessSpecTypeByConverter[*localblob.AccessSpec, *AccessSpec](TypeV1, &converterV1{}))) - accspeccpi.RegisterAccessTypeVersions(versions) -} - -// New creates a new localFilesystemBlob accessor. -func New(path string, media string) *localblob.AccessSpec { - return &localblob.AccessSpec{ - InternalVersionedTypedObject: runtime.NewInternalVersionedTypedObject[accspeccpi.AccessSpec](versions, Type), - LocalReference: path, - MediaType: media, - } -} - -func Decode(data []byte) (*localblob.AccessSpec, error) { - spec, err := versions.Decode(data, runtime.DefaultYAMLEncoding) - if err != nil { - return nil, err - } - return spec.(*localblob.AccessSpec), nil -} - -// AccessSpec describes the access for a blob on the filesystem. -// Deprecated: use LocalBlob. -type AccessSpec struct { - runtime.ObjectVersionedType `json:",inline"` - // FileName is the - Filename string `json:"fileName"` - // MediaType is the media type of the object represented by the blob - MediaType string `json:"mediaType"` -} - -//////////////////////////////////////////////////////////////////////////////// - -type converterV1 struct{} - -func (_ converterV1) ConvertFrom(in *localblob.AccessSpec) (*AccessSpec, error) { - return &AccessSpec{ - ObjectVersionedType: runtime.NewVersionedTypedObject(in.Type), - Filename: in.LocalReference, - MediaType: in.MediaType, - }, nil -} - -func (_ converterV1) ConvertTo(in *AccessSpec) (*localblob.AccessSpec, error) { - return &localblob.AccessSpec{ - InternalVersionedTypedObject: runtime.NewInternalVersionedTypedObject[accspeccpi.AccessSpec](versions, in.Type), - LocalReference: in.Filename, - MediaType: in.MediaType, - }, nil -} diff --git a/pkg/contexts/ocm/accessmethods/localfsblob/method_test.go b/pkg/contexts/ocm/accessmethods/localfsblob/method_test.go deleted file mode 100644 index bbd3e093a..000000000 --- a/pkg/contexts/ocm/accessmethods/localfsblob/method_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package localfsblob_test - -import ( - "encoding/json" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localfsblob" - "github.com/open-component-model/ocm/pkg/mime" -) - -var _ = Describe("Method", func() { - data := `globalAccess: - digest: sha256:1bf729fa00e355199e711933ccfa27467ee3d2de1343aef2a7c1ecbdf885e63a - mediaType: application/tar+gzip - ref: ghcr.io/vasu1124/ocm/component-descriptors/github.com/vasu1124/introspect-delivery - size: 11287 - type: ociBlob -localReference: sha256:1bf729fa00e355199e711933ccfa27467ee3d2de1343aef2a7c1ecbdf885e63a -mediaType: application/tar+gzip -type: localBlob -` - _ = data - - It("marshal/unmarshal simple", func() { - spec := localfsblob.New("path", mime.MIME_TEXT) - data := Must(json.Marshal(spec)) - Expect(string(data)).To(Equal("{\"type\":\"localFilesystemBlob\",\"fileName\":\"path\",\"mediaType\":\"text/plain\"}")) - r := Must(localfsblob.Decode(data)) - Expect(r).To(Equal(spec)) - }) -}) diff --git a/pkg/contexts/ocm/accessmethods/localociblob/method.go b/pkg/contexts/ocm/accessmethods/localociblob/method.go deleted file mode 100644 index 8ea9e3ef1..000000000 --- a/pkg/contexts/ocm/accessmethods/localociblob/method.go +++ /dev/null @@ -1,70 +0,0 @@ -package localociblob - -import ( - . "github.com/mandelsoft/goutils/exception" - - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -// Type is the access type for a component version local blob in an OCI repository. -const ( - Type = "localOciBlob" - TypeV1 = Type + runtime.VersionSeparator + "v1" -) - -var versions = accspeccpi.NewAccessTypeVersionScheme(Type) - -func init() { - Must(versions.Register(accspeccpi.NewAccessSpecTypeByConverter[*localblob.AccessSpec, *AccessSpec](Type, &converterV1{}))) - Must(versions.Register(accspeccpi.NewAccessSpecTypeByConverter[*localblob.AccessSpec, *AccessSpec](TypeV1, &converterV1{}))) - accspeccpi.RegisterAccessTypeVersions(versions) -} - -// New creates a new LocalOCIBlob accessor. -// Deprecated: Use LocalBlob. -func New(digest digest.Digest) *localblob.AccessSpec { - return &localblob.AccessSpec{ - InternalVersionedTypedObject: runtime.NewInternalVersionedTypedObject[accspeccpi.AccessSpec](versions, Type), - LocalReference: digest.String(), - } -} - -func Decode(data []byte) (*localblob.AccessSpec, error) { - spec, err := versions.Decode(data, runtime.DefaultYAMLEncoding) - if err != nil { - return nil, err - } - return spec.(*localblob.AccessSpec), nil -} - -// AccessSpec describes the access for a oci registry. -// Deprecated: Use LocalBlob. -type AccessSpec struct { - runtime.ObjectVersionedType `json:",inline"` - - // Digest is the digest of the targeted content. - Digest digest.Digest `json:"digest"` -} - -//////////////////////////////////////////////////////////////////////////////// - -type converterV1 struct{} - -func (_ converterV1) ConvertFrom(in *localblob.AccessSpec) (*AccessSpec, error) { - return &AccessSpec{ - ObjectVersionedType: runtime.NewVersionedTypedObject(in.Type), - Digest: digest.Digest(in.LocalReference), - }, nil -} - -func (_ converterV1) ConvertTo(in *AccessSpec) (*localblob.AccessSpec, error) { - return &localblob.AccessSpec{ - InternalVersionedTypedObject: runtime.NewInternalVersionedTypedObject[accspeccpi.AccessSpec](versions, in.Type), - LocalReference: in.Digest.String(), - MediaType: "", - }, nil -} diff --git a/pkg/contexts/ocm/accessmethods/localociblob/method_test.go b/pkg/contexts/ocm/accessmethods/localociblob/method_test.go deleted file mode 100644 index 4692eaa28..000000000 --- a/pkg/contexts/ocm/accessmethods/localociblob/method_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package localociblob_test - -import ( - "encoding/json" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localociblob" -) - -var _ = Describe("Method", func() { - It("marshal/unmarshal simple", func() { - spec := localociblob.New("sha256:1bf729fa00e355199e711933ccfa27467ee3d2de1343aef2a7c1ecbdf885e63a") - data := Must(json.Marshal(spec)) - Expect(string(data)).To(Equal("{\"type\":\"localOciBlob\",\"digest\":\"sha256:1bf729fa00e355199e711933ccfa27467ee3d2de1343aef2a7c1ecbdf885e63a\"}")) - r := Must(localociblob.Decode(data)) - Expect(r).To(Equal(spec)) - }) -}) diff --git a/pkg/contexts/ocm/accessmethods/maven/cli.go b/pkg/contexts/ocm/accessmethods/maven/cli.go deleted file mode 100644 index db82b8a8b..000000000 --- a/pkg/contexts/ocm/accessmethods/maven/cli.go +++ /dev/null @@ -1,62 +0,0 @@ -package maven - -import ( - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/options" -) - -func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { - return flagsets.NewConfigOptionTypeSetHandler( - Type, AddConfig, - options.RepositoryOption, - options.GroupOption, - options.ArtifactOption, - options.VersionOption, - // optional - options.ClassifierOption, - options.ExtensionOption, - ) -} - -func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { - 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") - // optional - flagsets.AddFieldByOptionP(opts, options.ClassifierOption, config, "classifier") - flagsets.AddFieldByOptionP(opts, options.ExtensionOption, config, "extension") - return nil -} - -var usage = ` -This method implements the access of a Maven artifact in a Maven repository. -` - -var formatV1 = ` -The type specific specification fields are: - -- **repoUrl** *string* - - URL of the Maven repository - -- **groupId** *string* - - The groupId of the Maven artifact - -- **artifactId** *string* - - The artifactId of the Maven artifact - -- **version** *string* - - The version name of the Maven artifact - -- **classifier** *string* - - The optional classifier of the Maven artifact - -- **extension** *string* - - 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 deleted file mode 100644 index fd6c7d13e..000000000 --- a/pkg/contexts/ocm/accessmethods/maven/method.go +++ /dev/null @@ -1,132 +0,0 @@ -package maven - -import ( - "fmt" - - "github.com/mandelsoft/goutils/optionutils" - - "github.com/open-component-model/ocm/pkg/blobaccess/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/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 { - if a.IsPackage() { - return a.GAV() - } - return "" -} - -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.BlobAccessForCoords(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) 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 deleted file mode 100644 index 09801620c..000000000 --- a/pkg/contexts/ocm/accessmethods/maven/method_test.go +++ /dev/null @@ -1,137 +0,0 @@ -package maven_test - -import ( - "crypto" - "time" - - . "github.com/mandelsoft/goutils/testutils" - . "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/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/maven/maventest" - "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.SHA256, r) - li := Must(tarutils.ListArchiveContentFromReader(dr)) - 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(dr.Size()).To(Equal(int64(maventest.ARTIFACT_SIZE))) - Expect(dr.Digest().String()).To(Equal("SHA-256:" + maventest.ARTIFACT_DIGEST)) - }) - It("test empty repoUrl", func() { - acc := me.New("", "com.sap.cloud.sdk", "sdk-modules-bom", "5.7.0") - ExpectError(acc.AccessMethod(cv)).ToNot(BeNil()) - }) - - 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(maventest.POM_SHA1)) - }) - - 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.ArtifactId).To(Equal("maven")) - }) - } - }) -}) diff --git a/pkg/contexts/ocm/accessmethods/none/method.go b/pkg/contexts/ocm/accessmethods/none/method.go deleted file mode 100644 index ea0183ca0..000000000 --- a/pkg/contexts/ocm/accessmethods/none/method.go +++ /dev/null @@ -1,96 +0,0 @@ -package none - -import ( - "io" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -// Type is the access type for no blob. -const ( - Type = compdesc.NoneType - TypeV1 = Type + runtime.VersionSeparator + "v1" - LegacyType = compdesc.NoneLegacyType -) - -func init() { - accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](Type, accspeccpi.WithDescription("dummy resource with no access"))) - accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](TypeV1)) - accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](LegacyType)) -} - -// New creates a new OCIBlob accessor. -func New() *AccessSpec { - return &AccessSpec{ObjectVersionedType: runtime.NewVersionedTypedObject(Type)} -} - -func IsNone(kind string) bool { - return compdesc.IsNoneAccessKind(kind) -} - -// AccessSpec describes the access for a oci registry. -type AccessSpec struct { - runtime.ObjectVersionedType `json:",inline"` -} - -var _ accspeccpi.AccessSpec = (*AccessSpec)(nil) - -func (a *AccessSpec) Describe(ctx accspeccpi.Context) string { - return "none" -} - -func (s *AccessSpec) IsLocal(context accspeccpi.Context) bool { - return false -} - -func (s *AccessSpec) GlobalAccessSpec(ctx accspeccpi.Context) accspeccpi.AccessSpec { - return nil -} - -func (s *AccessSpec) GetMimeType() string { - return "" -} - -func (s *AccessSpec) AccessMethod(access accspeccpi.ComponentVersionAccess) (accspeccpi.AccessMethod, error) { - return accspeccpi.AccessMethodForImplementation(&accessMethod{spec: s}, nil) -} - -//////////////////////////////////////////////////////////////////////////////// - -type accessMethod struct { - spec *AccessSpec -} - -var _ accspeccpi.AccessMethodImpl = (*accessMethod)(nil) - -func (_ *accessMethod) IsLocal() bool { - return false -} - -func (m *accessMethod) GetKind() string { - return Type -} - -func (m *accessMethod) AccessSpec() accspeccpi.AccessSpec { - return m.spec -} - -func (m *accessMethod) Close() error { - return nil -} - -func (m *accessMethod) Get() ([]byte, error) { - return nil, errors.ErrNotSupported("access") -} - -func (m *accessMethod) Reader() (io.ReadCloser, error) { - return nil, errors.ErrNotSupported("access") -} - -func (m *accessMethod) MimeType() string { - return "" -} diff --git a/pkg/contexts/ocm/accessmethods/npm/cli.go b/pkg/contexts/ocm/accessmethods/npm/cli.go deleted file mode 100644 index 7f1533631..000000000 --- a/pkg/contexts/ocm/accessmethods/npm/cli.go +++ /dev/null @@ -1,42 +0,0 @@ -package npm - -import ( - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/options" -) - -func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { - return flagsets.NewConfigOptionTypeSetHandler( - Type, AddConfig, - options.RegistryOption, - options.PackageOption, - options.VersionOption, - ) -} - -func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { - flagsets.AddFieldByOptionP(opts, options.RegistryOption, config, "registry") - flagsets.AddFieldByOptionP(opts, options.PackageOption, config, "package") - flagsets.AddFieldByOptionP(opts, options.VersionOption, config, "version") - return nil -} - -var usage = ` -This method implements the access of an NPM package in an NPM registry. -` - -var formatV1 = ` -The type specific specification fields are: - -- **registry** *string* - - Base URL of the NPM registry. - -- **package** *string* - - The name of the NPM package - -- **version** *string* - - The version name of the NPM package -` diff --git a/pkg/contexts/ocm/accessmethods/npm/method.go b/pkg/contexts/ocm/accessmethods/npm/method.go deleted file mode 100644 index f99c42e68..000000000 --- a/pkg/contexts/ocm/accessmethods/npm/method.go +++ /dev/null @@ -1,228 +0,0 @@ -package npm - -import ( - "bytes" - "context" - "crypto" - "encoding/json" - "fmt" - "io" - "net/http" - "path" - "strings" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/blobaccess/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/cpi" - "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/mime" - "github.com/open-component-model/ocm/pkg/npm" - "github.com/open-component-model/ocm/pkg/runtime" -) - -// Type is the access type of NPM registry. -const ( - Type = "npm" - 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 NPM registry. -type AccessSpec struct { - runtime.ObjectVersionedType `json:",inline"` - - // Registry is the base URL of the NPM registry - Registry string `json:"registry"` - // Package is the name of NPM package - Package string `json:"package"` - // Version of the NPM package. - Version string `json:"version"` -} - -var _ accspeccpi.AccessSpec = (*AccessSpec)(nil) - -// New creates a new NPM registry access spec version v1. -func New(registry, pkg, version string) *AccessSpec { - return &AccessSpec{ - ObjectVersionedType: runtime.NewVersionedTypedObject(Type), - Registry: registry, - Package: pkg, - Version: version, - } -} - -func (a *AccessSpec) Describe(_ accspeccpi.Context) string { - return fmt.Sprintf("NPM package %s:%s in registry %s", a.Package, a.Version, a.Registry) -} - -func (_ *AccessSpec) IsLocal(accspeccpi.Context) bool { - return false -} - -func (a *AccessSpec) GlobalAccessSpec(_ accspeccpi.Context) accspeccpi.AccessSpec { - return a -} - -func (a *AccessSpec) GetReferenceHint(_ accspeccpi.ComponentVersionAccess) string { - return a.Package + ":" + a.Version -} - -func (_ *AccessSpec) GetType() string { - return Type -} - -func (a *AccessSpec) AccessMethod(c accspeccpi.ComponentVersionAccess) (accspeccpi.AccessMethod, error) { - return accspeccpi.AccessMethodForImplementation(newMethod(c, a)) -} - -// PackageUrl returns the URL of the NPM package (Registry/Package). -func (a *AccessSpec) PackageUrl() string { - return strings.TrimSuffix(a.Registry, "/") + path.Join("/", a.Package) -} - -// PackageVersionUrl returns the URL of the NPM package-version (Registry/Package/Version). -func (a *AccessSpec) PackageVersionUrl() string { - return strings.TrimSuffix(a.Registry, "/") + path.Join("/", a.Package, a.Version) -} - -func (a *AccessSpec) GetPackageVersion(ctx accspeccpi.Context) (*npm.Version, error) { - r, err := reader(a, vfsattr.Get(ctx), ctx) - if err != nil { - return nil, err - } - defer r.Close() - buf, err := io.ReadAll(r) - if err != nil { - return nil, errors.Wrapf(err, "cannot get version metadata for %s", a.PackageVersionUrl()) - } - var version npm.Version - err = json.Unmarshal(buf, &version) - if err != nil || version.Dist.Tarball == "" { - // ugly fallback as workaround for https://github.com/sonatype/nexus-public/issues/224 - var project npm.Project - err = json.Unmarshal(buf, &project) // parse the complete project - if err != nil { - return nil, errors.Wrapf(err, "cannot unmarshal version metadata for %s", a.PackageVersionUrl()) - } - v, ok := project.Version[a.Version] // and pick only the specified version - if !ok { - return nil, errors.Newf("version '%s' doesn't exist", a.Version) - } - version = v - } - return &version, nil -} - -//////////////////////////////////////////////////////////////////////////////// - -func newMethod(c accspeccpi.ComponentVersionAccess, a *AccessSpec) (accspeccpi.AccessMethodImpl, error) { - factory := func() (blobaccess.BlobAccess, error) { - meta, err := a.GetPackageVersion(c.GetContext()) - if err != nil { - return nil, err - } - - f := func() (io.ReadCloser, error) { - return reader(a, vfsattr.Get(c.GetContext()), c.GetContext(), meta.Dist.Tarball) - } - if meta.Dist.Integrity != "" { - tf := f - f = func() (io.ReadCloser, error) { - r, err := tf() - if err != nil { - return nil, err - } - digest, err := iotools.DecodeBase64ToHex(meta.Dist.Integrity) - if err != nil { - return nil, err - } - return iotools.VerifyingReaderWithHash(r, crypto.SHA512, digest), nil - } - } - if meta.Dist.Shasum != "" { - tf := f - f = func() (io.ReadCloser, error) { - r, err := tf() - if err != nil { - return nil, err - } - return iotools.VerifyingReaderWithHash(r, crypto.SHA1, meta.Dist.Shasum), nil - } - } - acc := blobaccess.DataAccessForReaderFunction(f, meta.Dist.Tarball) - return accessobj.CachedBlobAccessForWriter(c.GetContext(), mime.MIME_TGZ, accessio.NewDataAccessWriter(acc)), nil - } - return accspeccpi.NewDefaultMethodImpl(c, a, "", mime.MIME_TGZ, factory), nil -} - -func reader(a *AccessSpec, fs vfs.FileSystem, ctx cpi.ContextProvider, tar ...string) (io.ReadCloser, error) { - url := a.PackageVersionUrl() - if len(tar) > 0 { - url = tar[0] - } - 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 = npm.BasicAuth(req, ctx, a.Registry, a.Package) - if err != nil { - return nil, err - } - c := &http.Client{} - resp, err := c.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - if resp.StatusCode == http.StatusNotFound { - // maybe it's stupid Nexus - https://github.com/sonatype/nexus-public/issues/224? - url = a.PackageUrl() - req, err = http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil) - if err != nil { - return nil, err - } - err = npm.BasicAuth(req, ctx, a.Registry, a.Package) - if err != nil { - return nil, err - } - - // close body before overwriting to close any pending connections - resp.Body.Close() - resp, err = c.Do(req) - if err != nil { - return nil, err - } - - defer resp.Body.Close() - } - - if resp.StatusCode != http.StatusOK { - defer resp.Body.Close() - buf := &bytes.Buffer{} - _, err = io.Copy(buf, io.LimitReader(resp.Body, 2000)) - if err != nil { - return nil, errors.Newf("version meta data request %s provides %s", url, resp.Status) - } - return nil, errors.Newf("version meta data request %s provides %s: %s", url, resp.Status, buf.String()) - } - content, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - return io.NopCloser(bytes.NewBuffer(content)), nil -} diff --git a/pkg/contexts/ocm/accessmethods/npm/method_test.go b/pkg/contexts/ocm/accessmethods/npm/method_test.go deleted file mode 100644 index 0e00ec152..000000000 --- a/pkg/contexts/ocm/accessmethods/npm/method_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package npm_test - -import ( - "crypto" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/env" - . "github.com/open-component-model/ocm/pkg/env/builder" - - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/npm" - "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" -) - -const ( - NPMPATH = "/testdata/registry" - FAILPATH = "/testdata/failregistry" -) - -var _ = Describe("Method", func() { - var env *Builder - var cv ocm.ComponentVersionAccess - - BeforeEach(func() { - env = NewBuilder(TestData()) - cv = &cpi.DummyComponentVersionAccess{env.OCMContext()} - }) - - AfterEach(func() { - env.Cleanup() - }) - - It("accesses artifact", func() { - acc := npm.New("file://"+NPMPATH, "yargs", "17.7.1") - // acc := npm.New("https://registry.npmjs.org", "yargs", "17.7.1") - - 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(65690))) - Expect(dr.Digest().String()).To(Equal("SHA-1:34a77645201d1a8fc5213ace787c220eabbd0967")) - }) - - It("detects digests mismatch", func() { - acc := npm.New("file://"+FAILPATH, "yargs", "17.7.1") - - m := Must(acc.AccessMethod(cv)) - defer m.Close() - _, err := m.Reader() - Expect(err).To(MatchError(ContainSubstring("SHA-1 digest mismatch: expected 44a77645201d1a8fc5213ace787c220eabbd0967, found 34a77645201d1a8fc5213ace787c220eabbd0967"))) - }) - - It("PackageUrl()", func() { - packageUrl := "https://registry.npmjs.org/yargs" - acc := npm.New("https://registry.npmjs.org", "yargs", "17.7.1") - Expect(acc.PackageUrl()).To(Equal(packageUrl)) - acc = npm.New("https://registry.npmjs.org/", "yargs", "17.7.1") - Expect(acc.PackageUrl()).To(Equal(packageUrl)) - }) - - It("PackageVersionUrl()", func() { - packageVersionUrl := "https://registry.npmjs.org/yargs/17.7.1" - acc := npm.New("https://registry.npmjs.org", "yargs", "17.7.1") - Expect(acc.PackageVersionUrl()).To(Equal(packageVersionUrl)) - acc = npm.New("https://registry.npmjs.org/", "yargs", "17.7.1") - Expect(acc.PackageVersionUrl()).To(Equal(packageVersionUrl)) - }) -}) diff --git a/pkg/contexts/ocm/accessmethods/ociartifact/cli.go b/pkg/contexts/ocm/accessmethods/ociartifact/cli.go deleted file mode 100644 index 11eb07b0a..000000000 --- a/pkg/contexts/ocm/accessmethods/ociartifact/cli.go +++ /dev/null @@ -1,32 +0,0 @@ -package ociartifact - -import ( - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/options" -) - -func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { - return flagsets.NewConfigOptionTypeSetHandler( - Type, AddConfig, - options.ReferenceOption, - ) -} - -func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { - flagsets.AddFieldByOptionP(opts, options.ReferenceOption, config, "imageReference") - return nil -} - -var usage = ` -This method implements the access of an OCI artifact stored in an OCI registry. -` - -var formatV1 = ` -The type specific specification fields are: - -- **imageReference** *string* - - OCI image/artifact reference following the possible docker schemes: - - <repo>/<artifact>:<digest>@<tag> - - [<port>]/<repo path>/<artifact>:<version>@<tag> -` diff --git a/pkg/contexts/ocm/accessmethods/ociartifact/logging.go b/pkg/contexts/ocm/accessmethods/ociartifact/logging.go deleted file mode 100644 index 9c855da8d..000000000 --- a/pkg/contexts/ocm/accessmethods/ociartifact/logging.go +++ /dev/null @@ -1,30 +0,0 @@ -package ociartifact - -import ( - "github.com/mandelsoft/logging" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - ocmlog "github.com/open-component-model/ocm/pkg/logging" -) - -var REALM = ocmlog.DefineSubRealm("access method ociArtifact", "accessmethod/ociartifact") - -type ContextProvider interface { - GetContext() cpi.Context -} - -func Logger(c ContextProvider, keyValuePairs ...interface{}) logging.Logger { - return c.GetContext().Logger(REALM).WithValues(keyValuePairs...) -} - -type localContextProvider struct { - cpi.ContextProvider -} - -func (l *localContextProvider) GetContext() cpi.Context { - return l.OCMContext() -} - -func WrapContextProvider(ctx cpi.ContextProvider) ContextProvider { - return &localContextProvider{ctx} -} diff --git a/pkg/contexts/ocm/accessmethods/ociartifact/method.go b/pkg/contexts/ocm/accessmethods/ociartifact/method.go deleted file mode 100644 index 10a3dee40..000000000 --- a/pkg/contexts/ocm/accessmethods/ociartifact/method.go +++ /dev/null @@ -1,362 +0,0 @@ -package ociartifact - -import ( - "fmt" - "io" - "strings" - "sync" - - . "github.com/mandelsoft/goutils/finalizer" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/general" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - ociidentity "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/grammar" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ocireg" - ocmcpi "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/logging" - "github.com/open-component-model/ocm/pkg/runtime" -) - -// Type is the access type of a oci registry. -const ( - Type = "ociArtifact" - TypeV1 = Type + runtime.VersionSeparator + "v1" -) - -const ( - LegacyType = "ociRegistry" - LegacyTypeV1 = LegacyType + 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()))) - - accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](LegacyType)) - accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](LegacyTypeV1)) -} - -func Is(spec accspeccpi.AccessSpec) bool { - return spec != nil && (spec.GetKind() == Type || spec.GetKind() == LegacyType) -} - -// AccessSpec describes the access for a oci registry. -type AccessSpec struct { - runtime.ObjectVersionedType `json:",inline"` - - // ImageReference is the actual reference to the oci image repository and tag. - ImageReference string `json:"imageReference"` -} - -var ( - _ accspeccpi.AccessSpec = (*AccessSpec)(nil) - _ accspeccpi.HintProvider = (*AccessSpec)(nil) - _ blobaccess.DigestSource = (*AccessSpec)(nil) -) - -// New creates a new oci registry access spec version v1. -func New(ref string) *AccessSpec { - return &AccessSpec{ - ObjectVersionedType: runtime.NewVersionedTypedObject(Type), - ImageReference: ref, - } -} - -func (a *AccessSpec) Describe(ctx accspeccpi.Context) string { - return fmt.Sprintf("OCI artifact %s", a.ImageReference) -} - -func (_ *AccessSpec) IsLocal(accspeccpi.Context) bool { - return false -} - -func (a *AccessSpec) Digest() digest.Digest { - ref, err := oci.ParseRef(a.ImageReference) - if err != nil || ref.Digest == nil { - return "" - } - return *ref.Digest -} - -func (a *AccessSpec) GlobalAccessSpec(ctx accspeccpi.Context) accspeccpi.AccessSpec { - return a -} - -func (a *AccessSpec) GetReferenceHint(cv accspeccpi.ComponentVersionAccess) string { - ref, err := oci.ParseRef(a.ImageReference) - if err != nil { - return "" - } - hint := ref.Repository - r := cv.Repository() - if r != nil { - prefix := ocmcpi.RepositoryPrefix(cv.Repository().GetSpecification()) - if strings.HasPrefix(hint, prefix+grammar.RepositorySeparator) { - // try to keep hint identical, even across intermediate - // artifact globalizations - hint = hint[len(prefix)+1:] - } - } - if ref.Tag != nil { - hint += grammar.TagSeparator + *ref.Tag - } - return hint -} - -func (a *AccessSpec) GetOCIReference(cv accspeccpi.ComponentVersionAccess) (string, error) { - return a.ImageReference, nil -} - -func (_ *AccessSpec) GetType() string { - return Type -} - -func (a *AccessSpec) AccessMethod(c accspeccpi.ComponentVersionAccess) (accspeccpi.AccessMethod, error) { - return NewMethod(c.GetContext(), a, a.ImageReference) -} - -//////////////////////////////////////////////////////////////////////////////// - -type AccessMethodImpl = *accessMethod - -type accessMethod struct { - lock sync.Mutex - ctx accspeccpi.Context - spec accspeccpi.AccessSpec - reference string - - finalizer Finalizer - err error - - id credentials.ConsumerIdentity - ref *oci.RefSpec - mime string - digest digest.Digest - art oci.ArtifactAccess - - repo oci.Repository - blob artifactset.ArtifactBlob -} - -var ( - _ accspeccpi.AccessMethodImpl = (*accessMethod)(nil) - _ blobaccess.DigestSource = (*accessMethod)(nil) - _ accspeccpi.DigestSource = (*accessMethod)(nil) - _ credentials.ConsumerIdentityProvider = (*accessMethod)(nil) -) - -func NewMethod(ctx accspeccpi.ContextProvider, a accspeccpi.AccessSpec, ref string, repo ...oci.Repository) (accspeccpi.AccessMethod, error) { - m := &accessMethod{ - spec: a, - reference: ref, - ctx: ctx.OCMContext(), - } - return accspeccpi.AccessMethodForImplementation(m, m.eval(general.Optional(repo...))) -} - -func (_ *accessMethod) IsLocal() bool { - return false -} - -func (m *accessMethod) GetOCIReference(cv accspeccpi.ComponentVersionAccess) (string, error) { - return m.reference, nil -} - -func (m *accessMethod) GetKind() string { - return m.spec.GetKind() -} - -func (m *accessMethod) AccessSpec() accspeccpi.AccessSpec { - return m.spec -} - -func (m *accessMethod) Cache() { - m.lock.Lock() - ref := m.ref - m.lock.Unlock() - if ref == nil { - return - } - logger := Logger(WrapContextProvider(m.ctx)) - logger.Info("cache artifact blob", "ref", m.reference) - - _, m.err = m.getBlob() - - m.finalizer.Finalize() -} - -func (m *accessMethod) Close() error { - m.lock.Lock() - defer m.lock.Unlock() - - list := errors.ErrorList{} - - if m.blob != nil { - list.Add(m.blob.Close()) - } - m.blob = nil - m.art = nil - m.ref = nil - list.Add(m.finalizer.Finalize()) - return list.Result() -} - -func (m *accessMethod) eval(relto oci.Repository) error { - var ( - err error - ref oci.RefSpec - ) - - if relto == nil { - ref, err = oci.ParseRef(m.reference) - if err != nil { - return err - } - ocictx := m.ctx.OCIContext() - spec := ocictx.GetAlias(ref.Host) - if spec == nil { - spec = ocireg.NewRepositorySpec(ref.Host) - } - repo, err := ocictx.RepositoryForSpec(spec) - if err != nil { - return err - } - m.finalizer.Close(repo, "repository for accessing %s", m.reference) - m.repo = repo - } else { - repo, err := relto.Dup() - if err != nil { - return err - } - m.finalizer.Close(repo) - art, err := oci.ParseArt(m.reference) - if err != nil { - return err - } - ref = oci.RefSpec{ - UniformRepositorySpec: *repo.GetSpecification().UniformRepositorySpec(), - ArtSpec: art, - } - m.repo = repo - } - - m.ref = &ref - m.id = credentials.GetProvidedConsumerId(m.repo, credentials.StringUsageContext(ref.Repository)) - return nil -} - -func (m *accessMethod) GetArtifact() (oci.ArtifactAccess, *oci.RefSpec, error) { - m.lock.Lock() - defer m.lock.Unlock() - - err := m.getArtifact() - if err != nil { - return nil, nil, m.err - } - art := m.art - if art != nil { - art, err = art.Dup() - if err != nil { - return nil, nil, err - } - } - return art, m.ref, err -} - -func (m *accessMethod) getArtifact() error { - if m.art == nil && m.err == nil && m.ref != nil { - art, err := m.repo.LookupArtifact(m.ref.Repository, m.ref.Version()) - m.finalizer.Close(art, "artifact for accessing %s", m.reference) - m.art, m.err = art, err - if art != nil { - m.mime = artdesc.ToContentMediaType(m.art.GetDescriptor().MimeType()) + artifactset.SynthesizedBlobFormat - m.digest = art.Digest() - } - } - return m.err -} - -func (m *accessMethod) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { - m.lock.Lock() - defer m.lock.Unlock() - - return m.id -} - -func (m *accessMethod) GetIdentityMatcher() string { - return ociidentity.CONSUMER_TYPE -} - -func (m *accessMethod) Digest() digest.Digest { - d, _ := m.GetDigest() - return d -} - -func (m *accessMethod) GetDigest() (digest.Digest, error) { - m.lock.Lock() - defer m.lock.Unlock() - - err := m.getArtifact() - return m.digest, err -} - -func (m *accessMethod) Get() ([]byte, error) { - blob, err := m.getBlob() - if err != nil { - return nil, err - } - return blob.Get() -} - -func (m *accessMethod) Reader() (io.ReadCloser, error) { - b, err := m.getBlob() - if err != nil { - return nil, err - } - r, err := b.Reader() - if err != nil { - return nil, err - } - return r, nil -} - -func (m *accessMethod) MimeType() string { - if m.mime == "" { - m.lock.Lock() - defer m.lock.Unlock() - m.getArtifact() - } - return m.mime -} - -func (m *accessMethod) getBlob() (artifactset.ArtifactBlob, error) { - m.lock.Lock() - defer m.lock.Unlock() - - if m.blob != nil || m.err != nil { - return m.blob, m.err - } - - err := m.getArtifact() - if err != nil { - return nil, err - } - logger := Logger(WrapContextProvider(m.ctx)) - logger.Info("synthesize artifact blob", "ref", m.reference) - m.blob, err = artifactset.SynthesizeArtifactBlobForArtifact(m.art, m.ref.Version()) - logger.Info("synthesize artifact blob done", "ref", m.reference, "error", logging.ErrorMessage(err)) - if err != nil { - m.err = err - return nil, err - } - return m.blob, nil -} diff --git a/pkg/contexts/ocm/accessmethods/ociartifact/method_test.go b/pkg/contexts/ocm/accessmethods/ociartifact/method_test.go deleted file mode 100644 index 093533e10..000000000 --- a/pkg/contexts/ocm/accessmethods/ociartifact/method_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package ociartifact_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/oci/testhelper" - . "github.com/open-component-model/ocm/pkg/env/builder" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" -) - -const ( - OCIPATH = "/tmp/oci" - OCIHOST = "alias" -) - -var _ = Describe("Method", func() { - var env *Builder - - BeforeEach(func() { - env = NewBuilder() - }) - - AfterEach(func() { - env.Cleanup() - }) - - It("accesses artifact", func() { - env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { - OCIManifest1(env) - }) - - FakeOCIRepo(env, OCIPATH, OCIHOST) - - spec := ociartifact.New(oci.StandardOCIRef(OCIHOST+".alias", OCINAMESPACE, OCIVERSION)) - - m, err := spec.AccessMethod(&cpi.DummyComponentVersionAccess{env.OCMContext()}) - Expect(err).To(Succeed()) - - // no credentials required for CTF as fake OCI registry. - Expect(credentials.GetProvidedConsumerId(m)).To(BeNil()) - Expect(accspeccpi.GetAccessMethodImplementation(m).(blobaccess.DigestSource).Digest().String()).To(Equal("sha256:0c4abdb72cf59cb4b77f4aacb4775f9f546ebc3face189b2224a966c8826ca9f")) - }) -}) diff --git a/pkg/contexts/ocm/accessmethods/ociartifact/utils.go b/pkg/contexts/ocm/accessmethods/ociartifact/utils.go deleted file mode 100644 index d1bb35a05..000000000 --- a/pkg/contexts/ocm/accessmethods/ociartifact/utils.go +++ /dev/null @@ -1,73 +0,0 @@ -package ociartifact - -import ( - "fmt" - "strings" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/oci/grammar" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" -) - -// OCIArtifactReferenceProvider should be implemented by -// access specs providing access to globally retrievable -// OCI artifacts. -type OCIArtifactReferenceProvider interface { - // GetOCIReference returns the externally usable OCI reference. - // The component version miggt be nil. If it is required to - // determine the ref, an appropriate error has to be returned. - GetOCIReference(cv cpi.ComponentVersionAccess) (string, error) -} - -func GetOCIArtifactReference(ctx cpi.Context, spec cpi.AccessSpec, cv cpi.ComponentVersionAccess) (string, error) { - for spec != nil { - eff, err := ctx.AccessSpecForSpec(spec) - if err != nil { - return "", err - } - if p, ok := eff.(OCIArtifactReferenceProvider); ok { - ref, err := p.GetOCIReference(cv) - if ref != "" || err != nil { - return ref, err - } - } - spec = cpi.GlobalAccess(spec, ctx) - if spec == eff { - spec = nil - } - } - return "", nil -} - -//////////////////////////////////////////////////////////////////////////////// - -func Hint(nv common.NameVersion, locator, repo, version string) string { - if i := strings.LastIndex(version, "@"); i >= 0 { - version = version[:i] // remove digest - } - repository := repoName(nv, locator) - if repo != "" { - if strings.HasPrefix(repo, grammar.RepositorySeparator) { - repository = repo[1:] - } else { - repository = repoName(nv, repo) - } - } - if repository != "" && version != "" { - if !strings.Contains(repository, ":") { - repository = fmt.Sprintf("%s:%s", repository, version) - } - } - return repository -} - -func repoName(nv common.NameVersion, locator string) string { - if nv.GetName() == "" { - return locator - } else { - if locator == "" { - return nv.GetName() - } - return fmt.Sprintf("%s/%s", nv.GetName(), locator) - } -} diff --git a/pkg/contexts/ocm/accessmethods/ociblob/cli.go b/pkg/contexts/ocm/accessmethods/ociblob/cli.go deleted file mode 100644 index e71d693e6..000000000 --- a/pkg/contexts/ocm/accessmethods/ociblob/cli.go +++ /dev/null @@ -1,48 +0,0 @@ -package ociblob - -import ( - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/options" -) - -func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { - return flagsets.NewConfigOptionTypeSetHandler( - Type, AddConfig, - options.ReferenceOption, - options.MediatypeOption, - options.SizeOption, - options.DigestOption, - ) -} - -func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { - flagsets.AddFieldByOptionP(opts, options.ReferenceOption, config, "ref") - flagsets.AddFieldByOptionP(opts, options.MediatypeOption, config, "mediaType") - flagsets.AddFieldByOptionP(opts, options.SizeOption, config, "size") - flagsets.AddFieldByOptionP(opts, options.DigestOption, config, "digest") - return nil -} - -var usage = ` -This method implements the access of an OCI blob stored in an OCI repository. -` - -var formatV1 = ` -The type specific specification fields are: - -- **imageReference** *string* - - OCI repository reference (this artifact name used to store the blob). - -- **mediaType** *string* - - The media type of the blob - -- **digest** *string* - - The digest of the blob used to access the blob in the OCI repository. - -- **size** *integer* - - The size of the blob -` diff --git a/pkg/contexts/ocm/accessmethods/ociblob/method.go b/pkg/contexts/ocm/accessmethods/ociblob/method.go deleted file mode 100644 index c9c916eed..000000000 --- a/pkg/contexts/ocm/accessmethods/ociblob/method.go +++ /dev/null @@ -1,193 +0,0 @@ -package ociblob - -import ( - "fmt" - "io" - "sync" - - "github.com/mandelsoft/goutils/errors" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - ociidentity "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -// Type is the access type for a blob in an OCI repository. -const ( - Type = "ociBlob" - 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()))) -} - -// New creates a new OCIBlob accessor. -func New(repository string, digest digest.Digest, mediaType string, size int64) *AccessSpec { - return &AccessSpec{ - ObjectVersionedType: runtime.NewVersionedTypedObject(Type), - Reference: repository, - MediaType: mediaType, - Digest: digest, - Size: size, - } -} - -// AccessSpec describes the access for a oci registry. -type AccessSpec struct { - runtime.ObjectVersionedType `json:",inline"` - - // Reference is the oci reference to the OCI repository - Reference string `json:"ref"` - - // MediaType is the media type of the object this schema refers to. - MediaType string `json:"mediaType,omitempty"` - - // Digest is the digest of the targeted content. - Digest digest.Digest `json:"digest"` - - // Size specifies the size in bytes of the blob. - Size int64 `json:"size"` -} - -var _ accspeccpi.AccessSpec = (*AccessSpec)(nil) - -func (a *AccessSpec) Describe(ctx accspeccpi.Context) string { - return fmt.Sprintf("OCI blob %s in repository %s", a.Digest, a.Reference) -} - -func (s *AccessSpec) IsLocal(context accspeccpi.Context) bool { - return false -} - -func (s *AccessSpec) GlobalAccessSpec(ctx accspeccpi.Context) accspeccpi.AccessSpec { - return s -} - -func (s *AccessSpec) GetMimeType() string { - return s.MediaType -} - -func (s *AccessSpec) AccessMethod(access accspeccpi.ComponentVersionAccess) (accspeccpi.AccessMethod, error) { - return accspeccpi.AccessMethodForImplementation(&accessMethod{comp: access, spec: s}, nil) -} - -//////////////////////////////////////////////////////////////////////////////// - -// TODO add cache - -type accessMethod struct { - lock sync.Mutex - blob blobaccess.BlobAccess - comp accspeccpi.ComponentVersionAccess - spec *AccessSpec -} - -var _ accspeccpi.AccessMethodImpl = (*accessMethod)(nil) - -func (_ *accessMethod) IsLocal() bool { - return false -} - -func (m *accessMethod) GetKind() string { - return Type -} - -func (m *accessMethod) AccessSpec() accspeccpi.AccessSpec { - return m.spec -} - -func (m *accessMethod) Close() error { - var err error - m.lock.Lock() - defer m.lock.Unlock() - - if m.blob != nil { - err = m.blob.Close() - m.blob = nil - } - return err -} - -func (m *accessMethod) Get() ([]byte, error) { - return blobaccess.BlobData(m.getBlob()) -} - -func (m *accessMethod) Reader() (io.ReadCloser, error) { - return blobaccess.BlobReader(m.getBlob()) -} - -func (m *accessMethod) MimeType() string { - return m.spec.MediaType -} - -func (m *accessMethod) getBlob() (blobaccess.BlobAccess, error) { - m.lock.Lock() - defer m.lock.Unlock() - - if m.blob != nil { - return m.blob, nil - } - ref, err := oci.ParseRef(m.spec.Reference) - if err != nil { - return nil, err - } - if ref.Tag != nil || ref.Digest != nil { - return nil, errors.ErrInvalid("oci repository", m.spec.Reference) - } - ocictx := m.comp.GetContext().OCIContext() - spec := ocictx.GetAlias(ref.Host) - if spec == nil { - spec = ocireg.NewRepositorySpec(ref.Host) - } - ocirepo, err := m.comp.GetContext().OCIContext().RepositoryForSpec(spec) - if err != nil { - return nil, err - } - ns, err := ocirepo.LookupNamespace(ref.Repository) - if err != nil { - return nil, err - } - size, acc, err := ns.GetBlobData(m.spec.Digest) - if err != nil { - return nil, err - } - if m.spec.Size == blobaccess.BLOB_UNKNOWN_SIZE { - m.spec.Size = size - } else if size != blobaccess.BLOB_UNKNOWN_SIZE { - return nil, errors.Newf("blob size mismatch %d != %d", size, m.spec.Size) - } - m.blob = blobaccess.ForDataAccess(m.spec.Digest, m.spec.Size, m.spec.MediaType, acc) - return m.blob, nil -} - -func (m *accessMethod) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { - m.lock.Lock() - defer m.lock.Unlock() - - ref, err := oci.ParseRef(m.spec.Reference) - if err != nil { - return nil - } - - ocictx := m.comp.GetContext().OCIContext() - spec := ocictx.GetAlias(ref.Host) - if spec == nil { - spec = ocireg.NewRepositorySpec(ref.Host) - } - ocirepo, err := m.comp.GetContext().OCIContext().RepositoryForSpec(spec) - if err != nil { - return nil - } - return credentials.GetProvidedConsumerId(ocirepo, credentials.StringUsageContext(ref.Repository)) -} - -func (m *accessMethod) GetIdentityMatcher() string { - return ociidentity.CONSUMER_TYPE -} diff --git a/pkg/contexts/ocm/accessmethods/ociblob/method_test.go b/pkg/contexts/ocm/accessmethods/ociblob/method_test.go deleted file mode 100644 index f673bb37a..000000000 --- a/pkg/contexts/ocm/accessmethods/ociblob/method_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package ociblob_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/oci/testhelper" - . "github.com/open-component-model/ocm/pkg/env/builder" - - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/grammar" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" -) - -const ( - OCIPATH = "/tmp/oci" - OCIHOST = "alias" -) - -var _ = Describe("Method", func() { - var env *Builder - - BeforeEach(func() { - env = NewBuilder() - }) - - AfterEach(func() { - env.Cleanup() - }) - - It("accesses artifact", func() { - var desc *artdesc.Descriptor - env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { - desc = OCIManifest1(env) - }) - - FakeOCIRepo(env, OCIPATH, OCIHOST) - - spec := ociblob.New(OCIHOST+".alias"+grammar.RepositorySeparator+OCINAMESPACE, desc.Digest, "", -1) - - m, err := spec.AccessMethod(&cpi.DummyComponentVersionAccess{env.OCMContext()}) - Expect(err).To(Succeed()) - - blob, err := m.Get() - Expect(err).To(Succeed()) - - Expect(string(blob)).To(Equal("manifestlayer")) - }) -}) diff --git a/pkg/contexts/ocm/accessmethods/options/registry.go b/pkg/contexts/ocm/accessmethods/options/registry.go deleted file mode 100644 index 7020df0a4..000000000 --- a/pkg/contexts/ocm/accessmethods/options/registry.go +++ /dev/null @@ -1,105 +0,0 @@ -package options - -import ( - "sync" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/utils" -) - -const ( - KIND_OPTIONTYPE = "option type" - KIND_OPTION = "option" -) - -type OptionTypeCreator func(name string, description string) OptionType - -type ValueTypeInfo struct { - OptionTypeCreator - Description string -} - -func (i ValueTypeInfo) GetDescription() string { - return i.Description -} - -type Registry = *registry - -var DefaultRegistry = New() - -type registry struct { - lock sync.RWMutex - valueTypes map[string]ValueTypeInfo - optionTypes map[string]OptionType -} - -func New() Registry { - return ®istry{ - valueTypes: map[string]ValueTypeInfo{}, - optionTypes: map[string]OptionType{}, - } -} - -func (r *registry) RegisterOptionType(t OptionType) { - r.lock.Lock() - defer r.lock.Unlock() - r.optionTypes[t.GetName()] = t -} - -func (r *registry) RegisterValueType(name string, c OptionTypeCreator, desc string) { - r.lock.Lock() - defer r.lock.Unlock() - r.valueTypes[name] = ValueTypeInfo{OptionTypeCreator: c, Description: desc} -} - -func (r *registry) GetValueType(name string) *ValueTypeInfo { - r.lock.RLock() - defer r.lock.RUnlock() - if t, ok := r.valueTypes[name]; ok { - return &t - } - return nil -} - -func (r *registry) GetOptionType(name string) OptionType { - r.lock.RLock() - defer r.lock.RUnlock() - return r.optionTypes[name] -} - -func (r *registry) CreateOptionType(typ, name, desc string) (OptionType, error) { - r.lock.RLock() - defer r.lock.RUnlock() - t, ok := r.valueTypes[typ] - if !ok { - return nil, errors.ErrUnknown(KIND_OPTIONTYPE, typ) - } - - n := t.OptionTypeCreator(name, desc) - o := r.optionTypes[name] - if o != nil { - if o.ValueType() != n.ValueType() { - return nil, errors.ErrAlreadyExists(KIND_OPTION, name) - } - return o, nil - } - return n, nil -} - -func (r *registry) Usage() string { - r.lock.RLock() - defer r.lock.RUnlock() - - tinfo := utils.FormatMap("", r.valueTypes) - oinfo := utils.FormatMap("", r.optionTypes) - - return ` -The following predefined option types can be used: - -` + oinfo + ` - -The following predefined value types are supported: - -` + tinfo -} diff --git a/pkg/contexts/ocm/accessmethods/options/types.go b/pkg/contexts/ocm/accessmethods/options/types.go deleted file mode 100644 index 96883a564..000000000 --- a/pkg/contexts/ocm/accessmethods/options/types.go +++ /dev/null @@ -1,118 +0,0 @@ -package options - -import ( - "fmt" - - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" -) - -type OptionType interface { - flagsets.ConfigOptionType - ValueType() string - GetDescriptionText() string -} - -type base = flagsets.ConfigOptionType - -type option struct { - base - valueType string -} - -func (o *option) Equal(t flagsets.ConfigOptionType) bool { - if ot, ok := t.(*option); ok { - return o.valueType == ot.valueType && o.GetName() == ot.GetName() - } - return false -} - -func (o *option) ValueType() string { - return o.valueType -} - -func (o *option) GetDescription() string { - return fmt.Sprintf("[*%s*] %s", o.ValueType(), o.base.GetDescription()) -} - -func (o *option) GetDescriptionText() string { - return o.base.GetDescription() -} - -//////////////////////////////////////////////////////////////////////////////// - -func NewStringOptionType(name, desc string) OptionType { - return &option{ - base: flagsets.NewStringOptionType(name, desc), - valueType: TYPE_STRING, - } -} - -func NewStringArrayOptionType(name, desc string) OptionType { - return &option{ - base: flagsets.NewStringArrayOptionType(name, desc), - valueType: TYPE_STRINGARRAY, - } -} - -func NewIntOptionType(name, desc string) OptionType { - return &option{ - base: flagsets.NewIntOptionType(name, desc), - valueType: TYPE_INT, - } -} - -func NewBoolOptionType(name, desc string) OptionType { - return &option{ - base: flagsets.NewBoolOptionType(name, desc), - valueType: TYPE_BOOL, - } -} - -func NewYAMLOptionType(name, desc string) OptionType { - return &option{ - base: flagsets.NewYAMLOptionType(name, desc), - valueType: TYPE_YAML, - } -} - -func NewValueMapYAMLOptionType(name, desc string) OptionType { - return &option{ - base: flagsets.NewValueMapYAMLOptionType(name, desc), - valueType: TYPE_STRINGMAPYAML, - } -} - -func NewValueMapOptionType(name, desc string) OptionType { - return &option{ - base: flagsets.NewValueMapOptionType(name, desc), - valueType: TYPE_STRING2YAML, - } -} - -func NewStringMapOptionType(name, desc string) OptionType { - return &option{ - base: flagsets.NewStringMapOptionType(name, desc), - valueType: TYPE_STRING2STRING, - } -} - -func NewStringSliceMapOptionType(name, desc string) OptionType { - return &option{ - base: flagsets.NewStringSliceMapOptionType(name, desc), - valueType: TYPE_STRING2STRINGSLICE, - } -} - -func NewStringSliceMapColonOptionType(name, desc string) OptionType { - return &option{ - base: flagsets.NewStringSliceMapColonOptionType(name, desc), - valueType: TYPE_STRINGCOLONSTRINGSLICE, - } -} - -func NewBytesOptionType(name, desc string) OptionType { - return &option{ - base: flagsets.NewBytesOptionType(name, desc), - valueType: TYPE_BYTES, - } -} diff --git a/pkg/contexts/ocm/accessmethods/plugin/cmd_test.go b/pkg/contexts/ocm/accessmethods/plugin/cmd_test.go deleted file mode 100644 index 2c2ccc035..000000000 --- a/pkg/contexts/ocm/accessmethods/plugin/cmd_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package plugin_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/testutils" - . "github.com/open-component-model/ocm/pkg/env" - - "github.com/mandelsoft/goutils/sliceutils" - "github.com/mandelsoft/goutils/transformer" - "github.com/spf13/pflag" - - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/options" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/plugins" - "github.com/open-component-model/ocm/pkg/contexts/ocm/registration" -) - -const ( - CA = "/tmp/ca" - VERSION = "v1" -) - -var _ = Describe("Add with new access method", func() { - var env *Environment - var ctx ocm.Context - var registry plugins.Set - var plugins TempPluginDir - - BeforeEach(func() { - env = NewEnvironment(TestData()) - ctx = env.OCMContext() - plugins, registry = Must2(ConfigureTestPlugins2(env, "testdata")) - Expect(registration.RegisterExtensions(ctx)).To(Succeed()) - p := registry.Get("test") - Expect(p).NotTo(BeNil()) - }) - - AfterEach(func() { - plugins.Cleanup() - env.Cleanup() - }) - - It("handles resource options", func() { - at := ctx.AccessMethods().GetType("test") - Expect(at).NotTo(BeNil()) - - h := at.ConfigOptionTypeSetHandler() - Expect(h).NotTo(BeNil()) - Expect(h.GetName()).To(Equal("test")) - - ot := h.OptionTypes() - Expect(len(ot)).To(Equal(2)) - - opts := h.CreateOptions() - Expect(sliceutils.Transform(opts.Options(), transformer.GetName[flagsets.Option, string])).To(ConsistOf( - "mediaType", "accessPath")) - - fs := &pflag.FlagSet{} - fs.SortFlags = true - opts.AddFlags(fs) - - Expect("\n" + fs.FlagUsages()).To(Equal(` - --accessPath string file path - --mediaType string media type for artifact blob representation -`)) - - MustBeSuccessful(fs.Parse([]string{"--accessPath", "filepath", "--" + options.MediatypeOption.GetName(), "yaml"})) - - cfg := flagsets.Config{} - MustBeSuccessful(h.ApplyConfig(opts, cfg)) - Expect(cfg).To(YAMLEqual(` -mediaType: yaml -path: filepath -`)) - }) -}) diff --git a/pkg/contexts/ocm/accessmethods/plugin/method.go b/pkg/contexts/ocm/accessmethods/plugin/method.go deleted file mode 100644 index 9fb8f9e04..000000000 --- a/pkg/contexts/ocm/accessmethods/plugin/method.go +++ /dev/null @@ -1,144 +0,0 @@ -package plugin - -import ( - "encoding/json" - "io" - "sync" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "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/identity/hostpath" - "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/plugin" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -type AccessSpec struct { - runtime.UnstructuredVersionedTypedObject `json:",inline"` - handler *PluginHandler -} - -var ( - _ cpi.AccessSpec = &AccessSpec{} - _ cpi.HintProvider = &AccessSpec{} -) - -func (s *AccessSpec) AccessMethod(cv cpi.ComponentVersionAccess) (cpi.AccessMethod, error) { - return s.handler.AccessMethod(s, cv) -} - -func (s *AccessSpec) Describe(ctx cpi.Context) string { - return s.handler.Describe(s, ctx) -} - -func (_ *AccessSpec) IsLocal(cpi.Context) bool { - return false -} - -func (s *AccessSpec) GlobalAccessSpec(cpi.Context) cpi.AccessSpec { - return s -} - -func (s *AccessSpec) GetMimeType() string { - return s.handler.GetMimeType(s) -} - -func (s *AccessSpec) GetReferenceHint(cv cpi.ComponentVersionAccess) string { - return s.handler.GetReferenceHint(s, cv) -} - -func (s *AccessSpec) Handler() *PluginHandler { - return s.handler -} - -//////////////////////////////////////////////////////////////////////////////// - -type accessMethod struct { - lock sync.Mutex - blob blobaccess.BlobAccess - ctx ocm.Context - - handler *PluginHandler - spec *AccessSpec - info *ppi.AccessSpecInfo - creds json.RawMessage -} - -var _ cpi.AccessMethodImpl = (*accessMethod)(nil) - -func newMethod(p *PluginHandler, spec *AccessSpec, ctx ocm.Context, info *ppi.AccessSpecInfo, creds json.RawMessage) *accessMethod { - return &accessMethod{ - ctx: ctx, - handler: p, - spec: spec, - info: info, - creds: creds, - } -} - -func (_ *accessMethod) IsLocal() bool { - return false -} - -func (m *accessMethod) GetKind() string { - return m.spec.GetKind() -} - -func (m *accessMethod) AccessSpec() cpi.AccessSpec { - return m.spec -} - -func (m *accessMethod) Close() error { - var err error - m.lock.Lock() - defer m.lock.Unlock() - if m.blob != nil { - err = m.blob.Close() - m.blob = nil - } - return err -} - -func (m *accessMethod) Get() ([]byte, error) { - return blobaccess.BlobData(m.getBlob()) -} - -func (m *accessMethod) Reader() (io.ReadCloser, error) { - return blobaccess.BlobReader(m.getBlob()) -} - -func (m *accessMethod) MimeType() string { - return m.info.MediaType -} - -func (m *accessMethod) getBlob() (blobaccess.BlobAccess, error) { - m.lock.Lock() - defer m.lock.Unlock() - - if m.blob != nil { - return m.blob, nil - } - - spec, err := json.Marshal(m.spec) - if err != nil { - return nil, errors.Wrapf(err, "cannot marshal access spec") - } - m.blob = accessobj.CachedBlobAccessForWriter(m.ctx, m.MimeType(), plugin.NewAccessDataWriter(m.handler.plug, m.creds, spec)) - return m.blob, nil -} - -func (m *accessMethod) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { - if len(m.info.ConsumerId) == 0 { - return nil - } - return m.info.ConsumerId -} - -func (m *accessMethod) GetIdentityMatcher() string { - return hostpath.IDENTITY_TYPE -} diff --git a/pkg/contexts/ocm/accessmethods/plugin/method_test.go b/pkg/contexts/ocm/accessmethods/plugin/method_test.go deleted file mode 100644 index 581c00fbe..000000000 --- a/pkg/contexts/ocm/accessmethods/plugin/method_test.go +++ /dev/null @@ -1,84 +0,0 @@ -//go:build unix - -package plugin_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/testutils" - . "github.com/open-component-model/ocm/pkg/env/builder" - - "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/ocm" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/plugins" - "github.com/open-component-model/ocm/pkg/contexts/ocm/registration" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" -) - -const ( - ARCH = "ctf" - COMP = "github.com/mandelsoft/comp" - VERS = "1.0.0" - PROVIDER = "mandelsoft" -) - -var _ = Describe("setup plugin cache", func() { - var ctx ocm.Context - var registry plugins.Set - var env *Builder - var plugins TempPluginDir - - var accessSpec ocm.AccessSpec - - BeforeEach(func() { - var err error - - accessSpec, err = ocm.NewGenericAccessSpec(` -type: test -someattr: value -`) - Expect(err).To(Succeed()) - - env = NewBuilder(nil) - ctx = env.OCMContext() - plugins, registry = Must2(ConfigureTestPlugins2(env, "testdata")) - Expect(registration.RegisterExtensions(ctx)).To(Succeed()) - p := registry.Get("test") - Expect(p).NotTo(BeNil()) - }) - - AfterEach(func() { - plugins.Cleanup() - env.Cleanup() - }) - - It("registers access methods", func() { - env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { - env.Component(COMP, func() { - env.Version(VERS, func() { - env.Provider(PROVIDER) - env.Resource("testdata", VERS, "PlainText", metav1.ExternalRelation, func() { - env.Access(accessSpec) - }) - }) - }) - }) - - repo := Must(ctf.Open(ctx, accessobj.ACC_READONLY, ARCH, 0, env)) - defer Close(repo) - - cv := Must(repo.LookupComponentVersion(COMP, VERS)) - defer Close(cv) - - r := Must(cv.GetResourceByIndex(0)) - - m := Must(r.AccessMethod()) - Expect(m.MimeType()).To(Equal("plain/text")) - - data := Must(m.Get()) - Expect(string(data)).To(Equal("test content\n{\"someattr\":\"value\",\"type\":\"test\"}\n")) - }) -}) diff --git a/pkg/contexts/ocm/accessmethods/plugin/plugin.go b/pkg/contexts/ocm/accessmethods/plugin/plugin.go deleted file mode 100644 index 711e99d8d..000000000 --- a/pkg/contexts/ocm/accessmethods/plugin/plugin.go +++ /dev/null @@ -1,134 +0,0 @@ -package plugin - -import ( - "bytes" - "encoding/json" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/identity/hostpath" - "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/plugin" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/errkind" -) - -type plug = plugin.Plugin - -// PluginHandler is a shared object between the AccessMethod implementation and the AccessSpec implementation. The -// object knows the actual plugin and can therefore forward the method calls to corresponding cli commands. -type PluginHandler struct { - plug - - // cached info - info *ppi.AccessSpecInfo - err error - orig []byte -} - -func NewPluginHandler(p plugin.Plugin) *PluginHandler { - return &PluginHandler{plug: p} -} - -func (p *PluginHandler) Info(spec *AccessSpec) (*ppi.AccessSpecInfo, error) { - if p.info != nil || p.err != nil { - raw, err := spec.UnstructuredVersionedTypedObject.GetRaw() - if err != nil { - return nil, errors.Wrapf(err, "cannot marshal access specification") - } - if bytes.Equal(raw, p.orig) { - return p.info, p.err - } - } - p.info, p.err = p.Validate(spec) - return p.info, p.err -} - -func (p *PluginHandler) AccessMethod(spec *AccessSpec, cv cpi.ComponentVersionAccess) (cpi.AccessMethod, error) { - mspec := p.GetAccessMethodDescriptor(spec.GetKind(), spec.GetVersion()) - if mspec == nil { - return nil, errors.ErrNotFound(errkind.KIND_ACCESSMETHOD, spec.GetType(), descriptor.KIND_PLUGIN, p.Name()) - } - - creddata, err := p.getCredentialData(spec, cv) - if err != nil { - return nil, err - } - - info, err := p.Info(spec) - if err != nil { - return nil, err - } - return accspeccpi.AccessMethodForImplementation(newMethod(p, spec, cv.GetContext(), info, creddata), nil) -} - -func (p *PluginHandler) getCredentialData(spec *AccessSpec, cv cpi.ComponentVersionAccess) (json.RawMessage, error) { - info, err := p.Info(spec) - if err != nil { - return nil, err - } - - var creds credentials.Credentials - if len(info.ConsumerId) > 0 { - creds, err = credentials.CredentialsForConsumer(cv.GetContext(), info.ConsumerId, hostpath.IdentityMatcher(info.ConsumerId.Type())) - if err != nil { - return nil, err - } - } - - var creddata json.RawMessage - if creds != nil { - creddata, err = json.Marshal(creds) - if err != nil { - return nil, err - } - } - return creddata, nil -} - -func (p *PluginHandler) Describe(spec *AccessSpec, ctx cpi.Context) string { - mspec := p.GetAccessMethodDescriptor(spec.GetKind(), spec.GetVersion()) - if mspec == nil { - return "unknown type " + spec.GetType() - } - info, err := p.Info(spec) - if err != nil { - return err.Error() - } - return info.Short -} - -func (p *PluginHandler) GetMimeType(spec *AccessSpec) string { - mspec := p.GetAccessMethodDescriptor(spec.GetKind(), spec.GetVersion()) - if mspec == nil { - return "unknown type " + spec.GetType() - } - info, err := p.Info(spec) - if err != nil { - return "" - } - return info.Short -} - -func (p *PluginHandler) GetReferenceHint(spec *AccessSpec, cv cpi.ComponentVersionAccess) string { - mspec := p.GetAccessMethodDescriptor(spec.GetKind(), spec.GetVersion()) - if mspec == nil { - return "unknown type " + spec.GetType() - } - info, err := p.Info(spec) - if err != nil { - return "" - } - return info.Hint -} - -func (p *PluginHandler) Validate(spec *AccessSpec) (*ppi.AccessSpecInfo, error) { - data, err := spec.GetRaw() - if err != nil { - return nil, err - } - return p.plug.ValidateAccessMethod(data) -} diff --git a/pkg/contexts/ocm/accessmethods/plugin/type.go b/pkg/contexts/ocm/accessmethods/plugin/type.go deleted file mode 100644 index e050a6ae7..000000000 --- a/pkg/contexts/ocm/accessmethods/plugin/type.go +++ /dev/null @@ -1,69 +0,0 @@ -package plugin - -import ( - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/options" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin" - "github.com/open-component-model/ocm/pkg/runtime" -) - -type accessType struct { - accspeccpi.AccessType - plug plugin.Plugin - cliopts flagsets.ConfigOptionTypeSet -} - -var _ accspeccpi.AccessType = (*accessType)(nil) - -func NewType(name string, p plugin.Plugin, desc *plugin.AccessMethodDescriptor) accspeccpi.AccessType { - format := desc.Format - if format != "" { - format = "\n" + format - } - - t := &accessType{ - plug: p, - } - - cfghdlr := flagsets.NewConfigOptionTypeSetHandler(name, t.AddConfig) - for _, o := range desc.CLIOptions { - var opt flagsets.ConfigOptionType - if o.Type == "" { - opt = options.DefaultRegistry.GetOptionType(o.Name) - if opt == nil { - p.Context().Logger(plugin.TAG).Warn("unknown option", "plugin", p.Name(), "accessmethod", name, "option", o.Name) - } - } else { - var err error - opt, err = options.DefaultRegistry.CreateOptionType(o.Type, o.Name, o.Description) - if err != nil { - p.Context().Logger(plugin.TAG).Warn("invalid option", "plugin", p.Name(), "accessmethod", name, "option", o.Name, "error", err.Error()) - } - } - if opt != nil { - cfghdlr.AddOptionType(opt) - } - } - aopts := []accspeccpi.AccessSpecTypeOption{accspeccpi.WithDescription(desc.Description), accspeccpi.WithFormatSpec(format)} - if cfghdlr.Size() > 0 { - aopts = append(aopts, accspeccpi.WithConfigHandler(cfghdlr)) - t.cliopts = cfghdlr - } - t.AccessType = accspeccpi.NewAccessSpecType[*AccessSpec](name, aopts...) - return t -} - -func (t *accessType) Decode(data []byte, unmarshaler runtime.Unmarshaler) (accspeccpi.AccessSpec, error) { - spec, err := t.AccessType.Decode(data, unmarshaler) - if err != nil { - return nil, err - } - spec.(*AccessSpec).handler = NewPluginHandler(t.plug) - return spec, nil -} - -func (t *accessType) AddConfig(opts flagsets.ConfigOptions, cfg flagsets.Config) error { - opts = opts.FilterBy(t.cliopts.HasOptionType) - return t.plug.ComposeAccessMethod(t.GetType(), opts, cfg) -} diff --git a/pkg/contexts/ocm/accessmethods/relativeociref/method.go b/pkg/contexts/ocm/accessmethods/relativeociref/method.go deleted file mode 100644 index 8066eec65..000000000 --- a/pkg/contexts/ocm/accessmethods/relativeociref/method.go +++ /dev/null @@ -1,88 +0,0 @@ -package relativeociref - -import ( - "fmt" - - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" - "github.com/open-component-model/ocm/pkg/runtime" -) - -// Type describes the access of an OCI artifact stored as OCI artifact in the OCI -// registry hosting the actual component version. -const ( - Type = "relativeOciReference" - TypeV1 = Type + runtime.VersionSeparator + "v1" -) - -func init() { - accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](Type)) - accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](TypeV1)) -} - -var _ accspeccpi.HintProvider = (*AccessSpec)(nil) - -// New creates a new localFilesystemBlob accessor. -func New(ref string) *AccessSpec { - return &AccessSpec{ - ObjectVersionedType: runtime.NewVersionedObjectType(Type), - Reference: ref, - } -} - -// AccessSpec describes the access of an OCI artifact stored as OCI artifact in -// the OCI registry hosting the actual component version. -type AccessSpec struct { - runtime.ObjectVersionedType `json:",inline"` - // Reference is the OCI repository name and version separated by a colon. - Reference string `json:"reference"` -} - -func (a *AccessSpec) Describe(context accspeccpi.Context) string { - return fmt.Sprintf("local OCI artifact %s", a.Reference) -} - -func (a *AccessSpec) IsLocal(context accspeccpi.Context) bool { - return true -} - -func (a *AccessSpec) GlobalAccessSpec(context accspeccpi.Context) accspeccpi.AccessSpec { - return nil -} - -func (a *AccessSpec) AccessMethod(access accspeccpi.ComponentVersionAccess) (accspeccpi.AccessMethod, error) { - return access.AccessMethod(a) -} - -func (a *AccessSpec) GetDigest() (string, bool) { - ref, err := oci.ParseRef(a.Reference) - if err != nil { - return "", true - } - if ref.Digest != nil { - return ref.Digest.String(), true - } - return "", false -} - -func (a *AccessSpec) GetReferenceHint(cv internal.ComponentVersionAccess) string { - return a.Reference -} - -func (a *AccessSpec) GetOCIReference(cv accspeccpi.ComponentVersionAccess) (string, error) { - if cv == nil { - return "", fmt.Errorf("component version required to determine OCI reference") - } - m, err := a.AccessMethod(cv) - if err != nil { - return "", err - } - defer m.Close() - - if o, ok := accspeccpi.GetAccessMethodImplementation(m).(ociartifact.OCIArtifactReferenceProvider); ok { - return o.GetOCIReference(nil) - } - return "", nil -} diff --git a/pkg/contexts/ocm/accessmethods/relativeociref/method_test.go b/pkg/contexts/ocm/accessmethods/relativeociref/method_test.go deleted file mode 100644 index 65de51186..000000000 --- a/pkg/contexts/ocm/accessmethods/relativeociref/method_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package relativeociref_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/oci/testhelper" - . "github.com/open-component-model/ocm/pkg/env/builder" - - "github.com/mandelsoft/goutils/finalizer" - - "github.com/open-component-model/ocm/pkg/blobaccess/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/ocm/accessmethods/relativeociref" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" -) - -const ( - OCIPATH = "/tmp/oci" - OCIHOST = "alias" -) - -const ( - COMP = "acme.org/compo" - COMPVERS = "v1.0.0" - RES = "ref" -) - -var _ = Describe("Method", func() { - var env *Builder - - BeforeEach(func() { - env = NewBuilder() - }) - - AfterEach(func() { - env.Cleanup() - }) - - It("accesses artifact", func() { - var finalize finalizer.Finalizer - defer Defer(finalize.Finalize) - - env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { - OCIManifest1(env) - }) - FakeOCIRepo(env, OCIPATH, OCIHOST) - - env.OCMCommonTransport(OCIPATH, accessio.FormatDirectory, func() { - env.ComponentVersion(COMP, COMPVERS, func() { - env.Resource(RES, COMPVERS, "testtyp", v1.LocalRelation, func() { - env.Access(relativeociref.New(OCINAMESPACE + ":" + OCIVERSION)) - }) - }) - }) - - repo := Must(ctf.Open(env, accessobj.ACC_READONLY, OCIPATH, 0, env)) - finalize.Close(repo) - vers := Must(repo.LookupComponentVersion(COMP, COMPVERS)) - finalize.Close(vers) - res := Must(vers.GetResourceByIndex(0)) - m := Must(res.AccessMethod()) - finalize.With(func() error { - return m.Close() - }) - data := Must(m.Get()) - Expect(len(data)).To(Equal(628)) - Expect(accspeccpi.GetAccessMethodImplementation(m).(blobaccess.DigestSource).Digest().String()).To(Equal("sha256:0c4abdb72cf59cb4b77f4aacb4775f9f546ebc3face189b2224a966c8826ca9f")) - Expect(utils.GetOCIArtifactRef(env, res)).To(Equal("ocm/value:v2.0")) - }) -}) diff --git a/pkg/contexts/ocm/accessmethods/relativeociref/transfer_test.go b/pkg/contexts/ocm/accessmethods/relativeociref/transfer_test.go deleted file mode 100644 index 5435603a1..000000000 --- a/pkg/contexts/ocm/accessmethods/relativeociref/transfer_test.go +++ /dev/null @@ -1,149 +0,0 @@ -package relativeociref_test - -import ( - "encoding/json" - "fmt" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/oci/testhelper" - . "github.com/open-component-model/ocm/pkg/env/builder" - - "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/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - ocictf "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/relativeociref" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/keepblobattr" - storagecontext "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/oci" - "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/oci/ocirepo" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" -) - -const OUT = "/tmp/res" - -func FakeOCIRegBaseFunction(ctx *storagecontext.StorageContext) string { - return "baseurl.io" -} - -var _ = Describe("Transfer handler", func() { - var env *Builder - var ldesc *artdesc.Descriptor - - BeforeEach(func() { - env = NewBuilder() - - env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { - ldesc = OCIManifest1(env) - }) - - env.OCMCommonTransport(OCIPATH, accessio.FormatDirectory, func() { - env.ComponentVersion(COMP, COMPVERS, func() { - env.Resource("artifact", "", resourcetypes.OCI_IMAGE, metav1.LocalRelation, func() { - env.Access( - relativeociref.New(OCINAMESPACE + ":" + OCIVERSION), - ) - }) - }) - }) - }) - - AfterEach(func() { - env.Cleanup() - }) - - It("it should copy an image by value to a ctf file", func() { - src, err := ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, OCIPATH, 0, env) - Expect(err).To(Succeed()) - cv, err := src.LookupComponentVersion(COMP, COMPVERS) - Expect(err).To(Succeed()) - tgt, err := ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env) - Expect(err).To(Succeed()) - defer tgt.Close() - opts := &standard.Options{} - opts.SetResourcesByValue(true) - handler := standard.NewDefaultHandler(opts) - // handler, err := standard.New(standard.ResourcesByValue()) - Expect(err).To(Succeed()) - err = transfer.TransferVersion(nil, nil, cv, tgt, handler) - Expect(err).To(Succeed()) - Expect(env.DirExists(OUT)).To(BeTrue()) - - list, err := tgt.ComponentLister().GetComponents("", true) - Expect(err).To(Succeed()) - Expect(list).To(Equal([]string{COMP})) - comp, err := tgt.LookupComponentVersion(COMP, COMPVERS) - Expect(err).To(Succeed()) - Expect(len(comp.GetDescriptor().Resources)).To(Equal(1)) - Expect(comp.GetDescriptor().Resources[0].Access.GetType()).To(Equal(localblob.Type)) - data, err := json.Marshal(comp.GetDescriptor().Resources[0].Access) - Expect(err).To(Succeed()) - - fmt.Printf("%s\n", string(data)) - hash := HashManifest1(artifactset.DefaultArtifactSetDescriptorFileName) - Expect(string(data)).To(StringEqualWithContext(fmt.Sprintf(`{"localReference":"%s","mediaType":"application/vnd.oci.image.manifest.v1+tar+gzip","referenceName":"ocm/value:v2.0","type":"localBlob"}`, hash))) - - r, err := comp.GetResourceByIndex(0) - Expect(err).To(Succeed()) - meth, err := r.AccessMethod() - Expect(err).To(Succeed()) - defer meth.Close() - reader, err := meth.Reader() - Expect(err).To(Succeed()) - defer reader.Close() - set, err := artifactset.Open(accessobj.ACC_READONLY, "", 0, accessio.Reader(reader)) - Expect(err).To(Succeed()) - defer set.Close() - - _, blob, err := set.GetBlobData(ldesc.Digest) - Expect(err).To(Succeed()) - data, err = blob.Get() - Expect(err).To(Succeed()) - Expect(string(data)).To(Equal("manifestlayer")) - }) - - It("it should copy an image by value to an oci repo with uploader", func() { - env.OCMContext().BlobHandlers().Register(ocirepo.NewArtifactHandler(FakeOCIRegBaseFunction), - cpi.ForRepo(oci.CONTEXT_TYPE, ocictf.Type), cpi.ForMimeType(artdesc.ToContentMediaType(artdesc.MediaTypeImageManifest))) - keepblobattr.Set(env.OCMContext(), true) - - src, err := ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, OCIPATH, 0, env) - Expect(err).To(Succeed()) - cv, err := src.LookupComponentVersion(COMP, COMPVERS) - Expect(err).To(Succeed()) - - tgt := Must(ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) - defer tgt.Close() - opts := &standard.Options{} - opts.SetResourcesByValue(true) - handler := standard.NewDefaultHandler(opts) - // handler, err := standard.New(standard.ResourcesByValue()) - Expect(err).To(Succeed()) - err = transfer.TransferVersion(nil, nil, cv, tgt, handler) - Expect(err).To(Succeed()) - Expect(env.DirExists(OUT)).To(BeTrue()) - - list, err := tgt.ComponentLister().GetComponents("", true) - Expect(err).To(Succeed()) - Expect(list).To(Equal([]string{COMP})) - comp, err := tgt.LookupComponentVersion(COMP, COMPVERS) - Expect(err).To(Succeed()) - Expect(len(comp.GetDescriptor().Resources)).To(Equal(1)) - Expect(comp.GetDescriptor().Resources[0].Access.GetType()).To(Equal(localblob.Type)) - data, err := json.Marshal(comp.GetDescriptor().Resources[0].Access) - Expect(err).To(Succeed()) - - fmt.Printf("%s\n", string(data)) - hash := HashManifest1(artifactset.DefaultArtifactSetDescriptorFileName) - Expect(string(data)).To(StringEqualWithContext(fmt.Sprintf(`{"globalAccess":{"imageReference":"baseurl.io/ocm/value:v2.0","type":"ociArtifact"},"localReference":"%s","mediaType":"application/vnd.oci.image.manifest.v1+tar+gzip","referenceName":"ocm/value:v2.0","type":"localBlob"}`, hash))) - }) -}) diff --git a/pkg/contexts/ocm/accessmethods/s3/cli.go b/pkg/contexts/ocm/accessmethods/s3/cli.go deleted file mode 100644 index b8d452ab3..000000000 --- a/pkg/contexts/ocm/accessmethods/s3/cli.go +++ /dev/null @@ -1,30 +0,0 @@ -package s3 - -import ( - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/options" -) - -func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { - return flagsets.NewConfigOptionTypeSetHandler( - Type, AddConfig, - options.RegionOption, - options.BucketOption, - options.ReferenceOption, - options.MediatypeOption, - options.VersionOption, - ) -} - -func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { - flagsets.AddFieldByOptionP(opts, options.ReferenceOption, config, "key") - flagsets.AddFieldByOptionP(opts, options.MediatypeOption, config, "mediaType") - flagsets.AddFieldByOptionP(opts, options.RegionOption, config, "region") - flagsets.AddFieldByOptionP(opts, options.BucketOption, config, "bucket") - flagsets.AddFieldByOptionP(opts, options.VersionOption, config, "version") - return nil -} - -var usage = ` -This method implements the access of a blob stored in an S3 bucket. -` diff --git a/pkg/contexts/ocm/accessmethods/s3/identity/identity.go b/pkg/contexts/ocm/accessmethods/s3/identity/identity.go deleted file mode 100644 index 2df1faacd..000000000 --- a/pkg/contexts/ocm/accessmethods/s3/identity/identity.go +++ /dev/null @@ -1,66 +0,0 @@ -package identity - -import ( - "path" - "strings" - - "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/listformat" -) - -const CONSUMER_TYPE = "S3" - -// identity properties. -const ( - ID_HOSTNAME = hostpath.ID_HOSTNAME - ID_PORT = hostpath.ID_PORT - ID_PATHPREFIX = hostpath.ID_PATHPREFIX -) - -// credential properties. -const ( - ATTR_AWS_ACCESS_KEY_ID = "awsAccessKeyID" - ATTR_AWS_SECRET_ACCESS_KEY = "awsSecretAccessKey" - ATTR_TOKEN = cpi.ATTR_TOKEN -) - -const GITHUB = "github.com" - -var identityMatcher = hostpath.IdentityMatcher(CONSUMER_TYPE) - -func IdentityMatcher(pattern, cur, id cpi.ConsumerIdentity) bool { - return identityMatcher(pattern, cur, id) -} - -func init() { - attrs := listformat.FormatListElements("", listformat.StringElementDescriptionList{ - ATTR_AWS_ACCESS_KEY_ID, "AWS access key id", - ATTR_AWS_SECRET_ACCESS_KEY, "AWS secret for access key id", - ATTR_TOKEN, "AWS access token (alternatively)", - }) - cpi.RegisterStandardIdentity(CONSUMER_TYPE, identityMatcher, - `S3 credential matcher - -This matcher is a hostpath matcher.`, - attrs) -} - -func GetConsumerId(host, bucket, key, version string) cpi.ConsumerIdentity { - id := cpi.NewConsumerIdentity(CONSUMER_TYPE) - - parts := strings.Split(host, ":") - if parts[0] != "" { - id[ID_HOSTNAME] = parts[0] - } - if len(parts) > 1 { - id[ID_PORT] = parts[1] - } - id[ID_PATHPREFIX] = path.Join(bucket, key, version) - return id -} - -func GetCredentials(ctx cpi.ContextProvider, host, bucket, key, version string) (cpi.Credentials, error) { - id := GetConsumerId(host, bucket, key, version) - return cpi.CredentialsForConsumer(ctx.CredentialsContext(), id, identityMatcher) -} diff --git a/pkg/contexts/ocm/accessmethods/s3/ifce_test.go b/pkg/contexts/ocm/accessmethods/s3/ifce_test.go deleted file mode 100644 index 0a31c93fb..000000000 --- a/pkg/contexts/ocm/accessmethods/s3/ifce_test.go +++ /dev/null @@ -1,9 +0,0 @@ -package s3 - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" -) - -func Versions() accspeccpi.AccessTypeVersionScheme { - return versions -} diff --git a/pkg/contexts/ocm/accessmethods/s3/method.go b/pkg/contexts/ocm/accessmethods/s3/method.go deleted file mode 100644 index 50ff881df..000000000 --- a/pkg/contexts/ocm/accessmethods/s3/method.go +++ /dev/null @@ -1,175 +0,0 @@ -package s3 - -import ( - "fmt" - - . "github.com/mandelsoft/goutils/exception" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/common/accessio/downloader" - "github.com/open-component-model/ocm/pkg/common/accessio/downloader/s3" - "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/identity/hostpath" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/s3/identity" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -// Type is the access type of S3 registry. -const ( - Type = "s3" - - LegacyType = "S3" - LegacyTypeV1 = LegacyType + runtime.VersionSeparator + "v1" -) - -var versions = accspeccpi.NewAccessTypeVersionScheme(Type).WithKindAliases(LegacyType) - -var formats = accspeccpi.NewAccessSpecFormatVersionRegistry() - -func init() { - formats.Register(Type, runtime.NewConvertedVersion[accspeccpi.AccessSpec, *AccessSpec, *AccessSpecV1](&converterV1{})) - formats.Register(LegacyType, runtime.NewConvertedVersion[accspeccpi.AccessSpec, *AccessSpec, *AccessSpecV1](&converterV1{})) - - initV1() - initV2() - - anon := accspeccpi.MustNewAccessSpecMultiFormatVersion(Type, formats) - Must(versions.Register(accspeccpi.NewAccessSpecTypeByFormatVersion(Type, anon, accspeccpi.WithDescription(usage), accspeccpi.WithConfigHandler(ConfigHandler())))) - Must(versions.Register(accspeccpi.NewAccessSpecTypeByFormatVersion(LegacyType, anon, accspeccpi.WithDescription(usage)))) - accspeccpi.RegisterAccessTypeVersions(versions) -} - -// AccessSpec describes the access for a S3 registry. -type AccessSpec struct { - runtime.InternalVersionedTypedObject[accspeccpi.AccessSpec] - - // Region needs to be set even though buckets are global. - // We can't assume that there is a default region setting sitting somewhere. - // +optional - Region string - // Bucket where the s3 object is located. - Bucket string - // Key of the object to look for. This value will be used together with Bucket and Version to form an identity. - Key string - // Version of the object. - // +optional - Version string - // MediaType defines the mime type of the object to download. - // +optional - MediaType string - downloader downloader.Downloader -} - -var _ accspeccpi.AccessSpec = (*AccessSpec)(nil) - -// New creates a new GitHub registry access spec version v1. -func New(region, bucket, key, version, mediaType string, downloader ...downloader.Downloader) *AccessSpec { - return &AccessSpec{ - InternalVersionedTypedObject: runtime.NewInternalVersionedTypedObject[accspeccpi.AccessSpec](versions, Type), - Region: region, - Bucket: bucket, - Key: key, - Version: version, - MediaType: mediaType, - downloader: utils.Optional(downloader...), - } -} - -func (a AccessSpec) MarshalJSON() ([]byte, error) { - return runtime.MarshalVersionedTypedObject(&a) -} - -func (a *AccessSpec) Describe(ctx accspeccpi.Context) string { - return fmt.Sprintf("S3 key %s in bucket %s", a.Key, a.Bucket) -} - -func (_ *AccessSpec) IsLocal(accspeccpi.Context) bool { - return false -} - -func (a *AccessSpec) GlobalAccessSpec(ctx accspeccpi.Context) accspeccpi.AccessSpec { - return a -} - -func (a *AccessSpec) AccessMethod(c accspeccpi.ComponentVersionAccess) (accspeccpi.AccessMethod, error) { - return accspeccpi.AccessMethodForImplementation(newMethod(c, a)) -} - -//////////////////////////////////////////////////////////////////////////////// - -type accessMethod struct { - blobaccess.BlobAccess - - comp accspeccpi.ComponentVersionAccess - spec *AccessSpec -} - -var _ accspeccpi.AccessMethodImpl = (*accessMethod)(nil) - -func newMethod(c accspeccpi.ComponentVersionAccess, a *AccessSpec) (*accessMethod, error) { - creds, err := getCreds(a, c.GetContext().CredentialsContext()) - if err != nil { - return nil, fmt.Errorf("failed to get creds: %w", err) - } - - var ( - accessKeyID string - accessSecret string - ) - if creds != nil { - accessKeyID = creds.GetProperty(identity.ATTR_AWS_ACCESS_KEY_ID) - accessSecret = creds.GetProperty(identity.ATTR_AWS_SECRET_ACCESS_KEY) - } - var awsCreds *s3.AWSCreds - if accessKeyID != "" { - awsCreds = &s3.AWSCreds{ - AccessKeyID: accessKeyID, - AccessSecret: accessSecret, - } - } - d := a.downloader - if d == nil { - d = s3.NewDownloader(a.Region, a.Bucket, a.Key, a.Version, awsCreds) - } - w := accessio.NewWriteAtWriter(d.Download) - // don't change the spec, leave it empty. - mediaType := a.MediaType - if mediaType == "" { - mediaType = mime.MIME_OCTET - } - cacheBlobAccess := accessobj.CachedBlobAccessForWriter(c.GetContext(), mediaType, w) - return &accessMethod{ - spec: a, - comp: c, - BlobAccess: cacheBlobAccess, - }, nil -} - -func getCreds(a *AccessSpec, cctx credentials.Context) (credentials.Credentials, error) { - return identity.GetCredentials(cctx, "", a.Bucket, a.Key, a.Version) -} - -func (_ *accessMethod) IsLocal() bool { - return false -} - -func (m *accessMethod) GetKind() string { - return Type -} - -func (m *accessMethod) AccessSpec() accspeccpi.AccessSpec { - return m.spec -} - -func (m *accessMethod) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { - return identity.GetConsumerId("", m.spec.Bucket, m.spec.Key, m.spec.Version) -} - -func (m *accessMethod) GetIdentityMatcher() string { - return hostpath.IDENTITY_TYPE -} diff --git a/pkg/contexts/ocm/accessmethods/s3/method_test.go b/pkg/contexts/ocm/accessmethods/s3/method_test.go deleted file mode 100644 index 6b2ee71cd..000000000 --- a/pkg/contexts/ocm/accessmethods/s3/method_test.go +++ /dev/null @@ -1,200 +0,0 @@ -package s3_test - -import ( - "encoding/json" - "fmt" - "io" - "os" - "reflect" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/env/builder" - - "github.com/mandelsoft/filepath/pkg/filepath" - "github.com/mandelsoft/goutils/general" - "github.com/mandelsoft/vfs/pkg/osfs" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/common/accessio/downloader" - "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" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/vfsattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/s3" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/s3/identity" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" -) - -type mockDownloader struct { - expected []byte - err error -} - -func (m *mockDownloader) Download(w io.WriterAt) error { - if _, err := w.WriteAt(m.expected, 0); err != nil { - return fmt.Errorf("failed to write to mock writer: %w", err) - } - return m.err -} - -func checkMarshal(spec *s3.AccessSpec, typ string, fmt string) { - if typ != "" { - spec.SetType(typ) - } - data := MustWithOffset(1, Calling(json.Marshal(spec))) - ExpectWithOffset(1, string(data)).To(Equal(fmt)) - - n := MustWithOffset(1, Calling(ocm.DefaultContext().AccessSpecForConfig(data, nil))) - Expect(reflect.TypeOf(n)).To(Equal(reflect.TypeOf(spec))) - Expect(n.GetType()).To(Equal(general.Conditional(typ == "", s3.Type, typ))) - data2 := Must(json.Marshal(n)) - ExpectWithOffset(1, string(data2)).To(StringEqualWithContext(string(data))) -} - -func checkDecode(spec *s3.AccessSpec, typ string, fmt string) { - if typ != "" { - spec.SetType(typ) - } - data := MustWithOffset(1, Calling(json.Marshal(spec))) - - n := MustWithOffset(1, Calling(s3.Versions().Decode([]byte(fmt), nil))) - Expect(reflect.TypeOf(n)).To(Equal(reflect.TypeOf(spec))) - - data2 := Must(json.Marshal(n)) - ExpectWithOffset(1, string(data2)).To(StringEqualWithContext(string(data))) -} - -var _ = Describe("Method", func() { - Context("specification", func() { - var spec *s3.AccessSpec - - BeforeEach(func() { - spec = s3.New( - "region", - "bucket", - "key", - "version", - "tar/gz", - ) - }) - - It("serializes", func() { - checkMarshal(spec, "", "{\"type\":\"s3\",\"region\":\"region\",\"bucket\":\"bucket\",\"key\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") - checkMarshal(spec, s3.TypeV1, "{\"type\":\"s3/v1\",\"region\":\"region\",\"bucket\":\"bucket\",\"key\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") - checkMarshal(spec, s3.TypeV2, "{\"type\":\"s3/v2\",\"region\":\"region\",\"bucketName\":\"bucket\",\"objectKey\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") - checkMarshal(spec, s3.LegacyType, "{\"type\":\"S3\",\"region\":\"region\",\"bucket\":\"bucket\",\"key\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") - checkMarshal(spec, s3.LegacyTypeV1, "{\"type\":\"S3/v1\",\"region\":\"region\",\"bucket\":\"bucket\",\"key\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") - }) - - It("deserializes versioned", func() { - checkDecode(spec, s3.TypeV1, "{\"type\":\"s3/v1\",\"region\":\"region\",\"bucket\":\"bucket\",\"key\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") - checkDecode(spec, s3.TypeV2, "{\"type\":\"s3/v2\",\"region\":\"region\",\"bucketName\":\"bucket\",\"objectKey\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") - - checkDecode(spec, s3.LegacyTypeV1, "{\"type\":\"S3/v1\",\"region\":\"region\",\"bucket\":\"bucket\",\"key\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") - checkDecode(spec, s3.LegacyTypeV2, "{\"type\":\"S3/v2\",\"region\":\"region\",\"bucketName\":\"bucket\",\"objectKey\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") - }) - - It("deserializes anonymous", func() { - checkDecode(spec, s3.Type, "{\"type\":\"s3\",\"region\":\"region\",\"bucket\":\"bucket\",\"key\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") - checkDecode(spec, s3.Type, "{\"type\":\"s3\",\"region\":\"region\",\"bucketName\":\"bucket\",\"objectKey\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") - - checkDecode(spec, s3.LegacyType, "{\"type\":\"S3\",\"region\":\"region\",\"bucket\":\"bucket\",\"key\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") - checkDecode(spec, s3.LegacyType, "{\"type\":\"S3\",\"region\":\"region\",\"bucketName\":\"bucket\",\"objectKey\":\"key\",\"version\":\"version\",\"mediaType\":\"tar/gz\"}") - }) - }) - - Context("accessmethod", func() { - var ( - env *Builder - accessSpec *s3.AccessSpec - downloader downloader.Downloader - expectedContent []byte - err error - mcc ocm.Context - fs vfs.FileSystem - ctx datacontext.Context - ) - - BeforeEach(func() { - expectedContent, err = os.ReadFile(filepath.Join("testdata", "repo.tar.gz")) - Expect(err).ToNot(HaveOccurred()) - env = NewBuilder() - downloader = &mockDownloader{ - expected: expectedContent, - } - accessSpec = s3.New( - "region", - "bucket", - "key", - "version", - "tar/gz", - downloader, - ) - fs, err = osfs.NewTempFileSystem() - Expect(err).To(Succeed()) - ctx = datacontext.New(nil) - vfsattr.Set(ctx, fs) - tmpcache.Set(ctx, &tmpcache.Attribute{Path: "/tmp", Filesystem: fs}) - mcc = ocm.New(datacontext.MODE_INITIAL) - mcc.CredentialsContext().SetCredentialsForConsumer(credentials.ConsumerIdentity{credentials.ID_TYPE: identity.CONSUMER_TYPE}, credentials.DirectCredentials{ - "accessKeyID": "accessKeyID", - "accessSecret": "accessSecret", - }) - }) - - AfterEach(func() { - env.Cleanup() - vfs.Cleanup(fs) - }) - - It("provides comsumer id", func() { - m, err := accessSpec.AccessMethod(&cpi.DummyComponentVersionAccess{Context: env.OCMContext()}) - Expect(err).ToNot(HaveOccurred()) - Expect(credentials.GetProvidedConsumerId(m)).To(Equal(credentials.NewConsumerIdentity(identity.CONSUMER_TYPE, - identity.ID_PATHPREFIX, "bucket/key/version"))) - }) - - It("downloads s3 objects", func() { - m, err := accessSpec.AccessMethod(&mockComponentVersionAccess{context: mcc}) - Expect(err).ToNot(HaveOccurred()) - defer Close(m, "method") - blob, err := m.Get() - Expect(err).ToNot(HaveOccurred()) - Expect(blob).To(Equal(expectedContent)) - }) - - When("the downloader fails to download the bucket object", func() { - BeforeEach(func() { - downloader = &mockDownloader{ - err: fmt.Errorf("object not found"), - } - accessSpec = s3.New( - "region", - "bucket", - "key", - "version", - "tar/gz", - downloader, - ) - }) - It("errors", func() { - m, err := accessSpec.AccessMethod(&mockComponentVersionAccess{context: mcc}) - Expect(err).ToNot(HaveOccurred()) - _, err = m.Get() - Expect(err).To(MatchError(ContainSubstring("object not found"))) - }) - }) - }) -}) - -type mockComponentVersionAccess struct { - ocm.ComponentVersionAccess - context ocm.Context -} - -func (m *mockComponentVersionAccess) GetContext() ocm.Context { - return m.context -} diff --git a/pkg/contexts/ocm/accessmethods/wget/cli.go b/pkg/contexts/ocm/accessmethods/wget/cli.go deleted file mode 100644 index ed72c4c69..000000000 --- a/pkg/contexts/ocm/accessmethods/wget/cli.go +++ /dev/null @@ -1,71 +0,0 @@ -package wget - -import ( - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/options" - "github.com/open-component-model/ocm/pkg/mime" -) - -func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { - return flagsets.NewConfigOptionTypeSetHandler( - Type, AddConfig, - options.URLOption, - options.MediatypeOption, - options.HTTPHeaderOption, - options.HTTPVerbOption, - options.HTTPBodyOption, - options.HTTPRedirectOption, - ) -} - -func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { - flagsets.AddFieldByOptionP(opts, options.URLOption, config, "url") - flagsets.AddFieldByOptionP(opts, options.MediatypeOption, config, "mediaType") - flagsets.AddFieldByOptionP(opts, options.HTTPHeaderOption, config, "header") - flagsets.AddFieldByOptionP(opts, options.HTTPVerbOption, config, "verb") - flagsets.AddFieldByOptionP(opts, options.HTTPBodyOption, config, "body") - flagsets.AddFieldByOptionP(opts, options.HTTPRedirectOption, config, "noredirect") - return nil -} - -var usage = ` -This method implements access to resources stored on an http server. -` - -var formatV1 = ` -The url is the url pointing to the http endpoint from which a resource is -downloaded. The mimeType can be used to specify the MIME type of the -resource. - -This blob type specification supports the following fields: -- **url** *string* - -This REQUIRED property describes the url from which the resource is to be -downloaded. - -- **mediaType** *string* - -This OPTIONAL property describes the media type of the resource to be -downloaded. If omitted, ocm tries to read the mediaType from the Content-Type header -of the http response. If the mediaType cannot be set from the Content-Type header as well, -ocm tries to deduct the mediaType from the URL. If that is not possible either, the default -media type is defaulted to ` + mime.MIME_OCTET + `. - -- **header** *map[string][]string* - -This OPTIONAL property describes the http headers to be set in the http request to the server. - -- **verb** *string* - -This OPTIONAL property describes the http verb (also known as http request method) for the http -request. If omitted, the http verb is defaulted to GET. - -- **body** *[]byte* - -This OPTIONAL property describes the http body to be included in the request. - -- **noredirect** *bool* - -This OPTIONAL property describes whether http redirects should be disabled. If omitted, -it is defaulted to false (so, per default, redirects are enabled). -` diff --git a/pkg/contexts/ocm/accessmethods/wget/logging.go b/pkg/contexts/ocm/accessmethods/wget/logging.go deleted file mode 100644 index 91d2c768f..000000000 --- a/pkg/contexts/ocm/accessmethods/wget/logging.go +++ /dev/null @@ -1,18 +0,0 @@ -package wget - -import ( - "github.com/mandelsoft/logging" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - ocmlog "github.com/open-component-model/ocm/pkg/logging" -) - -var REALM = ocmlog.DefineSubRealm("access method for wget", "accessmethod/wget") - -type ContextProvider interface { - GetContext() cpi.Context -} - -func Logger(c ContextProvider, keyValuePairs ...interface{}) logging.Logger { - return c.GetContext().Logger(REALM).WithValues(keyValuePairs...) -} diff --git a/pkg/contexts/ocm/accessmethods/wget/method.go b/pkg/contexts/ocm/accessmethods/wget/method.go deleted file mode 100644 index 66723b066..000000000 --- a/pkg/contexts/ocm/accessmethods/wget/method.go +++ /dev/null @@ -1,176 +0,0 @@ -package wget - -import ( - "fmt" - "io" - "sync" - - "github.com/mandelsoft/goutils/optionutils" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/blobaccess/wget" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/wget/identity" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/runtime" -) - -// Type is the access type for a blob on an http server . -const ( - Type = "wget" - 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()))) -} - -func Is(spec accspeccpi.AccessSpec) bool { - return spec != nil && spec.GetKind() == Type -} - -// New creates a new WGET accessor for http resources. -func New(url string, opts ...Option) *AccessSpec { - eff := optionutils.EvalOptions(opts...) - - return &AccessSpec{ - ObjectVersionedType: runtime.NewVersionedTypedObject(Type), - URL: url, - MediaType: eff.MimeType, - Header: eff.Header, - Verb: eff.Verb, - Body: eff.Body, - NoRedirect: optionutils.AsValue(eff.NoRedirect), - } -} - -// AccessSpec describes the access for files on HTTP and HTTPS servers. -type AccessSpec struct { - runtime.ObjectVersionedType `json:",inline"` - - // URLs to the files on a server - URL string `json:"URL"` - // MediaType is the media type of the object represented by the blob - MediaType string `json:"mediaType"` - // Header to be passed in the http request - Header map[string][]string `json:"header"` - // Verb is the http verb to be used for the request - Verb string `json:"verb"` - // Body is the body to be included in the http request - Body io.Reader `json:"body"` - // NoRedirect allows to disable redirects - NoRedirect bool `json:"noRedirect"` -} - -var _ accspeccpi.AccessSpec = (*AccessSpec)(nil) - -func (a *AccessSpec) Describe(ctx accspeccpi.Context) string { - return fmt.Sprintf("Files from %s", a.URL) -} - -func (a *AccessSpec) IsLocal(ctx accspeccpi.Context) bool { - return false -} - -func (a *AccessSpec) GlobalAccessSpec(ctx accspeccpi.Context) accspeccpi.AccessSpec { - return a -} - -func (a *AccessSpec) AccessMethod(access accspeccpi.ComponentVersionAccess) (accspeccpi.AccessMethod, error) { - return accspeccpi.AccessMethodForImplementation(&accessMethod{comp: access, spec: a}, nil) -} - -/////////////////// - -func (a *AccessSpec) GetURL() string { - return a.URL -} - -//////////////////////////////////////////////////////////////////////////////// - -type accessMethod struct { - lock sync.Mutex - blob blobaccess.BlobAccess - comp accspeccpi.ComponentVersionAccess - spec *AccessSpec -} - -var _ accspeccpi.AccessMethodImpl = (*accessMethod)(nil) - -func (_ *accessMethod) IsLocal() bool { - return false -} - -func (m *accessMethod) GetKind() string { - return Type -} - -func (m *accessMethod) AccessSpec() accspeccpi.AccessSpec { - return m.spec -} - -func (m *accessMethod) Get() ([]byte, error) { - return blobaccess.BlobData(m.getBlob()) -} - -func (m *accessMethod) Reader() (io.ReadCloser, error) { - return blobaccess.BlobReader(m.getBlob()) -} - -func (m *accessMethod) MimeType() string { - if m.spec.MediaType != "" { - return m.spec.MediaType - } - blob, err := m.getBlob() - if err != nil { - return mime.MIME_OCTET - } - return blob.MimeType() -} - -func (m *accessMethod) getBlob() (blobaccess.BlobAccess, error) { - m.lock.Lock() - defer m.lock.Unlock() - - if m.blob != nil { - return m.blob, nil - } - - blob, err := wget.BlobAccess(m.spec.URL, - wget.WithMimeType(m.spec.MediaType), - wget.WithCredentialContext(m.comp.GetContext()), - wget.WithLoggingContext(m.comp.GetContext()), - wget.WithHeader(m.spec.Header), - wget.WithVerb(m.spec.Verb), - wget.WithBody(m.spec.Body), - wget.WithNoRedirect(m.spec.NoRedirect)) - if err != nil { - return nil, err - } - - m.blob = blob - return m.blob, nil -} - -func (m *accessMethod) Close() error { - m.lock.Lock() - defer m.lock.Unlock() - - var err error - if m.blob != nil { - err = m.blob.Close() - m.blob = nil - } - - return err -} - -func (m *accessMethod) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { - return identity.GetConsumerId(m.spec.URL) -} - -func (m *accessMethod) GetIdentityMatcher() string { - return identity.CONSUMER_TYPE -} diff --git a/pkg/contexts/ocm/accessmethods/wget/method_test.go b/pkg/contexts/ocm/accessmethods/wget/method_test.go deleted file mode 100644 index f82237843..000000000 --- a/pkg/contexts/ocm/accessmethods/wget/method_test.go +++ /dev/null @@ -1,409 +0,0 @@ -package wget_test - -import ( - "bytes" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" - "fmt" - "io" - "net/http" - "strings" - "time" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/wget" - - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/wget/identity" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" - "github.com/open-component-model/ocm/pkg/signing/signutils" -) - -var ( - caCert *x509.Certificate - caPriv *rsa.PrivateKey - caPub *rsa.PublicKey - caPEM []byte - - serverCert *x509.Certificate - serverPriv *rsa.PrivateKey - serverPub *rsa.PublicKey - serverPEM []byte - - httpsServerClientAuth http.Server - httpsServer http.Server - httpServer http.Server -) - -const ( - HTTP_PORT = ":18080" - HTTPS_PORT = ":1443" - HTTPS_PORT_WITH_CLIENT_AUTH = ":2443" - - HTTP_HOST = "http://localhost" + HTTP_PORT - HTTPS_HOST = "https://localhost" + HTTPS_PORT - HTTPS_HOST_WITH_CLIENT_AUTH = "https://localhost" + HTTPS_PORT_WITH_CLIENT_AUTH - - TO_MEMORY = "/tomemory" - TO_FILE = "/tofile" - BASIC_LOGIN = "/basic-login" - BEARER_LOGIN = "/bearer-login" - ECHO_HEADERS = "/headers" - ECHO_BODY = "/body" - ECHO_METHOD = "/method" - CONTENT_TYPE = " /content-type" - DOT_EXT = "/somefile.tar" - REDIRECT = "/redirect" - - USERNAME = "user" - PASSWORD = "password" - TOKEN = "token" - - CONTENT = "hello world" - NOREDIRECT_CONTENT = "noredirect" -) - -var _ = BeforeSuite(func() { - // setup certificate authority - _capriv, _capub := Must2(rsa.Handler{}.CreateKeyPair()) - caPriv = _capriv.(*rsa.PrivateKey) - caPub = _capub.(*rsa.PublicKey) - - caSpec := &signutils.Specification{ - Subject: *signutils.CommonName("caCert-authority"), - Validity: 10 * time.Minute, - CAPrivateKey: caPriv, - IsCA: true, - Usages: []interface{}{x509.KeyUsageDigitalSignature}, - } - - caCert, caPEM = Must2(signutils.CreateCertificate(caSpec)) - - // use certificate authority to create httpsServer certificate - _serverPriv, _serverPub := Must2(rsa.Handler{}.CreateKeyPair()) - serverPriv = _serverPriv.(*rsa.PrivateKey) - serverPub = _serverPub.(*rsa.PublicKey) - - serverSpec := &signutils.Specification{ - IsCA: false, - Subject: pkix.Name{CommonName: "localhost"}, - Validity: 10 * time.Minute, - RootCAs: caCert, - CAChain: caCert, - CAPrivateKey: caPriv, - PublicKey: serverPub, - Usages: []interface{}{x509.ExtKeyUsageServerAuth}, - Hosts: []string{"localhost", "127.0.0.1"}, - } - - serverCert, serverPEM = Must2(signutils.CreateCertificate(serverSpec)) - - // setup tls configuration for the httpsServer for https with the corresponding certs and keys - serverPrivPEM := Must(rsa.KeyData(serverPriv)) - serverTlsCert := Must(tls.X509KeyPair(serverPEM, serverPrivPEM)) - - // ca's used by the server to validate client certificates - clientCaCertPool := x509.NewCertPool() - clientCaCertPool.AddCert(caCert) - - tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{serverTlsCert}, - } - - tlsConfigClientAuth := &tls.Config{ - Certificates: []tls.Certificate{serverTlsCert}, - ClientAuth: tls.RequireAndVerifyClientCert, - ClientCAs: clientCaCertPool, - } - - // configure test routes - mux := http.NewServeMux() - mux.HandleFunc(TO_MEMORY, func(writer http.ResponseWriter, request *http.Request) { - n, err := writer.Write([]byte(CONTENT)) - _, _ = n, err - }) - mux.HandleFunc(BASIC_LOGIN, func(writer http.ResponseWriter, request *http.Request) { - username, password, ok := request.BasicAuth() - if !ok { - n, err := writer.Write([]byte(`failure`)) - _, _ = n, err - } - if username != "" && password != "" { - res := fmt.Sprintf("%s:%s", username, password) - n, err := writer.Write([]byte(res)) - _, _ = n, err - } else { - n, err := writer.Write([]byte(`failure`)) - _, _ = n, err - } - }) - mux.HandleFunc(BEARER_LOGIN, func(writer http.ResponseWriter, request *http.Request) { - auth := request.Header.Get("Authorization") - if auth == "" { - n, err := writer.Write([]byte(`failure`)) - _, _ = n, err - } else { - bearer, ok := strings.CutPrefix(auth, "Bearer ") - if !ok { - n, err := writer.Write([]byte(`failure`)) - _, _ = n, err - } else { - n, err := writer.Write([]byte(bearer)) - _, _ = n, err - } - } - }) - mux.HandleFunc(ECHO_HEADERS, func(writer http.ResponseWriter, request *http.Request) { - err := request.Header.Write(writer) - _ = err - }) - mux.HandleFunc(ECHO_BODY, func(writer http.ResponseWriter, request *http.Request) { - b, err := io.ReadAll(request.Body) - _, err = writer.Write(b) - _ = err - }) - mux.HandleFunc(ECHO_METHOD, func(writer http.ResponseWriter, request *http.Request) { - _, err := writer.Write([]byte(request.Method)) - _ = err - }) - mux.HandleFunc(CONTENT_TYPE, func(writer http.ResponseWriter, request *http.Request) { - writer.Header().Set("Content-Type", mime.MIME_TEXT) - }) - mux.HandleFunc(DOT_EXT, func(writer http.ResponseWriter, request *http.Request) {}) - mux.HandleFunc(REDIRECT, func(writer http.ResponseWriter, request *http.Request) { - writer.Header().Set("Location", TO_MEMORY) - writer.WriteHeader(307) - writer.Write([]byte(NOREDIRECT_CONTENT)) - }) - - // setup an https and an http httpsServer - httpsServerClientAuth := &http.Server{ - Addr: HTTPS_PORT_WITH_CLIENT_AUTH, - TLSConfig: tlsConfigClientAuth, - Handler: mux, - } - - httpsServer := &http.Server{ - Addr: HTTPS_PORT, - TLSConfig: tlsConfig, - Handler: mux, - } - - httpServer := &http.Server{ - Addr: HTTP_PORT, - Handler: mux, - } - - go func() { - MustBeSuccessful(httpsServerClientAuth.ListenAndServeTLS("", "")) - }() - - go func() { - MustBeSuccessful(httpsServer.ListenAndServeTLS("", "")) - }() - - go func() { - MustBeSuccessful(httpServer.ListenAndServe()) - }() -}) - -var _ = AfterSuite(func() { - MustBeSuccessful(httpsServerClientAuth.Close()) - MustBeSuccessful(httpsServer.Close()) - MustBeSuccessful(httpServer.Close()) -}) - -var _ = Describe("wget access method", func() { - It("access content on http server", func() { - url := HTTP_HOST + TO_MEMORY - spec := New(url) - - ctx := ocm.DefaultContext() - m := Must(spec.AccessMethod(&cpi.DummyComponentVersionAccess{ctx})) - defer Close(m, "method") - - b := Must(m.Get()) - Expect(string(b)).To(Equal(CONTENT)) - }) - - It("access content on https server", func() { - url := HTTPS_HOST + TO_MEMORY - spec := New(url) - - ctx := ocm.DefaultContext() - ctx.CredentialsContext().SetCredentialsForConsumer(identity.GetConsumerId(url), credentials.DirectCredentials{ - identity.ATTR_CERTIFICATE_AUTHORITY: string(caPEM), - }) - m := Must(spec.AccessMethod(&cpi.DummyComponentVersionAccess{ctx})) - defer Close(m, "method") - - b := Must(m.Get()) - Expect(string(b)).To(Equal(CONTENT)) - }) - - It("access content on https server with client authentication", func() { - // create a client certificate - _clientPriv, _clientPub := Must2(rsa.Handler{}.CreateKeyPair()) - clientPriv := _clientPriv.(*rsa.PrivateKey) - clientPrivData := Must(rsa.KeyData(clientPriv)) - clientPub := _clientPub.(*rsa.PublicKey) - - clientSpec := &signutils.Specification{ - IsCA: false, - Subject: pkix.Name{CommonName: "localhost"}, - Validity: 10 * time.Minute, - RootCAs: caCert, - CAChain: caCert, - CAPrivateKey: caPriv, - PublicKey: clientPub, - Usages: []interface{}{x509.ExtKeyUsageClientAuth}, - Hosts: []string{"localhost", "127.0.0.1"}, - } - - _, clientPEM := Must2(signutils.CreateCertificate(clientSpec)) - - // Request - url := HTTPS_HOST_WITH_CLIENT_AUTH + TO_MEMORY - spec := New(url) - - ctx := ocm.DefaultContext() - ctx.CredentialsContext().SetCredentialsForConsumer(identity.GetConsumerId(url), credentials.DirectCredentials{ - identity.ATTR_CERTIFICATE_AUTHORITY: string(caPEM), - identity.ATTR_CERTIFICATE: string(clientPEM), - identity.ATTR_PRIVATE_KEY: string(clientPrivData), - }) - m := Must(spec.AccessMethod(&cpi.DummyComponentVersionAccess{ctx})) - defer Close(m, "method") - - b := Must(m.Get()) - Expect(string(b)).To(Equal(CONTENT)) - }) - - It("check that username and password are passed correctly", func() { - url := HTTP_HOST + BASIC_LOGIN - spec := New(url) - - ctx := ocm.DefaultContext() - ctx.CredentialsContext().SetCredentialsForConsumer(identity.GetConsumerId(url), credentials.DirectCredentials{ - identity.ATTR_USERNAME: USERNAME, - identity.ATTR_PASSWORD: PASSWORD, - }) - m := Must(spec.AccessMethod(&cpi.DummyComponentVersionAccess{ctx})) - defer Close(m, "method") - - b := Must(m.Get()) - Expect(string(b)).To(Equal(USERNAME + ":" + PASSWORD)) - }) - - It("check that bearer token is passed correctly", func() { - url := HTTP_HOST + BEARER_LOGIN - spec := New(url) - - ctx := ocm.DefaultContext() - ctx.CredentialsContext().SetCredentialsForConsumer(identity.GetConsumerId(url), credentials.DirectCredentials{ - identity.ATTR_IDENTITY_TOKEN: TOKEN, - }) - m := Must(spec.AccessMethod(&cpi.DummyComponentVersionAccess{ctx})) - defer Close(m, "method") - - b := Must(m.Get()) - Expect(string(b)).To(Equal(TOKEN)) - }) - - It("check that basic auth is merged correctly with other provided headers", func() { - url := HTTP_HOST + ECHO_HEADERS - headers := map[string][]string{"Content-Type": {"text/plain"}} - spec := New(url, WithHeader(headers)) - - ctx := ocm.DefaultContext() - ctx.CredentialsContext().SetCredentialsForConsumer(identity.GetConsumerId(url), credentials.DirectCredentials{ - identity.ATTR_USERNAME: USERNAME, - identity.ATTR_PASSWORD: PASSWORD, - }) - m := Must(spec.AccessMethod(&cpi.DummyComponentVersionAccess{ctx})) - defer Close(m, "method") - - b := Must(m.Get()) - - Expect(strings.Contains(string(b), "Content-Type: text/plain")).To(BeTrue()) - Expect(strings.Contains(string(b), "Authorization: Basic")).To(BeTrue()) - }) - - It("check detect mime type based on content-type response header", func() { - url := HTTP_HOST + ECHO_HEADERS - spec := New(url) - - ctx := ocm.DefaultContext() - m := Must(spec.AccessMethod(&cpi.DummyComponentVersionAccess{ctx})) - defer Close(m, "method") - - Expect(m.MimeType()).To(Equal(mime.MIME_TEXT)) - }) - - It("check deduction of mime type based on url", func() { - url := HTTP_HOST + DOT_EXT - spec := New(url) - - ctx := ocm.DefaultContext() - m := Must(spec.AccessMethod(&cpi.DummyComponentVersionAccess{ctx})) - defer Close(m, "method") - - Expect(m.MimeType()).To(Equal("application/x-tar")) - }) - - It("check passing an http body", func() { - url := HTTP_HOST + ECHO_BODY - - content := `hello world` - spec := New(url, WithBody(bytes.NewReader([]byte(content)))) - - ctx := ocm.DefaultContext() - m := Must(spec.AccessMethod(&cpi.DummyComponentVersionAccess{ctx})) - defer Close(m, "method") - - b := Must(m.Get()) - - Expect(string(b)).To(Equal(content)) - }) - - It("check passing an http verb", func() { - url := HTTP_HOST + ECHO_METHOD - - method := http.MethodPost - spec := New(url, WithVerb(method)) - - ctx := ocm.DefaultContext() - m := Must(spec.AccessMethod(&cpi.DummyComponentVersionAccess{ctx})) - defer Close(m, "method") - - b := Must(m.Get()) - - Expect(string(b)).To(Equal(method)) - }) - - It("check noredirect behavior", func() { - url := HTTP_HOST + REDIRECT - - redirectSpec := New(url, WithNoRedirect(false)) - noredirectSpec := New(url, WithNoRedirect(true)) - - ctx := ocm.DefaultContext() - redirectMethod := Must(redirectSpec.AccessMethod(&cpi.DummyComponentVersionAccess{ctx})) - defer Close(redirectMethod, "redirectmethod") - - noredirectMethod := Must(noredirectSpec.AccessMethod(&cpi.DummyComponentVersionAccess{ctx})) - defer Close(noredirectMethod, "noredirectmethod") - - redirectContent := Must(redirectMethod.Get()) - Expect(string(redirectContent)).To(Equal(CONTENT)) - - noredirectContent := Must(noredirectMethod.Get()) - Expect(string(noredirectContent)).To(Equal(NOREDIRECT_CONTENT)) - }) -}) diff --git a/pkg/contexts/ocm/accessmethods/wget/options.go b/pkg/contexts/ocm/accessmethods/wget/options.go deleted file mode 100644 index fcf667661..000000000 --- a/pkg/contexts/ocm/accessmethods/wget/options.go +++ /dev/null @@ -1,33 +0,0 @@ -package wget - -import ( - "io" - "net/http" - - "github.com/open-component-model/ocm/pkg/blobaccess/wget" -) - -type ( - Options = wget.Options - Option = wget.Option -) - -func WithMimeType(mime string) Option { - return wget.WithMimeType(mime) -} - -func WithHeader(h http.Header) Option { - return wget.WithHeader(h) -} - -func WithVerb(v string) Option { - return wget.WithVerb(v) -} - -func WithBody(v io.Reader) Option { - return wget.WithBody(v) -} - -func WithNoRedirect(r ...bool) Option { - return wget.WithNoRedirect(r...) -} diff --git a/pkg/contexts/ocm/actionhandler/init.go b/pkg/contexts/ocm/actionhandler/init.go deleted file mode 100644 index f22bc9466..000000000 --- a/pkg/contexts/ocm/actionhandler/init.go +++ /dev/null @@ -1,5 +0,0 @@ -package actionhandler - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/actionhandler/plugin" -) diff --git a/pkg/contexts/ocm/actionhandler/plugin/action_test.go b/pkg/contexts/ocm/actionhandler/plugin/action_test.go deleted file mode 100644 index f684674d3..000000000 --- a/pkg/contexts/ocm/actionhandler/plugin/action_test.go +++ /dev/null @@ -1,87 +0,0 @@ -//go:build unix - -package plugin_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/testutils" - . "github.com/open-component-model/ocm/pkg/env/builder" - - "github.com/open-component-model/ocm/pkg/contexts/datacontext/action/handlers" - oci_repository_prepare "github.com/open-component-model/ocm/pkg/contexts/oci/actions/oci-repository-prepare" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/actionhandler/plugin" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/plugins" - "github.com/open-component-model/ocm/pkg/contexts/ocm/registration" -) - -const PLUGIN = "test" - -var _ = Describe("plugin action handler", func() { - var ctx ocm.Context - var registry plugins.Set - var env *Builder - var plugins TempPluginDir - - BeforeEach(func() { - env = NewBuilder(nil) - ctx = env.OCMContext() - plugins, registry = Must2(ConfigureTestPlugins2(env, "testdata")) - p := registry.Get("action") - Expect(p).NotTo(BeNil()) - }) - - AfterEach(func() { - plugins.Cleanup() - env.Cleanup() - }) - - It("executes with no plugin registration", func() { - result := Must(oci_repository_prepare.Execute(ctx.GetActions(), "ghcr.io", "mandelsoft", nil)) - Expect(result).To(BeNil()) - }) - - It("executes with no handler", func() { - registration.RegisterExtensions(ctx) - result := Must(oci_repository_prepare.Execute(ctx.GetActions(), "mandelsoft.org", "mandelsoft", nil)) - Expect(result).To(BeNil()) - }) - - It("used default registration", func() { - registration.RegisterExtensions(ctx) - opts := handlers.NewOptions(handlers.ForAction(oci_repository_prepare.Type), handlers.ForAction("test"), handlers.ForSelectors("mandelsoft.org")) - MustFailWithMessage(plugin.RegisterActionHandler(ctx.AttributesContext(), "action", ctx, opts), "action \"test\" is unknown for plugin action") - }) - - It("uses default registration", func() { - registration.RegisterExtensions(ctx) - result := Must(oci_repository_prepare.Execute(ctx.GetActions(), "ghcr.io", "mandelsoft", nil)) - Expect(result).NotTo(BeNil()) - Expect(result.Message).To(Equal("all good")) - }) - - It("uses default pattern registration", func() { - registration.RegisterExtensions(ctx) - result := Must(oci_repository_prepare.Execute(ctx.GetActions(), "xyz.dkr.ecr.us-west-2.amazonaws.com", "mandelsoft", nil)) - Expect(result).NotTo(BeNil()) - Expect(result.Message).To(Equal("all good")) - }) - - It("executes action for dynamic registration", func() { - registration.RegisterExtensions(ctx) - opts := handlers.NewOptions(handlers.ForAction(oci_repository_prepare.Type), handlers.ForAction(oci_repository_prepare.Type), handlers.ForSelectors("mandelsoft.org")) - MustBeSuccessful(plugin.RegisterActionHandler(ctx.AttributesContext(), "action", ctx, opts)) - - result := Must(oci_repository_prepare.Execute(ctx.GetActions(), "mandelsoft.org", "mandelsoft", nil)) - Expect(result.Message).To(Equal("all good")) - }) - - It("executed action after abstract registration", func() { - registration.RegisterExtensions(ctx) - opts := handlers.NewOptions(handlers.ForAction(oci_repository_prepare.Type), handlers.ForAction(oci_repository_prepare.Type), handlers.ForSelectors("mandelsoft.org")) - ok := Must(ctx.GetActions().RegisterByName("plugin/action", ctx.OCIContext(), ctx, opts)) - Expect(ok).To(BeTrue()) - }) -}) diff --git a/pkg/contexts/ocm/actionhandler/plugin/registration.go b/pkg/contexts/ocm/actionhandler/plugin/registration.go deleted file mode 100644 index a9facaec6..000000000 --- a/pkg/contexts/ocm/actionhandler/plugin/registration.go +++ /dev/null @@ -1,92 +0,0 @@ -package plugin - -import ( - "fmt" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/datacontext/action/handlers" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugincacheattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin" - "github.com/open-component-model/ocm/pkg/registrations" -) - -func init() { - handlers.DefaultRegistry().RegisterRegistrationHandler("plugin", &RegistrationHandler{}) -} - -type RegistrationHandler struct{} - -var _ handlers.HandlerRegistrationHandler = (*RegistrationHandler)(nil) - -func (r *RegistrationHandler) RegisterByName(handler string, target handlers.Target, config handlers.HandlerConfig, olist ...handlers.Option) (bool, error) { - path := cpi.NewNamePath(handler) - - if config == nil { - return true, fmt.Errorf("config required") - } - - ctx, ok := config.(cpi.Context) - if !ok { - return true, fmt.Errorf("expected ocm.Context as config but found: %T", config) - } - if len(path) != 1 { - return true, fmt.Errorf("plugin handler must be of the form ") - } - - opts := handlers.NewOptions(olist...) - name := path[0] - err := RegisterActionHandler(target, name, ctx, opts) - return true, err -} - -func RegisterActionHandler(target handlers.Target, pname string, ctx ocm.Context, opts *handlers.Options) error { - set := plugincacheattr.Get(ctx) - if set == nil { - return errors.ErrUnknown(plugin.KIND_PLUGIN, pname) - } - - p := set.Get(pname) - if p == nil { - return errors.ErrUnknown(plugin.KIND_PLUGIN, pname) - } - - h, err := New(p, opts.Action) - if err != nil { - return err - } - return target.GetActions().Register(h, opts) -} - -func (r *RegistrationHandler) GetHandlers(target handlers.Target) registrations.HandlerInfos { - infos := registrations.HandlerInfos{} - - ctx := ocm.DefaultContext() - if c, ok := target.(ocm.ContextProvider); ok { - ctx = c.OCMContext() - } - - set := plugincacheattr.Get(ctx) - if set == nil { - return infos - } - - for _, name := range set.PluginNames() { - for _, a := range set.Get(name).GetDescriptor().Actions { - d := target.GetActions().GetActionTypes().GetAction(a.GetName()) - short := "" - if d != nil { - short = d.Description() - } - i := registrations.HandlerInfo{ - Name: name + "/" + a.GetName(), - ShortDesc: short, - Description: a.GetDescription(), - } - infos = append(infos, i) - } - } - return infos -} diff --git a/pkg/contexts/ocm/attrs/compatattr/attr.go b/pkg/contexts/ocm/attrs/compatattr/attr.go deleted file mode 100644 index 72e21d87e..000000000 --- a/pkg/contexts/ocm/attrs/compatattr/attr.go +++ /dev/null @@ -1,57 +0,0 @@ -package compatattr - -import ( - "fmt" - - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - ATTR_KEY = "github.com/mandelsoft/ocm/compat" - ATTR_SHORT = "compat" -) - -func init() { - datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) -} - -type AttributeType struct{} - -func (a AttributeType) Name() string { - return ATTR_KEY -} - -func (a AttributeType) Description() string { - return ` -*bool* -Compatibility mode: Avoid generic local access methods and prefer type specific ones. -` -} - -func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { - if _, ok := v.(bool); !ok { - return nil, fmt.Errorf("boolean required") - } - return marshaller.Marshal(v) -} - -func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { - var value bool - err := unmarshaller.Unmarshal(data, &value) - return value, err -} - -//////////////////////////////////////////////////////////////////////////////// - -func Get(ctx datacontext.Context) bool { - a := ctx.GetAttributes().GetAttribute(ATTR_KEY) - if a == nil { - return false - } - return a.(bool) -} - -func Set(ctx datacontext.Context, flag bool) error { - return ctx.GetAttributes().SetAttribute(ATTR_KEY, flag) -} diff --git a/pkg/contexts/ocm/attrs/compatattr/attr_test.go b/pkg/contexts/ocm/attrs/compatattr/attr_test.go deleted file mode 100644 index cf4b1a4c3..000000000 --- a/pkg/contexts/ocm/attrs/compatattr/attr_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package compatattr_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/config" - "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" - me "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/compatattr" - "github.com/open-component-model/ocm/pkg/runtime" -) - -var _ = Describe("attribute", func() { - var ctx ocm.Context - var cfgctx config.Context - - BeforeEach(func() { - cfgctx = config.WithSharedAttributes(datacontext.New(nil)).New() - credctx := credentials.WithConfigs(cfgctx).New() - ocictx := oci.WithCredentials(credctx).New() - ctx = ocm.WithOCIRepositories(ocictx).New() - }) - It("local setting", func() { - Expect(me.Get(ctx)).To(BeFalse()) - Expect(me.Set(ctx, true)).To(Succeed()) - Expect(me.Get(ctx)).To(BeTrue()) - }) - - It("global setting", func() { - Expect(me.Get(cfgctx)).To(BeFalse()) - Expect(me.Set(ctx, true)).To(Succeed()) - Expect(me.Get(ctx)).To(BeTrue()) - }) - - It("parses string", func() { - Expect(me.AttributeType{}.Decode([]byte("true"), runtime.DefaultJSONEncoding)).To(BeTrue()) - }) -}) diff --git a/pkg/contexts/ocm/attrs/compositionmodeattr/attr.go b/pkg/contexts/ocm/attrs/compositionmodeattr/attr.go deleted file mode 100644 index 903ff856d..000000000 --- a/pkg/contexts/ocm/attrs/compositionmodeattr/attr.go +++ /dev/null @@ -1,70 +0,0 @@ -package compositionmodeattr - -import ( - "fmt" - - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/runtime" -) - -// UseCompositionMode enables the support of the new Composition mode for -// Component versions. It disabled the direct write-through and update-on-close -// to the underlying repository. Instead, an explicit call to AddVersion call -// s required to persist a composed change on a new as well as queried -// component version object. -const UseCompositionMode = false - -const ( - ATTR_KEY = "ocm.software/compositionmode" - ATTR_SHORT = "compositionmode" -) - -func init() { - datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) -} - -type AttributeType struct{} - -func (a AttributeType) Name() string { - return ATTR_KEY -} - -func (a AttributeType) Description() string { - return ` -*bool* (default: ` + fmt.Sprintf("%t", UseCompositionMode) + ` -Composition mode decouples a component version provided by a repository -implemention from the backened persistence. Added local blobs will -and other changes witll not be forwarded to the backend repository until -an AddVersion is called on the component. -If composition mode is disabled blobs will directly be forwarded to -the backend and descriptor updated will be persisted on AddVersion -or closing a provided existing component version. -` -} - -func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { - if _, ok := v.(bool); !ok { - return nil, fmt.Errorf("boolean required") - } - return marshaller.Marshal(v) -} - -func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { - var value bool - err := unmarshaller.Unmarshal(data, &value) - return value, err -} - -//////////////////////////////////////////////////////////////////////////////// - -func Get(ctx datacontext.Context) bool { - a := ctx.GetAttributes().GetAttribute(ATTR_KEY) - if a == nil { - return UseCompositionMode - } - return a.(bool) -} - -func Set(ctx datacontext.Context, flag bool) error { - return ctx.GetAttributes().SetAttribute(ATTR_KEY, flag) -} diff --git a/pkg/contexts/ocm/attrs/compositionmodeattr/attr_test.go b/pkg/contexts/ocm/attrs/compositionmodeattr/attr_test.go deleted file mode 100644 index a96e036c7..000000000 --- a/pkg/contexts/ocm/attrs/compositionmodeattr/attr_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package compositionmodeattr_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/config" - "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" - me "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/compositionmodeattr" - "github.com/open-component-model/ocm/pkg/runtime" -) - -var _ = Describe("attribute", func() { - var ctx ocm.Context - var cfgctx config.Context - - BeforeEach(func() { - cfgctx = config.WithSharedAttributes(datacontext.New(nil)).New() - credctx := credentials.WithConfigs(cfgctx).New() - ocictx := oci.WithCredentials(credctx).New() - ctx = ocm.WithOCIRepositories(ocictx).New() - }) - It("local setting", func() { - Expect(me.Get(ctx)).To(Equal(me.UseCompositionMode)) - Expect(me.Set(ctx, true)).To(Succeed()) - Expect(me.Get(ctx)).To(BeTrue()) - Expect(me.Set(ctx, false)).To(Succeed()) - Expect(me.Get(ctx)).To(BeFalse()) - }) - - It("global setting", func() { - Expect(me.Get(cfgctx)).To(Equal(me.UseCompositionMode)) - Expect(me.Set(cfgctx, true)).To(Succeed()) - Expect(me.Get(cfgctx)).To(BeTrue()) - Expect(me.Set(cfgctx, false)).To(Succeed()) - Expect(me.Get(cfgctx)).To(BeFalse()) - }) - - It("parses string", func() { - Expect(me.AttributeType{}.Decode([]byte("true"), runtime.DefaultJSONEncoding)).To(BeTrue()) - }) -}) diff --git a/pkg/contexts/ocm/attrs/hashattr/attr.go b/pkg/contexts/ocm/attrs/hashattr/attr.go deleted file mode 100644 index 7cb90ae4f..000000000 --- a/pkg/contexts/ocm/attrs/hashattr/attr.go +++ /dev/null @@ -1,109 +0,0 @@ -package hashattr - -import ( - "fmt" - - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" - ocm "github.com/open-component-model/ocm/pkg/contexts/ocm/context" - "github.com/open-component-model/ocm/pkg/listformat" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/hasher/sha256" - "github.com/open-component-model/ocm/pkg/utils" -) - -const ( - ATTR_KEY = "github.com/mandelsoft/ocm/hasher" - ATTR_SHORT = "hasher" -) - -type ( - Context = ocm.Context - ContextProvider = ocm.ContextProvider - Hasher = ocm.Hasher -) - -func init() { - datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}) -} - -type AttributeType struct{} - -var ( - _ datacontext.AttributeType = (*AttributeType)(nil) - _ datacontext.Converter = (*AttributeType)(nil) -) - -func (a AttributeType) Name() string { - return ATTR_KEY -} - -func (a AttributeType) Description() string { - return ` -*JSON* -Preferred hash algorithm to calculate resource digests. The following -digesters are supported: -` + listformat.FormatList(sha256.Algorithm, signing.DefaultRegistry().HasherNames()...) -} - -func (a AttributeType) Convert(v interface{}) (interface{}, error) { - switch s := v.(type) { - case string: - return &Attribute{ - s, - }, nil - case *Attribute: - return v, nil - } - return nil, fmt.Errorf("digest algorithm name or hash attribute required") -} - -func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { - switch s := v.(type) { - case string: - return []byte(s), nil - case *Attribute: - return []byte(s.DefaultHasher), nil - } - return nil, fmt.Errorf("digest algorithm name required") -} - -func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { - var value string - err := unmarshaller.Unmarshal(data, &value) - if err != nil { - return nil, err - } - return &Attribute{ - value, - }, nil -} - -//////////////////////////////////////////////////////////////////////////////// - -type Attribute struct { - DefaultHasher string -} - -func (a *Attribute) GetHasher(ctx ContextProvider, names ...string) Hasher { - name := utils.Optional(names...) - if name != "" { - return signingattr.Get(ctx).GetHasher(name) - } - return signingattr.Get(ctx).GetHasher(a.DefaultHasher) -} - -func Get(ctx ContextProvider) *Attribute { - a := ctx.OCMContext().GetAttributes().GetAttribute(ATTR_KEY) - if a == nil { - return &Attribute{ - sha256.Algorithm, - } - } - return a.(*Attribute) -} - -func Set(ctx ContextProvider, hasher string) error { - return ctx.OCMContext().GetAttributes().SetAttribute(ATTR_KEY, hasher) -} diff --git a/pkg/contexts/ocm/attrs/hashattr/attr_test.go b/pkg/contexts/ocm/attrs/hashattr/attr_test.go deleted file mode 100644 index cc3af8c4c..000000000 --- a/pkg/contexts/ocm/attrs/hashattr/attr_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package hashattr_test - -import ( - "encoding/json" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/config" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/hashattr" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/signing/hasher/sha512" -) - -const NAME = "test" - -var _ = Describe("attribute", func() { - var cfgctx config.Context - var ocmctx ocm.Context - - BeforeEach(func() { - ocmctx = ocm.New(datacontext.MODE_EXTENDED) - cfgctx = ocmctx.ConfigContext() - }) - - It("marshal/unmarshal", func() { - cfg := hashattr.New(sha512.Algorithm) - data := Must(json.Marshal(cfg)) - - r := &hashattr.Config{} - Expect(json.Unmarshal(data, r)).To(Succeed()) - Expect(r).To(Equal(cfg)) - }) - - It("decode", func() { - attr := &hashattr.Attribute{ - DefaultHasher: sha512.Algorithm, - } - - r := Must(hashattr.AttributeType{}.Decode([]byte(sha512.Algorithm), runtime.DefaultYAMLEncoding)) - Expect(r).To(Equal(attr)) - }) - - It("applies string", func() { - MustBeSuccessful(cfgctx.GetAttributes().SetAttribute(hashattr.ATTR_KEY, sha512.Algorithm)) - attr := hashattr.Get(ocmctx) - Expect(attr.GetHasher(ocmctx)).To(Equal(sha512.Handler{})) - }) - - It("applies config", func() { - cfg := hashattr.New(sha512.Algorithm) - - MustBeSuccessful(cfgctx.ApplyConfig(cfg, "from test")) - Expect(hashattr.Get(ocmctx).GetHasher(ocmctx)).To(Equal(sha512.Handler{})) - }) -}) diff --git a/pkg/contexts/ocm/attrs/hashattr/config.go b/pkg/contexts/ocm/attrs/hashattr/config.go deleted file mode 100644 index 53b517095..000000000 --- a/pkg/contexts/ocm/attrs/hashattr/config.go +++ /dev/null @@ -1,54 +0,0 @@ -package hashattr - -import ( - "github.com/mandelsoft/goutils/errors" - - cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/listformat" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/hasher/sha256" -) - -const ( - ConfigType = "hasher" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX - ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" -) - -func init() { - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1, usage)) -} - -// Config describes a memory based repository interface. -type Config struct { - runtime.ObjectVersionedType `json:",inline"` - HashAlgorithm string `json:"hashAlgorithm"` -} - -// New creates a new memory ConfigSpec. -func New(algo string) *Config { - return &Config{ - ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), - HashAlgorithm: algo, - } -} - -func (a *Config) GetType() string { - return ConfigType -} - -func (a *Config) ApplyTo(ctx cfgcpi.Context, target interface{}) error { - t, ok := target.(Context) - if !ok { - return cfgcpi.ErrNoContext(ConfigType) - } - return errors.Wrapf(t.GetAttributes().SetAttribute(ATTR_KEY, a.HashAlgorithm), "applying config failed") -} - -var usage = ` -The config type ` + ConfigType + ` can be used to define -the default hash algorithm used to calculate digests for resources. -It supports the field hashAlgorithm, with one of the following -values: -` + listformat.FormatList(sha256.Algorithm, signing.DefaultRegistry().HasherNames()...) diff --git a/pkg/contexts/ocm/attrs/init.go b/pkg/contexts/ocm/attrs/init.go deleted file mode 100644 index 591dc43bb..000000000 --- a/pkg/contexts/ocm/attrs/init.go +++ /dev/null @@ -1,12 +0,0 @@ -package attrs - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/compatattr" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/hashattr" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/keepblobattr" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/mapocirepoattr" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/ociuploadattr" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugincacheattr" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugindirattr" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" -) diff --git a/pkg/contexts/ocm/attrs/keepblobattr/attr.go b/pkg/contexts/ocm/attrs/keepblobattr/attr.go deleted file mode 100644 index 2f2181bb5..000000000 --- a/pkg/contexts/ocm/attrs/keepblobattr/attr.go +++ /dev/null @@ -1,61 +0,0 @@ -package keepblobattr - -import ( - "fmt" - - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - ATTR_KEY = "github.com/mandelsoft/ocm/keeplocalblob" - ATTR_SHORT = "keeplocalblob" -) - -func init() { - datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) -} - -type AttributeType struct{} - -func (a AttributeType) Name() string { - return ATTR_KEY -} - -func (a AttributeType) Description() string { - return ` -*bool* -Keep local blobs when importing OCI artifacts to OCI registries from localBlob -access methods. By default, they will be expanded to OCI artifacts with the -access method ociRegistry. If this option is set to true, they will be stored -as local blobs, also. The access method will still be localBlob but with a nested -ociRegistry access method for describing the global access. -` -} - -func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { - if _, ok := v.(bool); !ok { - return nil, fmt.Errorf("boolean required") - } - return marshaller.Marshal(v) -} - -func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { - var value bool - err := unmarshaller.Unmarshal(data, &value) - return value, err -} - -//////////////////////////////////////////////////////////////////////////////// - -func Get(ctx datacontext.Context) bool { - a := ctx.GetAttributes().GetAttribute(ATTR_KEY) - if a == nil { - return false - } - return a.(bool) -} - -func Set(ctx datacontext.Context, flag bool) error { - return ctx.GetAttributes().SetAttribute(ATTR_KEY, flag) -} diff --git a/pkg/contexts/ocm/attrs/keepblobattr/attr_test.go b/pkg/contexts/ocm/attrs/keepblobattr/attr_test.go deleted file mode 100644 index c46b0943e..000000000 --- a/pkg/contexts/ocm/attrs/keepblobattr/attr_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package keepblobattr_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/config" - "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" - me "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/keepblobattr" - "github.com/open-component-model/ocm/pkg/runtime" -) - -var _ = Describe("attribute", func() { - var ctx ocm.Context - var cfgctx config.Context - - BeforeEach(func() { - cfgctx = config.WithSharedAttributes(datacontext.New(nil)).New() - credctx := credentials.WithConfigs(cfgctx).New() - ocictx := oci.WithCredentials(credctx).New() - ctx = ocm.WithOCIRepositories(ocictx).New() - }) - It("local setting", func() { - Expect(me.Get(ctx)).To(BeFalse()) - Expect(me.Set(ctx, true)).To(Succeed()) - Expect(me.Get(ctx)).To(BeTrue()) - }) - - It("global setting", func() { - Expect(me.Get(cfgctx)).To(BeFalse()) - Expect(me.Set(ctx, true)).To(Succeed()) - Expect(me.Get(ctx)).To(BeTrue()) - }) - - It("parses string", func() { - Expect(me.AttributeType{}.Decode([]byte("true"), runtime.DefaultJSONEncoding)).To(BeTrue()) - }) -}) diff --git a/pkg/contexts/ocm/attrs/mapocirepoattr/attr.go b/pkg/contexts/ocm/attrs/mapocirepoattr/attr.go deleted file mode 100644 index d4babcc18..000000000 --- a/pkg/contexts/ocm/attrs/mapocirepoattr/attr.go +++ /dev/null @@ -1,249 +0,0 @@ -package mapocirepoattr - -import ( - "crypto/sha256" - "encoding/hex" - "fmt" - "strings" - - "github.com/mandelsoft/goutils/errors" - "golang.org/x/exp/maps" - - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/oci/grammar" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -const ( - ATTR_KEY = "github.com/mandelsoft/ocm/mapocirepo" - ATTR_SHORT = "mapocirepo" -) - -func init() { - datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) -} - -type AttributeType struct{} - -func (a AttributeType) Name() string { - return ATTR_KEY -} - -func (a AttributeType) Description() string { - return ` -*bool|YAML* -When uploading an OCI artifact blob to an OCI based OCM repository and the -artifact is uploaded as OCI artifact, the repository path part is shortened, -either by hashing all but the last repository name part or by executing -some prefix based name mappings. - -If a boolean is given the short hash or none mode is enabled. -The YAML flavor uses the following fields: -- *mode* *string*: hash, shortHash, prefixMapping - or none. If unset, no mapping is done. -- *prefixMappings*: *map[string]string* repository path prefix mapping. -- *prefix*: *string* repository prefix to use (replaces potential sub path of OCM repo). - or none. -- *prefixMapping*: *map[string]string* repository path prefix mapping. - -Notes: - -- The mapping only occurs in transfer commands and only when transferring to OCI registries (e.g. - when transferring to a CTF archive this option will be ignored). -- The mapping only happens for local resources. When external image references are transferred (with - option --copy-resources) the mapping will be ignored. -- The mapping in mode prefixMapping requires a full prefix of the composed final name. - Partial matches are not supported. The host name of the target will be skipped. -- The artifact name of the component-descriptor is not mapped. -- If the mapping is provided on the command line it must be JSON format and needs to be properly - escaped (see example below). - -Example: - -Assume a component named github.com/my_org/myexamplewithalongname and a chart name -echo in the Charts.yaml of the chart archive. The following input to a -resource.yaml creates a component version: - -
-name: mychart
-type: helmChart
-input:
-  type: helm
-  path: charts/mychart.tgz
----
-name: myimage
-type: ociImage
-version: 0.1.0
-input:
-  type: ociImage
-  repository: ocm/ocm.software/ocmcli/ocmcli-image
-  path: ghcr.io/acme/ocm/ocm.software/ocmcli/ocmcli-image:0.1.0
-
- -The following command: - -
-ocm "-X mapocirepo={\"mode\":\"mapping\",\"prefixMappings\":{\"acme/github.com/my_org/myexamplewithalongname/ocm/ocm.software/ocmcli\":\"acme/cli\", \"acme/github.com/my_org/myexamplewithalongnameabc123\":\"acme/mychart\"}}" transfer ctf -f --copy-resources ./ctf ghcr.io/acme
-
- -will result in the following artifacts in ghcr.io/my_org: - -
-mychart/echo
-cli/ocmcli-image
-
- -Note that the host name part of the transfer target ghcr.io/acme is excluded from the -prefix but the path acme is considered. - -The same using a config file .ocmconfig: -
-type: generic.config.ocm.software/v1
-configurations:
-...
-- type: attributes.config.ocm.software
-  attributes:
-	...
-	mapocirepo:
-	  mode: mapping
-	  prefixMappings:
-	    acme/github.com/my\_org/myexamplewithalongname/ocm/ocm.software/ocmcli: acme/cli
-		acme/github.com/my\_org/myexamplewithalongnameabc123: acme/mychart
-
- -
-ocm transfer ca -f --copy-resources ./ca ghcr.io/acme
-
-` -} - -func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { - if _, ok := v.(bool); ok { - return marshaller.Marshal(&Attribute{Mode: ShortHashMode}) - } - - if _, ok := v.(*Attribute); ok { - return marshaller.Marshal(v) - } - - return nil, fmt.Errorf("boolean or attribute struct required") -} - -func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { - var value bool - attr := &Attribute{} - - err := unmarshaller.Unmarshal(data, attr) - if err == nil { - switch attr.Mode { - case "": - case NoneMode: - case HashMode: - case ShortHashMode: - case MappingMode: - default: - return nil, errors.ErrInvalid("mode", attr.Mode) - } - return attr, nil - } - - err = unmarshaller.Unmarshal(data, &value) - if err == nil { - if value { - attr.Mode = ShortHashMode - } else { - attr.Mode = NoneMode - } - attr.PrefixMappings = map[string]string{} - return attr, nil - } - - return value, err -} - -//////////////////////////////////////////////////////////////////////////////// - -const ( - NoneMode = "none" - HashMode = "hash" - ShortHashMode = "shortHash" - MappingMode = "mapping" -) - -type Attribute struct { - Mode string `json:"mode"` - Always bool `json:"always,omitempty"` - Prefix *string `json:"prefix,omitempty"` - PrefixMappings map[string]string `json:"prefixMappings,omitempty"` -} - -func (a *Attribute) Map(name string) string { - if len(a.PrefixMappings) == 0 { - a.PrefixMappings = map[string]string{} - } - switch a.Mode { - case "", NoneMode: - return name - case HashMode, ShortHashMode: - return a.Hash(name, a.Mode == ShortHashMode) - case MappingMode: - return a.MapPrefix(name) - } - return name -} - -func (a *Attribute) MapPrefix(name string) string { - keys := utils.StringMapKeys(a.PrefixMappings) - for i := range keys { - k := keys[len(keys)-i-1] - if strings.HasPrefix(name, k+grammar.RepositorySeparator) { - name = a.PrefixMappings[k] + name[len(k):] - break - } - } - return name -} - -func (a *Attribute) Hash(name string, short bool) string { - if idx := strings.LastIndex(name, grammar.RepositorySeparator); idx > 0 { - prefix := name[:idx] - sum := sha256.Sum256([]byte(prefix)) - n := hex.EncodeToString(sum[:]) - if short { - n = n[:8] - } - n += grammar.RepositorySeparator + name[idx+1:] - if a.Always || len(n) < len(name) { - name = n - } - } - return name -} - -func (a *Attribute) Copy() *Attribute { - n := *a - n.PrefixMappings = maps.Clone(n.PrefixMappings) - return &n -} - -//////////////////////////////////////////////////////////////////////////////// - -func Get(ctx datacontext.Context) *Attribute { - a := ctx.GetAttributes().GetAttribute(ATTR_KEY) - if a == nil { - return &Attribute{Mode: NoneMode} - } - if b, ok := a.(bool); ok { - if b { - return &Attribute{Mode: ShortHashMode} - } else { - return &Attribute{Mode: NoneMode} - } - } - return a.(*Attribute).Copy() -} - -func Set(ctx datacontext.Context, a *Attribute) error { - return ctx.GetAttributes().SetAttribute(ATTR_KEY, a) -} diff --git a/pkg/contexts/ocm/attrs/mapocirepoattr/attr_test.go b/pkg/contexts/ocm/attrs/mapocirepoattr/attr_test.go deleted file mode 100644 index 8dbef8ffe..000000000 --- a/pkg/contexts/ocm/attrs/mapocirepoattr/attr_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package mapocirepoattr_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - me "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/mapocirepoattr" -) - -var _ = Describe("attribute", func() { - var ctx datacontext.Context - - BeforeEach(func() { - ctx = datacontext.New(nil) - }) - - It("set bool", func() { - Expect(me.Get(ctx)).To(Equal(&me.Attribute{Mode: me.NoneMode})) - ctx.GetAttributes().SetAttribute(me.ATTR_KEY, true) - a := me.Get(ctx) - Expect(a).To(Equal(&me.Attribute{Mode: me.ShortHashMode})) - hash := "5afa3f0f1b63d64422e7f93e2d9792b7c1f3b4462a931d80b25703f7e6fc79c2" - Expect(a.Map("very-long-path/with-many-path-segments/and-really-longer-than-a-hash/artifact")).To(Equal(hash[:8] + "/artifact")) - }) - - It("set attr", func() { - ctx.GetAttributes().SetAttribute(me.ATTR_KEY, &me.Attribute{Mode: me.MappingMode, PrefixMappings: map[string]string{"a": "b", "a/b": "c"}}) - a := me.Get(ctx) - Expect(a).To(Equal(&me.Attribute{Mode: me.MappingMode, PrefixMappings: map[string]string{"a": "b", "a/b": "c"}})) - Expect(a.Map("a/b/c")).To(Equal("c/c")) - Expect(a.Map("a/c")).To(Equal("b/c")) - Expect(a.Map("x/y")).To(Equal("x/y")) - }) -}) diff --git a/pkg/contexts/ocm/attrs/ociuploadattr/attr.go b/pkg/contexts/ocm/attrs/ociuploadattr/attr.go deleted file mode 100644 index 4fcf6dfeb..000000000 --- a/pkg/contexts/ocm/attrs/ociuploadattr/attr.go +++ /dev/null @@ -1,203 +0,0 @@ -package ociuploadattr - -import ( - "bytes" - "fmt" - "strings" - "sync" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/oci" - ocicpi "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - ATTR_KEY = "github.com/mandelsoft/ocm/ociuploadrepo" - ATTR_SHORT = "ociuploadrepo" -) - -func init() { - datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) -} - -type AttributeType struct{} - -func (a AttributeType) Name() string { - return ATTR_KEY -} - -func (a AttributeType) Description() string { - return ` -*oci base repository ref* -Upload local OCI artifact blobs to a dedicated repository. -` -} - -func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { - if _, ok := v.(*Attribute); !ok { - return nil, fmt.Errorf("OCI Upload Attribute structure required") - } - return marshaller.Marshal(v) -} - -func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { - var value Attribute - err := unmarshaller.Unmarshal(data, &value) - if err == nil { - if value.Repository != nil { - if value.Repository.GetType() == "" { - return nil, errors.ErrInvalidWrap(errors.Newf("missing repository type"), oci.KIND_OCI_REFERENCE, string(data)) - } - return &value, nil - } - if value.Ref == "" { - return nil, errors.ErrInvalidWrap(errors.Newf("missing repository or ref"), oci.KIND_OCI_REFERENCE, string(data)) - } - data = []byte(value.Ref) - } - ref, err := oci.ParseRef(string(data)) - if err != nil { - return nil, errors.ErrInvalidWrap(err, oci.KIND_OCI_REFERENCE, string(data)) - } - if ref.Tag != nil || ref.Digest != nil { - return nil, errors.ErrInvalidWrap(err, oci.KIND_OCI_REFERENCE, string(data)) - } - return &Attribute{Ref: strings.Trim(string(data), "\"")}, nil -} - -//////////////////////////////////////////////////////////////////////////////// - -type Attribute struct { - Ref string `json:"ociRef,omitempty"` - Repository *ocicpi.GenericRepositorySpec `json:"repository,omitempty"` - NamespacePrefix string `json:"namespacePrefix,omitempty"` - - lock sync.Mutex - ref *oci.RefSpec - spec []byte - - repo oci.Repository - prefix string -} - -func AttributeDescription() map[string]string { - return map[string]string{ - "ociRef": "an OCI repository reference", - "repository": "an OCI repository specification for the target OCI registry", - "namespacePrefix": "a namespace prefix used for the uploaded artifacts", - } -} - -func New(ref string) *Attribute { - return &Attribute{Ref: ref} -} - -func (a *Attribute) reset() { - a.repo = nil - a.prefix = "" - a.ref = nil - a.spec = nil -} - -func (a *Attribute) Close() error { - a.lock.Lock() - defer a.lock.Unlock() - if a.repo != nil { - defer a.reset() - return a.repo.Close() - } - return nil -} - -func (a *Attribute) GetInfo(ctx cpi.Context) (oci.Repository, *oci.UniformRepositorySpec, string, error) { - if a.Ref != "" { - return a.getByRef(ctx) - } - if a.Repository != nil { - return a.getBySpec(ctx) - } - return nil, nil, "", errors.ErrInvalid("ociuploadspec") -} - -func (a *Attribute) getBySpec(ctx cpi.Context) (oci.Repository, *oci.UniformRepositorySpec, string, error) { - data, err := a.Repository.MarshalJSON() - if err != nil { - return nil, nil, "", errors.Wrap(err, a.ref.String()) - } - - spec, err := a.Repository.Evaluate(ctx.OCIContext()) - if err != nil { - return nil, nil, "", errors.ErrInvalidWrap(err, oci.KIND_OCI_REFERENCE, string(data)) - } - - a.lock.Lock() - defer a.lock.Unlock() - - if a.spec == nil || bytes.Equal(a.spec, data) { - if a.repo != nil { - a.repo.Close() - a.reset() - } - - a.repo, err = ctx.OCIContext().RepositoryForSpec(spec) - if err != nil { - return nil, nil, "", err - } - - a.prefix = a.NamespacePrefix - a.spec = data - a.ref = &oci.RefSpec{UniformRepositorySpec: *spec.UniformRepositorySpec()} - ctx.Finalizer().Close(a) - } - return a.repo, &a.ref.UniformRepositorySpec, a.prefix, nil -} - -func (a *Attribute) getByRef(ctx cpi.Context) (oci.Repository, *oci.UniformRepositorySpec, string, error) { - ref, err := oci.ParseRef(a.Ref) - if err != nil { - return nil, nil, "", errors.ErrInvalidWrap(err, oci.KIND_OCI_REFERENCE, a.Ref) - } - if ref.Tag != nil || ref.Digest != nil { - return nil, nil, "", errors.ErrInvalidWrap(err, oci.KIND_OCI_REFERENCE, a.Ref) - } - - a.lock.Lock() - defer a.lock.Unlock() - if a.ref == nil || ref != *a.ref { - if a.repo != nil { - a.repo.Close() - a.reset() - } - - spec, err := ctx.OCIContext().MapUniformRepositorySpec(&ref.UniformRepositorySpec) - if err != nil { - return nil, nil, "", err - } - a.repo, err = ctx.OCIContext().RepositoryForSpec(spec) - if err != nil { - return nil, nil, "", err - } - a.prefix = ref.Repository - a.ref = &ref - ctx.Finalizer().Close(a) - } - return a.repo, &a.ref.UniformRepositorySpec, a.prefix, nil -} - -//////////////////////////////////////////////////////////////////////////////// - -func Get(ctx datacontext.Context) *Attribute { - a := ctx.GetAttributes().GetAttribute(ATTR_KEY) - if a == nil { - return nil - } - return a.(*Attribute) -} - -func Set(ctx datacontext.Context, attr *Attribute) error { - return ctx.GetAttributes().SetAttribute(ATTR_KEY, attr) -} diff --git a/pkg/contexts/ocm/attrs/ociuploadattr/attr_test.go b/pkg/contexts/ocm/attrs/ociuploadattr/attr_test.go deleted file mode 100644 index 649f0a7e6..000000000 --- a/pkg/contexts/ocm/attrs/ociuploadattr/attr_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package ociuploadattr_test - -import ( - "encoding/json" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/config" - "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/oci/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - me "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/ociuploadattr" - "github.com/open-component-model/ocm/pkg/runtime" -) - -var _ = Describe("attribute", func() { - var ctx ocm.Context - var cfgctx config.Context - - attr := &me.Attribute{Ref: "ref"} - - BeforeEach(func() { - cfgctx = config.WithSharedAttributes(datacontext.New(nil)).New() - credctx := credentials.WithConfigs(cfgctx).New() - ocictx := oci.WithCredentials(credctx).New() - ctx = ocm.WithOCIRepositories(ocictx).New() - }) - It("local setting", func() { - Expect(me.Get(ctx)).To(BeNil()) - Expect(me.Set(ctx, attr)).To(Succeed()) - Expect(me.Get(ctx)).To(BeIdenticalTo(attr)) - }) - - It("global setting", func() { - Expect(me.Get(cfgctx)).To(BeNil()) - Expect(me.Set(ctx, attr)).To(Succeed()) - Expect(me.Get(ctx)).To(BeIdenticalTo(attr)) - }) - - It("parses string", func() { - Expect(me.AttributeType{}.Decode([]byte("ref"), runtime.DefaultJSONEncoding)).To(Equal(&me.Attribute{Ref: "ref"})) - }) - - It("parses spec", func() { - spec, err := oci.ToGenericRepositorySpec(ocireg.NewRepositorySpec("ghcr.io")) - Expect(err).To(Succeed()) - attr := &me.Attribute{ - Repository: spec, - NamespacePrefix: "ref", - } - data, err := json.Marshal(attr) - Expect(err).To(Succeed()) - Expect(me.AttributeType{}.Decode(data, runtime.DefaultJSONEncoding)).To(Equal(attr)) - }) -}) diff --git a/pkg/contexts/ocm/attrs/plugincacheattr/attr.go b/pkg/contexts/ocm/attrs/plugincacheattr/attr.go deleted file mode 100644 index cb2bd2c8a..000000000 --- a/pkg/contexts/ocm/attrs/plugincacheattr/attr.go +++ /dev/null @@ -1,29 +0,0 @@ -package plugincacheattr - -import ( - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugindirattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/cache" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/plugins" -) - -const ( - ATTR_KEY = "github.com/mandelsoft/ocm/plugins" -) - -//////////////////////////////////////////////////////////////////////////////// - -func Get(ctxp ocm.ContextProvider) plugins.Set { - ctx := ctxp.OCMContext() - path := plugindirattr.Get(ctx) - - // avoid dead lock reading attribute during attribute creation - return ctx.GetAttributes().GetOrCreateAttribute(ATTR_KEY, func(ctx datacontext.Context) interface{} { - return plugins.New(ctx.(ocm.Context), path) - }).(plugins.Set) -} - -func Set(ctx ocm.Context, cache cache.PluginDir) error { - return ctx.GetAttributes().SetAttribute(ATTR_KEY, cache) -} diff --git a/pkg/contexts/ocm/attrs/plugindirattr/attr.go b/pkg/contexts/ocm/attrs/plugindirattr/attr.go deleted file mode 100644 index 5dbaf7ede..000000000 --- a/pkg/contexts/ocm/attrs/plugindirattr/attr.go +++ /dev/null @@ -1,77 +0,0 @@ -package plugindirattr - -import ( - "fmt" - "os" - - "github.com/mandelsoft/filepath/pkg/filepath" - "github.com/mandelsoft/vfs/pkg/osfs" - "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/modern-go/reflect2" - - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - ATTR_KEY = "github.com/mandelsoft/ocm/plugindir" - ATTR_SHORT = "plugindir" - - DEFAULT_PLUGIN_DIR = utils.DEFAULT_OCM_CONFIG_DIR + "/plugins" -) - -func init() { - datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) -} - -func DefaultDir(fs vfs.FileSystem) string { - home, _ := os.UserHomeDir() // use home if provided - if home != "" { - dir := filepath.Join(home, DEFAULT_PLUGIN_DIR) - if ok, err := vfs.DirExists(fs, dir); ok && err == nil { - return dir - } - } - return "" -} - -type AttributeType struct{} - -func (a AttributeType) Name() string { - return ATTR_KEY -} - -func (a AttributeType) Description() string { - return ` -*plugin directory* -Directory to look for OCM plugin executables. -` -} - -func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { - if _, ok := v.(string); !ok { - return nil, fmt.Errorf("directory path required") - } - return marshaller.Marshal(v) -} - -func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { - var value string - err := unmarshaller.Unmarshal(data, &value) - return value, err -} - -//////////////////////////////////////////////////////////////////////////////// - -func Get(ctx datacontext.Context) string { - a := ctx.GetAttributes().GetAttribute(ATTR_KEY) - if reflect2.IsNil(a) { - return DefaultDir(osfs.New()) - } - return a.(string) -} - -func Set(ctx datacontext.Context, path string) error { - return ctx.GetAttributes().SetAttribute(ATTR_KEY, path) -} diff --git a/pkg/contexts/ocm/attrs/plugindirattr/attr_test.go b/pkg/contexts/ocm/attrs/plugindirattr/attr_test.go deleted file mode 100644 index 46518dc7a..000000000 --- a/pkg/contexts/ocm/attrs/plugindirattr/attr_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package plugindirattr_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/config" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - me "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugindirattr" -) - -var _ = Describe("attribute", func() { - var ctx config.Context - - attr := "___test___" - - BeforeEach(func() { - ctx = config.WithSharedAttributes(datacontext.New(nil)).New() - }) - - It("local setting", func() { - Expect(me.Get(ctx)).NotTo(Equal(attr)) - Expect(me.Set(ctx, attr)).To(Succeed()) - Expect(me.Get(ctx)).To(BeIdenticalTo(attr)) - }) -}) diff --git a/pkg/contexts/ocm/attrs/signingattr/attr.go b/pkg/contexts/ocm/attrs/signingattr/attr.go deleted file mode 100644 index d6fe339e3..000000000 --- a/pkg/contexts/ocm/attrs/signingattr/attr.go +++ /dev/null @@ -1,102 +0,0 @@ -package signingattr - -import ( - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - ocm "github.com/open-component-model/ocm/pkg/contexts/ocm/context" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/signing" -) - -const ( - ATTR_KEY = "github.com/mandelsoft/ocm/signing" - ATTR_SHORT = "signing" -) - -type ( - Context = ocm.Context - ContextProvider = ocm.ContextProvider -) - -func init() { - datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}) -} - -type AttributeType struct{} - -func (a AttributeType) Name() string { - return ATTR_KEY -} - -func (a AttributeType) Description() string { - return ` -*JSON* -Public and private Key settings given as JSON document with the following -format: - -
-{
-  "publicKeys"": [
-     "<provider>": {
-       "data": ""<base64>"
-     }
-  ],
-  "privateKeys"": [
-     "<provider>": {
-       "path": ""<file path>"
-     }
-  ]
-
- -One of following data fields are possible: -- data: base64 encoded binary data -- stringdata: plain text data -- path: a file path to read the data from -` -} - -func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { - if _, ok := v.(signing.Registry); ok { - return nil, nil - } - return nil, errors.ErrNotSupported("encoding of key registry") -} - -func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { - var value Config - err := unmarshaller.Unmarshal(data, &value) - if err != nil { - return nil, err - } - value.SetType(ConfigType) - registry := signing.NewRegistry(signing.DefaultHandlerRegistry(), signing.DefaultKeyRegistry()) - value.ApplyToRegistry(registry) - return registry, err -} - -//////////////////////////////////////////////////////////////////////////////// - -func Get(ctx ContextProvider) signing.Registry { - a := ctx.OCMContext().GetAttributes().GetAttribute(ATTR_KEY) - if a == nil { - return signing.DefaultRegistry() - } - return a.(signing.Registry) -} - -func SetKeyRegistry(ctx ContextProvider, registry signing.KeyRegistry) error { - old := Get(ctx) - r := signing.NewRegistry(old.HandlerRegistry(), registry) - return ctx.OCMContext().GetAttributes().SetAttribute(ATTR_KEY, r) -} - -func SetHandlerRegistry(ctx ContextProvider, registry signing.HandlerRegistry) error { - old := Get(ctx) - r := signing.NewRegistry(registry, old.KeyRegistry()) - return ctx.OCMContext().GetAttributes().SetAttribute(ATTR_KEY, r) -} - -func Set(ctx ContextProvider, registry signing.Registry) error { - return ctx.OCMContext().GetAttributes().SetAttribute(ATTR_KEY, registry) -} diff --git a/pkg/contexts/ocm/attrs/signingattr/attr_test.go b/pkg/contexts/ocm/attrs/signingattr/attr_test.go deleted file mode 100644 index b79ce8b1c..000000000 --- a/pkg/contexts/ocm/attrs/signingattr/attr_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package signingattr_test - -import ( - "encoding/json" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/config" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/rootcertsattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" -) - -const NAME = "test" - -var _ = Describe("attribute", func() { - var cfgctx config.Context - var ocmctx ocm.Context - - BeforeEach(func() { - ocmctx = ocm.New(datacontext.MODE_EXTENDED) - cfgctx = ocmctx.ConfigContext() - }) - - It("marshal/unmarshal", func() { - cfg := signingattr.New() - cfg.AddPublicKeyData(NAME, []byte("keydata")) - - data, err := json.Marshal(cfg) - Expect(err).To(Succeed()) - - r := &signingattr.Config{} - Expect(json.Unmarshal(data, r)).To(Succeed()) - Expect(r).To(Equal(cfg)) - }) - - It("applies public key", func() { - cfg := signingattr.New() - cfg.AddPublicKeyData(NAME, []byte("keydata")) - - Expect(cfgctx.ApplyConfig(cfg, "from test")).To(Succeed()) - Expect(signingattr.Get(ocmctx).GetPublicKey(NAME)).To(Equal([]byte("keydata"))) - }) - - It("applies root certificate", func() { - certdata := ` ------BEGIN CERTIFICATE----- -MIIDBDCCAeygAwIBAgIQF+kRr0G+faDEAH5Y4P1J7DANBgkqhkiG9w0BAQsFADAc -MQwwCgYDVQQKEwNPQ00xDDAKBgNVBAMTA29jbTAeFw0yMzEyMjkxMDIyMzdaFw0y -NDEyMjgxMDIyMzdaMBwxDDAKBgNVBAoTA09DTTEMMAoGA1UEAxMDb2NtMIIBIjAN -BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvpTQIQFNy23ygef3pshdeNjT7TME -kPEuqrqcF3KIX1cX16pHMQeU+VzXAFRj3xCy3LAM8ZzLsdHSwZDsIsGdg0nAbGjz -+USez/9TGC58ktr/84Kh0gHDE28YSVhsnNSrBJcWaBlYZz4Iy89O2Xc4jbK34Cwg -Si0ES+Ru1lxLD6FSLYLe43wCIjWRJRrMFcua6nI0P4MCpcKmTkXG2/xz80QSobI3 -z/isqOT54FKHW8DZZVlQMOxh+loeLksfEq7EYVkQoUWEV6xyR24TEpMGfxERgDre -l7lmx8nIFzRMXkot+P19XWfUBgqctVEiDF4DlRE+SvCZsNCrg7nQuC2AZQIDAQAB -o0IwQDAOBgNVHQ8BAf8EBAMCAqQwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU -1iQqrWM/bCXMk+5c1bulfI5zlKcwDQYJKoZIhvcNAQELBQADggEBAAQO6lw6ePuX -E+NyhDYCulueMWHC7GRUKa1KpouFT2yM0BSQnP04VakTlwVO3w4w2KucSVVomHR3 -hTY9Ypx7iGLaqdXHmUZvx3uaTM5IXQKMMWL1LJsxAvuzucehgDlOnFBD91tKsr5o -VRvRU5ya0igBCnnGpFu7NuH3C9pgF01lrQ3EhUHuNeazxleaE3/uQWmAXfxFB4ci -gHMKSEk3HuYA1raDJFv4ihwO5pXHvlDhcW0C1oMG9lOCh8TXpVzzBDZiH1kWPWSs -gW9YBu7/p/22U4++X23RyaheGuysfRAMv9cTv+8T0J8NHaAmQz4/QHFXh+0/tQgU -EVQVGDF6KNU= ------END CERTIFICATE----- -` - cfg := signingattr.New() - cfg.AddRootCertificateData([]byte(certdata)) - - Expect(cfgctx.ApplyConfig(cfg, "from test")).To(Succeed()) - Expect(rootcertsattr.Get(ocmctx).HasRootCertificates()).To(BeTrue()) - }) -}) diff --git a/pkg/contexts/ocm/attrs/signingattr/config.go b/pkg/contexts/ocm/attrs/signingattr/config.go deleted file mode 100644 index 60d85c748..000000000 --- a/pkg/contexts/ocm/attrs/signingattr/config.go +++ /dev/null @@ -1,296 +0,0 @@ -package signingattr - -import ( - "crypto/x509/pkix" - "encoding/base64" - "encoding/json" - "encoding/pem" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/vfs/pkg/vfs" - "golang.org/x/exp/slices" - - cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/rootcertsattr" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/signutils" - "github.com/open-component-model/ocm/pkg/utils" -) - -const ( - ConfigType = "keys" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX - ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" -) - -func init() { - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1, usage)) -} - -type Issuer struct { - CommonName string `json:"commonName,omitempty"` - Organization []string `json:"organization,omitempty"` - OrganizationalUnit []string `json:"organizationalUnit,omitempty"` - - Country []string `json:"country,omitempty"` - Locality []string `json:"locality,omitempty"` - Province []string `json:"province,omitempty"` - StreetAddress []string `json:"streetAddress,omitempty"` - PostalCode []string `json:"postalCode,omitempty"` -} - -func (i *Issuer) Get() *pkix.Name { - return &pkix.Name{ - CommonName: i.CommonName, - - Country: slices.Clone(i.Country), - Organization: slices.Clone(i.Organization), - OrganizationalUnit: slices.Clone(i.OrganizationalUnit), - Locality: slices.Clone(i.Locality), - Province: slices.Clone(i.Province), - StreetAddress: slices.Clone(i.StreetAddress), - PostalCode: slices.Clone(i.PostalCode), - } -} - -func (i *Issuer) Set(issuer *pkix.Name) { - i.CommonName = issuer.CommonName - - i.Country = slices.Clone(issuer.Country) - i.Organization = slices.Clone(issuer.Organization) - i.OrganizationalUnit = slices.Clone(issuer.OrganizationalUnit) - i.Locality = slices.Clone(issuer.Locality) - i.Province = slices.Clone(issuer.Province) - i.StreetAddress = slices.Clone(issuer.StreetAddress) - i.PostalCode = slices.Clone(issuer.PostalCode) -} - -type KeySpec = cfgcpi.ContentSpec - -// Config describes a memory based repository interface. -type Config struct { - runtime.ObjectVersionedType `json:",inline"` - PublicKeys map[string]KeySpec `json:"publicKeys,omitempty"` - PrivateKeys map[string]KeySpec `json:"privateKeys,omitempty"` - Issuers map[string]Issuer `json:"issuers,omitempty"` - RootCertificates []KeySpec `json:"rootCertificates,omitempty"` - TSAUrl string `json:"tsaURL,omitempty"` -} - -type RawData []byte - -var _ json.Unmarshaler = (*RawData)(nil) - -func (r RawData) MarshalJSON() ([]byte, error) { - return json.Marshal(base64.StdEncoding.EncodeToString(r)) -} - -func (r *RawData) UnmarshalJSON(data []byte) error { - var s string - err := json.Unmarshal(data, &s) - if err != nil { - return err - } - *r, err = base64.StdEncoding.DecodeString(s) - return err -} - -// New creates a new memory ConfigSpec. -func New() *Config { - return &Config{ - ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), - } -} - -func (a *Config) GetType() string { - return ConfigType -} - -func (a *Config) AddIssuer(name string, issuer *pkix.Name) { - var i Issuer - - i.Set(issuer) - if a.Issuers == nil { - a.Issuers = map[string]Issuer{} - } - a.Issuers[name] = i -} - -func (a *Config) addKey(set *map[string]KeySpec, name string, key interface{}, conv func(interface{}) *pem.Block) error { - if *set == nil { - *set = map[string]KeySpec{} - } - switch data := key.(type) { - case []byte: - (*set)[name] = KeySpec{Data: data} - case string: - (*set)[name] = KeySpec{StringData: data} - default: - if conv != nil { - block := conv(key) - if block == nil { - return errors.ErrUnknown("format") - } - (*set)[name] = KeySpec{Parsed: key, StringData: string(pem.EncodeToMemory(block))} - } else { - (*set)[name] = KeySpec{Parsed: key} - } - } - return nil -} - -func (a *Config) AddPublicKey(name string, key interface{}) error { - return a.addKey(&a.PublicKeys, name, key, func(key interface{}) *pem.Block { return signutils.PemBlockForPublicKey(key) }) -} - -func (a *Config) AddPrivateKey(name string, key interface{}) error { - return a.addKey(&a.PrivateKeys, name, key, signutils.PemBlockForPrivateKey) -} - -func (a *Config) addKeyFile(set *map[string]KeySpec, name, path string, fss ...vfs.FileSystem) { - var fs vfs.FileSystem - for _, fs = range fss { - if fs != nil { - break - } - } - if *set == nil { - *set = map[string]KeySpec{} - } - (*set)[name] = KeySpec{Path: path, FileSystem: fs} -} - -func (a *Config) AddPublicKeyFile(name, path string, fss ...vfs.FileSystem) { - a.addKeyFile(&a.PublicKeys, name, path, fss...) -} - -func (a *Config) AddPrivateKeyFile(name, path string, fss ...vfs.FileSystem) { - a.addKeyFile(&a.PrivateKeys, name, path, fss...) -} - -func (a *Config) AddRootCertificateFile(name string, fss ...vfs.FileSystem) { - a.RootCertificates = append(a.RootCertificates, KeySpec{Path: name, FileSystem: utils.Optional(fss...)}) -} - -func (a *Config) addKeyData(set *map[string]KeySpec, name string, data []byte) { - if *set == nil { - *set = map[string]KeySpec{} - } - (*set)[name] = KeySpec{Data: data} -} - -func (a *Config) AddPublicKeyData(name string, data []byte) { - a.addKeyData(&a.PublicKeys, name, data) -} - -func (a *Config) AddPrivateKeyData(name string, data []byte) { - a.addKeyData(&a.PrivateKeys, name, data) -} - -func (a *Config) AddRootCertificateData(data []byte) { - a.RootCertificates = append(a.RootCertificates, KeySpec{Data: data}) -} - -func (a *Config) AddRootCertificate(chain signutils.GenericCertificateChain) error { - certs, err := signutils.GetCertificateChain(chain, false) - if err != nil { - return err - } - a.RootCertificates = append(a.RootCertificates, KeySpec{Data: signutils.CertificateChainToPem(certs), Parsed: certs}) - return nil -} - -func (a *Config) ApplyTo(ctx cfgcpi.Context, target interface{}) error { - t, ok := target.(Context) - if !ok { - if t, ok := target.(datacontext.AttributesContext); ok { - if t.AttributesContext().IsAttributesContext() { - return errors.Wrapf(a.ApplyToRootCertsAttr(rootcertsattr.Get(t)), "applying config to certattr failed") - } - } - return cfgcpi.ErrNoContext(ConfigType) - } - return errors.Wrapf(a.ApplyToRegistry(Get(t)), "applying config failed") -} - -func (a *Config) ApplyToRootCertsAttr(attr *rootcertsattr.Attribute) error { - for i, k := range a.RootCertificates { - key, err := k.Get() - if err != nil { - return errors.Wrapf(err, "cannot get root certificate %d", i) - } - err = attr.RegisterRootCertificates(key) - if err != nil { - return errors.Wrapf(err, "invalid certificate %d", i) - } - } - return nil -} - -func (a *Config) ApplyToRegistry(registry signing.Registry) error { - for n, k := range a.PublicKeys { - key, err := k.Get() - if err != nil { - return errors.Wrapf(err, "cannot get public key %s", n) - } - registry.RegisterPublicKey(n, key) - } - for n, k := range a.PrivateKeys { - key, err := k.Get() - if err != nil { - return errors.Wrapf(err, "cannot get private key %s", n) - } - registry.RegisterPrivateKey(n, key) - } - for n, k := range a.Issuers { - registry.RegisterIssuer(n, k.Get()) - } - if a.TSAUrl != "" { - registry.SetTSAUrl(a.TSAUrl) - } - return nil -} - -const usage = ` -The config type ` + ConfigType + ` can be used to define -public and private keys. A key value might be given by one of the fields: -- path: path of file with key data -- data: base64 encoded binary data -- stringdata: data a string parsed by key handler - -
-    type: ` + ConfigType + `
-    privateKeys:
-       <name>:
-         path: <file path>
-       ...
-    publicKeys:
-       <name>:
-         data: <base64 encoded key representation>
-       ...
-    rootCertificates:
-      - path: <file path>
-
-    issuers:
-       <name>:
-         commonName: acme.org
-
- -Issuers define an expected distinguished name for -public key certificates optionally provided together with -signatures. They support the following fields: -- commonName *string* -- organization *string array* -- organizationalUnit *string array* -- country *string array* -- locality *string array* -- province *string array* -- streetAddress *string array* -- postalCode *string array* - -At least the given values must be present in the certificate -to be accepted for a successful signature validation. - -` diff --git a/pkg/contexts/ocm/attrs/signingattr/setup.go b/pkg/contexts/ocm/attrs/signingattr/setup.go deleted file mode 100644 index 66b83ad4e..000000000 --- a/pkg/contexts/ocm/attrs/signingattr/setup.go +++ /dev/null @@ -1,27 +0,0 @@ -package signingattr - -import ( - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/signing" -) - -func init() { - datacontext.RegisterSetupHandler(datacontext.SetupHandlerFunction(setupContext)) -} - -func setupContext(mode datacontext.BuilderMode, ctx datacontext.Context) { - if octx, ok := ctx.(Context); ok { - switch mode { - case datacontext.MODE_SHARED: - fallthrough - case datacontext.MODE_DEFAULTED: - // do nothing, fallback to the default attribute lookup - case datacontext.MODE_EXTENDED: - Set(octx, signing.NewRegistry(signing.DefaultRegistry().HandlerRegistry(), signing.DefaultRegistry().KeyRegistry())) - case datacontext.MODE_CONFIGURED: - Set(octx, signing.DefaultRegistry().Copy()) - case datacontext.MODE_INITIAL: - Set(octx, signing.NewRegistry(nil, nil)) - } - } -} diff --git a/pkg/contexts/ocm/blobhandler/config/type.go b/pkg/contexts/ocm/blobhandler/config/type.go deleted file mode 100644 index 12a8f4bfc..000000000 --- a/pkg/contexts/ocm/blobhandler/config/type.go +++ /dev/null @@ -1,92 +0,0 @@ -package config - -import ( - "fmt" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/config" - cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - ConfigType = "uploader.ocm" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX - ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" -) - -func init() { - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1, usage)) -} - -// Config describes a memory based config interface. -type Config struct { - runtime.ObjectVersionedType `json:",inline"` - Registrations []Registration `json:"registrations,omitempty"` -} - -type Registration struct { - Name string `json:"name"` - Description string `json:"description,omitempty"` - blobhandler.HandlerOptions `json:",inline"` - Config blobhandler.HandlerConfig `json:"config,omitempty"` -} - -// New creates a new memory ConfigSpec. -func New() *Config { - return &Config{ - ObjectVersionedType: runtime.NewVersionedObjectType(ConfigType), - } -} - -func (a *Config) GetType() string { - return ConfigType -} - -func (a *Config) AddRegistration(hdlrs ...Registration) error { - for i, h := range hdlrs { - if h.Name == "" { - return fmt.Errorf("handler registration %d requires a name", i) - } - } - a.Registrations = append(a.Registrations, hdlrs...) - return nil -} - -func (a *Config) ApplyTo(ctx cfgcpi.Context, target interface{}) error { - t, ok := target.(cpi.Context) - if !ok { - return config.ErrNoContext(ConfigType) - } - reg := blobhandler.For(t) - for _, h := range a.Registrations { - accepted, err := reg.RegisterByName(h.Name, t, h.Config, &h.HandlerOptions) - if err != nil { - return errors.Wrapf(err, "registering upload handler %q[%s]", h.Name, h.Description) - } - if !accepted { - download.Logger(t).Info("no matching handler for configuration %q[%s]", h.Name, h.Description) - } - } - return nil -} - -const usage = ` -The config type ` + ConfigType + ` can be used to define a list -of pre-configured download handler registrations (see ocm ocm-downloadhandlers): - -
-    type: ` + ConfigType + `
-    descrition: "my standard download handler configuration"
-    handlers:
-      - name: oci/artifact
-        artifactType: ociImage
-        mimeType:
-        config: ...
-      ...
-
-` diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/maven/blobhandler.go b/pkg/contexts/ocm/blobhandler/handlers/generic/maven/blobhandler.go deleted file mode 100644 index b3811d6bd..000000000 --- a/pkg/contexts/ocm/blobhandler/handlers/generic/maven/blobhandler.go +++ /dev/null @@ -1,131 +0,0 @@ -package maven - -import ( - "crypto" - - "github.com/mandelsoft/goutils/finalizer" - "github.com/mandelsoft/goutils/ioutils" - "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_PACKAGE - -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) - - if hint == "" { - log.Warn("maven package hint is empty, skipping upload") - return nil, nil - } - // check conditions - if b.spec == nil { - return nil, nil - } - mimeType := blob.MimeType() - if resourcetypes.MAVEN_PACKAGE != 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.IsPackage() { - 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 := ioutils.NewDupReadCloser(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 deleted file mode 100644 index a0b6b0391..000000000 --- a/pkg/contexts/ocm/blobhandler/handlers/generic/maven/blobhandler_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package maven_test - -import ( - "encoding/json" - "os" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/env/builder" - - "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" - "github.com/open-component-model/ocm/pkg/maven/maventest" -) - -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()) - }) - - AfterEach(func() { - env.Cleanup() - }) - - 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.BlobAccessForCoords(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_PACKAGE)), 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/maven/registration.go b/pkg/contexts/ocm/blobhandler/handlers/generic/maven/registration.go deleted file mode 100644 index 6860b67c1..000000000 --- a/pkg/contexts/ocm/blobhandler/handlers/generic/maven/registration.go +++ /dev/null @@ -1,109 +0,0 @@ -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"` - 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 { - return nil - } - var raw rawConfig - err = json.Unmarshal(data, &raw) - if err != nil { - return err - } - *c = Config(raw) - - return nil -} - -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.MAVEN_PACKAGE, handler) - } - if config == nil { - return true, fmt.Errorf("maven target specification required") - } - cfg, err := registrations.DecodeConfig[Config](config) - if err != nil { - return true, errors.Wrapf(err, "blob handler configuration") - } - - ctx.BlobHandlers().Register(NewArtifactHandler(cfg), - cpi.ForArtifactType(resourcetypes.MAVEN_PACKAGE), - cpi.ForMimeType(mime.MIME_TGZ), - cpi.NewBlobHandlerOptions(olist...), - ) - - return true, nil -} - -func (r *RegistrationHandler) GetHandlers(_ cpi.Context) registrations.HandlerInfos { - 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 maven repository. -`, - ) -} diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/maven/registration_test.go b/pkg/contexts/ocm/blobhandler/handlers/generic/maven/registration_test.go deleted file mode 100644 index 316b8e206..000000000 --- a/pkg/contexts/ocm/blobhandler/handlers/generic/maven/registration_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package maven_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "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[maven.Config]("test")) - Expect(cfg).To(Equal(&maven.Config{Url: "test"})) - }) - - It("deserializes struct", func() { - 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/npm/blobhandler.go b/pkg/contexts/ocm/blobhandler/handlers/generic/npm/blobhandler.go deleted file mode 100644 index 526b1f3dc..000000000 --- a/pkg/contexts/ocm/blobhandler/handlers/generic/npm/blobhandler.go +++ /dev/null @@ -1,172 +0,0 @@ -package npm - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - - crds "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/npm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/logging" - "github.com/open-component-model/ocm/pkg/mime" - npmLogin "github.com/open-component-model/ocm/pkg/npm" -) - -const BLOB_HANDLER_NAME = "ocm/npmPackage" - -type artifactHandler struct { - spec *Config -} - -func NewArtifactHandler(repospec *Config) cpi.BlobHandler { - return &artifactHandler{repospec} -} - -func (b *artifactHandler) StoreBlob(blob cpi.BlobAccess, _ string, _ string, _ cpi.AccessSpec, ctx cpi.StorageContext) (cpi.AccessSpec, error) { - if b.spec == nil { - return nil, nil - } - - mimeType := blob.MimeType() - if mime.MIME_TGZ != mimeType && mime.MIME_TGZ_ALT != mimeType { - return nil, nil - } - - if b.spec.Url == "" { - return nil, fmt.Errorf("NPM registry url not provided") - } - - blobReader, err := blob.Reader() - if err != nil { - return nil, err - } - defer blobReader.Close() - - data, err := io.ReadAll(blobReader) - if err != nil { - return nil, err - } - - // read package.json from tarball to get name, version, etc. - log := logging.Context().Logger(npmLogin.REALM) - log.Debug("reading package.json from tarball") - var pkg *Package - pkg, err = prepare(data) - if err != nil { - return nil, err - } - tbName := pkg.Name + "-" + pkg.Version + ".tgz" - pkg.Dist.Tarball = b.spec.Url + pkg.Name + "/-/" + tbName - log = log.WithValues("package", pkg.Name, "version", pkg.Version) - log.Debug("identified") - - // check if package exists - exists, err := packageExists(b.spec.Url, *pkg, ctx.GetContext()) - if err != nil { - return nil, err - } - if exists { - log.Debug("package+version already exists, skipping upload") - return npm.New(b.spec.Url, pkg.Name, pkg.Version), nil - } - - // prepare body for upload - body := Body{ - ID: pkg.Name, - Name: pkg.Name, - Description: pkg.Description, - } - body.Versions = map[string]*Package{ - pkg.Version: pkg, - } - body.DistTags.Latest = pkg.Version - body.Readme = pkg.Readme - body.Attachments = map[string]*Attachment{ - tbName: NewAttachment(data), - } - marshal, err := json.Marshal(body) - if err != nil { - return nil, err - } - - // prepare PUT request - req, err := http.NewRequestWithContext(context.Background(), http.MethodPut, b.spec.Url+"/"+url.PathEscape(pkg.Name), bytes.NewReader(marshal)) - if err != nil { - return nil, err - } - err = npmLogin.Authorize(req, ctx.GetContext(), b.spec.Url, pkg.Name) - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", "application/json") - - // send PUT request - upload tgz - client := http.Client{} - log.Debug("uploading") - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusCreated { - all, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - return nil, fmt.Errorf("http (%d) - failed to upload package: %s", resp.StatusCode, string(all)) - } - log.Debug("successfully uploaded") - return npm.New(b.spec.Url, pkg.Name, pkg.Version), nil -} - -// Check if package already exists in npm registry. If it does, checks if it's the same. -func packageExists(repoUrl string, pkg Package, ctx crds.ContextProvider) (bool, error) { - client := http.Client{} - req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, repoUrl+"/"+url.PathEscape(pkg.Name)+"/"+url.PathEscape(pkg.Version), nil) - if err != nil { - return false, err - } - err = npmLogin.Authorize(req, ctx, repoUrl, pkg.Name) - if err != nil { - return false, err - } - resp, err := client.Do(req) - if err != nil { - return false, err - } - defer resp.Body.Close() - if resp.StatusCode == http.StatusNotFound { - // artifact doesn't exist, it's safe to upload - return false, nil - } - - // artifact exists, let's check if it's the same - all, err := io.ReadAll(resp.Body) - if err != nil { - return false, err - } - if resp.StatusCode != http.StatusOK { - return false, fmt.Errorf("http (%d) - %s", resp.StatusCode, string(all)) - } - var data map[string]interface{} - err = json.Unmarshal(all, &data) - if err != nil { - return false, err - } - dist := data["dist"].(map[string]interface{}) - if pkg.Dist.Integrity == dist["integrity"] { - // sha-512 sum is the same, we can skip the upload - return true, nil - } - if pkg.Dist.Shasum == dist["shasum"] { - // sha-1 sum is the same, we can skip the upload - return true, nil - } - - return false, fmt.Errorf("artifact already exists but has different shasum or integrity") -} diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/npm/registration.go b/pkg/contexts/ocm/blobhandler/handlers/generic/npm/registration.go deleted file mode 100644 index 40027a509..000000000 --- a/pkg/contexts/ocm/blobhandler/handlers/generic/npm/registration.go +++ /dev/null @@ -1,75 +0,0 @@ -package npm - -import ( - "encoding/json" - "fmt" - - "github.com/mandelsoft/goutils/errors" - - "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/mime" - "github.com/open-component-model/ocm/pkg/registrations" -) - -type Config struct { - Url string `json:"url"` -} - -type rawConfig Config - -func (c *Config) UnmarshalJSON(data []byte) error { - err := json.Unmarshal(data, &c.Url) - if err == nil { - return nil - } - var raw rawConfig - err = json.Unmarshal(data, &raw) - if err != nil { - return err - } - *c = Config(raw) - - return nil -} - -func init() { - cpi.RegisterBlobHandlerRegistrationHandler(BLOB_HANDLER_NAME, &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 npmjsArtifact handler %q", handler) - } - if config == nil { - return true, fmt.Errorf("npm target specification required") - } - cfg, err := registrations.DecodeConfig[Config](config) - if err != nil { - return true, errors.Wrapf(err, "blob handler configuration") - } - - ctx.BlobHandlers().Register(NewArtifactHandler(cfg), - cpi.ForArtifactType(resourcetypes.NPM_PACKAGE), - cpi.ForMimeType(mime.MIME_TGZ), - cpi.NewBlobHandlerOptions(olist...), - ) - - return true, nil -} - -func (r *RegistrationHandler) GetHandlers(_ cpi.Context) registrations.HandlerInfos { - return registrations.NewLeafHandlerInfo("uploading npm artifacts", ` -The `+BLOB_HANDLER_NAME+` uploader is able to upload npm artifacts -as artifact archive according to the npm package 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 npm repository. -`, - ) -} diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/npm/registration_test.go b/pkg/contexts/ocm/blobhandler/handlers/generic/npm/registration_test.go deleted file mode 100644 index c5ad5c005..000000000 --- a/pkg/contexts/ocm/blobhandler/handlers/generic/npm/registration_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package npm_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/generic/npm" - "github.com/open-component-model/ocm/pkg/registrations" -) - -var _ = Describe("Config deserialization Test Environment", func() { - It("deserializes string", func() { - cfg := Must(registrations.DecodeConfig[npm.Config]("test")) - Expect(cfg).To(Equal(&npm.Config{Url: "test"})) - }) - - It("deserializes struct", func() { - cfg := Must(registrations.DecodeConfig[npm.Config](`{"url":"test"}`)) - Expect(cfg).To(Equal(&npm.Config{Url: "test"})) - }) -}) diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/ocirepo/blobhandler.go b/pkg/contexts/ocm/blobhandler/handlers/generic/ocirepo/blobhandler.go deleted file mode 100644 index d4ba687be..000000000 --- a/pkg/contexts/ocm/blobhandler/handlers/generic/ocirepo/blobhandler.go +++ /dev/null @@ -1,126 +0,0 @@ -package ocirepo - -import ( - "encoding/json" - "path" - "strings" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/sliceutils" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common/accessobj" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/ociuploadattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/utils" -) - -func init() { - for _, mime := range artdesc.ArchiveBlobTypes() { - cpi.RegisterBlobHandler(NewArtifactHandler(), cpi.ForMimeType(mime), cpi.WithPrio(10)) - } -} - -//////////////////////////////////////////////////////////////////////////////// - -// artifactHandler stores artifact blobs as OCIArtifacts regardless of the -// intended OCM target repository. -// It acts on the OCI upload attribute to determine the target OCI repository. -// If none is configured, it does nothing. -type artifactHandler struct { - spec *ociuploadattr.Attribute -} - -func NewArtifactHandler(repospec ...*ociuploadattr.Attribute) cpi.BlobHandler { - return &artifactHandler{utils.Optional(repospec...)} -} - -func (b *artifactHandler) StoreBlob(blob cpi.BlobAccess, artType, hint string, global cpi.AccessSpec, ctx cpi.StorageContext) (cpi.AccessSpec, error) { - attr := b.spec - if attr == nil { - attr = ociuploadattr.Get(ctx.GetContext()) - } - if attr == nil { - return nil, nil - } - - mediaType := blob.MimeType() - if !artdesc.IsOCIMediaType(mediaType) || (!strings.HasSuffix(mediaType, "+tar") && !strings.HasSuffix(mediaType, "+tar+gzip")) { - return nil, nil - } - - repo, base, prefix, err := attr.GetInfo(ctx.GetContext()) - if err != nil { - return nil, err - } - - // this section is for logging, only - target, err := json.Marshal(repo.GetSpecification()) - if err != nil { - return nil, errors.Wrapf(err, "cannot marshal target specification") - } - values := []interface{}{ - "arttype", artType, - "mediatype", mediaType, - "hint", hint, - "target", string(target), - } - if m, ok := blob.(blobaccess.AnnotatedBlobAccess[cpi.AccessMethod]); ok { - // prepare for optimized point to point implementation - cpi.BlobHandlerLogger(ctx.GetContext()).Debug("oci generic artifact handler with ocm access source", - sliceutils.CopyAppend[any](values, "sourcetype", m.Source().AccessSpec().GetType())..., - ) - } else { - cpi.BlobHandlerLogger(ctx.GetContext()).Debug("oci generic artifact handler", values...) - } - - var namespace oci.NamespaceAccess - var version string - var name string - var tag string - - if hint == "" { - name = path.Join(prefix, ctx.TargetComponentName()) - } else { - i := strings.LastIndex(hint, ":") - if i > 0 { - version = hint[i:] - name = path.Join(prefix, hint[:i]) - tag = version[1:] // remove colon - } else { - name = hint - } - } - namespace, err = repo.LookupNamespace(name) - if err != nil { - return nil, errors.Wrapf(err, "lookup namespace %s in target repository %s", name, attr.Ref) - } - defer namespace.Close() - - set, err := artifactset.OpenFromBlob(accessobj.ACC_READONLY, blob) - if err != nil { - return nil, err - } - defer set.Close() - digest := set.GetMain() - if version == "" { - version = "@" + digest.String() - } - art, err := set.GetArtifact(digest.String()) - if err != nil { - return nil, err - } - defer art.Close() - - err = artifactset.TransferArtifact(art, namespace, oci.AsTags(tag)...) - if err != nil { - return nil, err - } - - ref := base.ComposeRef(namespace.GetNamespace() + version) - return ociartifact.New(ref), nil -} diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/ocirepo/registration.go b/pkg/contexts/ocm/blobhandler/handlers/generic/ocirepo/registration.go deleted file mode 100644 index 2b7935816..000000000 --- a/pkg/contexts/ocm/blobhandler/handlers/generic/ocirepo/registration.go +++ /dev/null @@ -1,77 +0,0 @@ -package ocirepo - -import ( - "fmt" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/ociuploadattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/listformat" - "github.com/open-component-model/ocm/pkg/registrations" -) - -type Config = ociuploadattr.Attribute - -func init() { - cpi.RegisterBlobHandlerRegistrationHandler("ocm/ociArtifacts", &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 ociArtifact handler %q", handler) - } - if config == nil { - return true, fmt.Errorf("oci target specification required") - } - attr, err := registrations.DecodeConfig[Config](config, ociuploadattr.AttributeType{}.Decode) - if err != nil { - return true, errors.Wrapf(err, "blob handler configuration") - } - - var mimes []string - opts := cpi.NewBlobHandlerOptions(olist...) - if opts.MimeType != "" { - found := false - for _, a := range artdesc.ArchiveBlobTypes() { - if a == opts.MimeType { - found = true - break - } - } - if !found { - return true, fmt.Errorf("unexpected type mime type %q for oci blob handler target", opts.MimeType) - } - mimes = append(mimes, opts.MimeType) - } else { - mimes = artdesc.ArchiveBlobTypes() - } - - h := NewArtifactHandler(attr) - for _, m := range mimes { - opts.MimeType = m - ctx.BlobHandlers().Register(h, opts) - } - - return true, nil -} - -func (r *RegistrationHandler) GetHandlers(ctx cpi.Context) registrations.HandlerInfos { - return registrations.NewLeafHandlerInfo("downloading OCI artifacts", ` -The ociArtifacts downloader is able to download OCI artifacts -as artifact archive according to the OCI distribution spec. -The following artifact media types are supported: -`+listformat.FormatList("", artdesc.ArchiveBlobTypes()...)+` -By default, it is registered for these mimetypes. - -It accepts a config with the following fields: -`+listformat.FormatMapElements("", ociuploadattr.AttributeDescription())+` -Alternatively, a single string value can be given representing an OCI repository -reference.`, - ) -} diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/ocirepo/upload_test.go b/pkg/contexts/ocm/blobhandler/handlers/generic/ocirepo/upload_test.go deleted file mode 100644 index 97c132bac..000000000 --- a/pkg/contexts/ocm/blobhandler/handlers/generic/ocirepo/upload_test.go +++ /dev/null @@ -1,244 +0,0 @@ -package ocirepo_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/oci/testhelper" - . "github.com/open-component-model/ocm/pkg/env/builder" - - "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/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/grammar" - ctfoci "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/ociuploadattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/comparch" - ctfocm "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" -) - -const ( - COMP = "github.com/compa" - VERS = "1.0.0" - CA = "ca" - CTF = "ctf" - COPY = "ctf.copy" - TARGET = "/tmp/target" -) - -const ( - OCIHOST = "alias" - OCIPATH = "/tmp/source" -) - -var _ = Describe("upload", func() { - var env *Builder - - BeforeEach(func() { - env = NewBuilder() - - // fake OCI registry - FakeOCIRepo(env, OCIPATH, OCIHOST) - - env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { - OCIManifest1(env) - }) - - env.OCICommonTransport(TARGET, accessio.FormatDirectory) - - env.ComponentArchive(CA, accessio.FormatDirectory, COMP, VERS, func() { - env.Provider("mandelsoft") - env.Resource("value", "", resourcetypes.OCI_IMAGE, v1.LocalRelation, func() { - env.Access( - ociartifact.New(oci.StandardOCIRef(OCIHOST+".alias", OCINAMESPACE, OCIVERSION)), - ) - }) - }) - - ca := Must(comparch.Open(env.OCMContext(), accessobj.ACC_READONLY, CA, 0, env)) - oca := accessio.OnceCloser(ca) - defer Close(oca) - - ctf := Must(ctfocm.Create(env.OCMContext(), accessobj.ACC_CREATE, CTF, 0o700, env)) - octf := accessio.OnceCloser(ctf) - defer Close(octf) - - handler := Must(standard.New(standard.ResourcesByValue())) - - MustBeSuccessful(transfer.TransferVersion(nil, nil, ca, ctf, handler)) - - // now we have a transport archive with local blob for the image - }) - - AfterEach(func() { - env.Cleanup() - }) - - It("validated original oci manifest", func() { - ctx := env.OCMContext() - - ocirepo := Must(ctfoci.Open(ctx, accessobj.ACC_READONLY, OCIPATH, 0o700, env)) - defer Close(ocirepo, "ocoirepo") - - ns := Must(ocirepo.LookupNamespace(OCINAMESPACE)) - defer Close(ns, "namespace") - - art := Must(ns.GetArtifact(OCIVERSION)) - defer Close(art, "artifact") - - Expect(art.Digest().Encoded()).To(Equal(D_OCIMANIFEST1)) - }) - - It("validated original digest", func() { - ctx := env.OCMContext() - - ctf := Must(ctfocm.Open(ctx, accessobj.ACC_READONLY, CTF, 0o700, env)) - defer Close(ctf, "ctf") - - cv := Must(ctf.LookupComponentVersion(COMP, VERS)) - defer Close(cv, "component version") - - ra := Must(cv.GetResourceByIndex(0)) - acc := Must(ra.Access()) - Expect(acc.GetKind()).To(Equal(localblob.Type)) - - Expect(ra.Meta().Digest).To(Equal(DS_OCIMANIFEST1)) - }) - - It("transfers oci artifact", func() { - ctx := env.OCMContext() - - ctf := Must(ctfocm.Open(ctx, accessobj.ACC_READONLY, CTF, 0o700, env)) - defer Close(ctf, "ctf") - - cv := Must(ctf.LookupComponentVersion(COMP, VERS)) - ocv := accessio.OnceCloser(cv) - defer Close(ocv) - ra := Must(cv.GetResourceByIndex(0)) - acc := Must(ra.Access()) - Expect(acc.GetKind()).To(Equal(localblob.Type)) - - // transfer component - copy := Must(ctfocm.Create(ctx, accessobj.ACC_CREATE, COPY, 0o700, env)) - ocopy := accessio.OnceCloser(copy) - defer Close(ocopy) - - // prepare upload to target OCI repo - attr := ociuploadattr.New(TARGET + grammar.RepositorySeparator + grammar.RepositorySeparator + "copy") - ociuploadattr.Set(ctx, attr) - - MustBeSuccessful(transfer.TransferVersion(nil, nil, cv, copy, nil)) - - // check type - cv2 := Must(copy.LookupComponentVersion(COMP, VERS)) - ocv2 := accessio.OnceCloser(cv2) - defer Close(ocv2) - ra = Must(cv2.GetResourceByIndex(0)) - Expect(ra.Meta().Digest).To(Equal(DS_OCIMANIFEST1)) - acc = Must(ra.Access()) - Expect(acc.GetKind()).To(Equal(ociartifact.Type)) - val := Must(ctx.AccessSpecForSpec(acc)) - // TODO: the result is invalid for ctf: better handling for ctf refs - Expect(val.(*ociartifact.AccessSpec).ImageReference).To(Equal("/tmp/target//copy/ocm/value:v2.0")) - - attr.Close() - target, err := ctfoci.Open(ctx.OCIContext(), accessobj.ACC_READONLY, TARGET, 0, env) - Expect(err).To(Succeed()) - defer Close(target) - Expect(target.ExistsArtifact("copy/ocm/value", "v2.0")).To(BeTrue()) - }) - - It("transfers oci artifact with named handler and object config", func() { - ctx := env.OCMContext() - - ctf := Must(ctfocm.Open(ctx, accessobj.ACC_READONLY, CTF, 0o700, env)) - defer Close(ctf, "ctf") - - cv := Must(ctf.LookupComponentVersion(COMP, VERS)) - ocv := accessio.OnceCloser(cv) - defer Close(ocv) - ra := Must(cv.GetResourceByIndex(0)) - acc := Must(ra.Access()) - Expect(acc.GetKind()).To(Equal(localblob.Type)) - - // transfer component - copy := Must(ctfocm.Create(ctx, accessobj.ACC_CREATE, COPY, 0o700, env)) - ocopy := accessio.OnceCloser(copy) - defer Close(ocopy) - - // prepare upload to target OCI repo - attr := ociuploadattr.New(TARGET + grammar.RepositorySeparator + grammar.RepositorySeparator + "copy") - MustBeSuccessful(blobhandler.RegisterHandlerByName(ctx, "ocm/ociArtifacts", attr)) - - MustBeSuccessful(transfer.TransferVersion(nil, nil, cv, copy, nil)) - - // check type - cv2 := Must(copy.LookupComponentVersion(COMP, VERS)) - ocv2 := accessio.OnceCloser(cv2) - defer Close(ocv2) - ra = Must(cv2.GetResourceByIndex(0)) - acc = Must(ra.Access()) - Expect(acc.GetKind()).To(Equal(ociartifact.Type)) - val := Must(ctx.AccessSpecForSpec(acc)) - // TODO: the result is invalid for ctf: better handling for ctf refs - Expect(val.(*ociartifact.AccessSpec).ImageReference).To(Equal("/tmp/target//copy/ocm/value:v2.0")) - - // attr.Close() - env.OCMContext().Finalize() - target, err := ctfoci.Open(ctx.OCIContext(), accessobj.ACC_READONLY, TARGET, 0, env) - Expect(err).To(Succeed()) - defer Close(target) - Expect(target.ExistsArtifact("copy/ocm/value", "v2.0")).To(BeTrue()) - }) - - It("transfers oci artifact with named handler and string config", func() { - ctx := env.OCMContext() - - ctf := Must(ctfocm.Open(ctx, accessobj.ACC_READONLY, CTF, 0o700, env)) - defer Close(ctf, "ctf") - - cv := Must(ctf.LookupComponentVersion(COMP, VERS)) - ocv := accessio.OnceCloser(cv) - defer Close(ocv) - ra := Must(cv.GetResourceByIndex(0)) - acc := Must(ra.Access()) - Expect(acc.GetKind()).To(Equal(localblob.Type)) - - // transfer component - copy := Must(ctfocm.Create(ctx, accessobj.ACC_CREATE, COPY, 0o700, env)) - ocopy := accessio.OnceCloser(copy) - defer Close(ocopy) - - // prepare upload to target OCI repo - // attr := ociuploadattr.New(TARGET + grammar.RepositorySeparator + grammar.RepositorySeparator + "copy") - attr := TARGET + grammar.RepositorySeparator + grammar.RepositorySeparator + "copy" - MustBeSuccessful(blobhandler.RegisterHandlerByName(ctx, "ocm/ociArtifacts", attr)) - - MustBeSuccessful(transfer.TransferVersion(nil, nil, cv, copy, nil)) - - // check type - cv2 := Must(copy.LookupComponentVersion(COMP, VERS)) - ocv2 := accessio.OnceCloser(cv2) - defer Close(ocv2) - ra = Must(cv2.GetResourceByIndex(0)) - acc = Must(ra.Access()) - Expect(acc.GetKind()).To(Equal(ociartifact.Type)) - val := Must(ctx.AccessSpecForSpec(acc)) - // TODO: the result is invalid for ctf: better handling for ctf refs - Expect(val.(*ociartifact.AccessSpec).ImageReference).To(Equal("/tmp/target//copy/ocm/value:v2.0")) - - // attr.Close() - env.OCMContext().Finalize() - target, err := ctfoci.Open(ctx.OCIContext(), accessobj.ACC_READONLY, TARGET, 0, env) - Expect(err).To(Succeed()) - defer Close(target) - Expect(target.ExistsArtifact("copy/ocm/value", "v2.0")).To(BeTrue()) - }) -}) diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/plugin/blobhandler.go b/pkg/contexts/ocm/blobhandler/handlers/generic/plugin/blobhandler.go deleted file mode 100644 index dd259c4f0..000000000 --- a/pkg/contexts/ocm/blobhandler/handlers/generic/plugin/blobhandler.go +++ /dev/null @@ -1,89 +0,0 @@ -package plugin - -import ( - "encoding/json" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/identity/hostpath" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" -) - -// pluginHandler delegates storage of blobs to a plugin based handler. -type pluginHandler struct { - plugin plugin.Plugin - name string - target json.RawMessage - targetinfo *plugin.UploadTargetSpecInfo -} - -func New(p plugin.Plugin, name string, target json.RawMessage) (cpi.BlobHandler, error) { - var err error - - ud := p.GetUploaderDescriptor(name) - if ud == nil { - return nil, errors.ErrUnknown(descriptor.KIND_UPLOADER, name, p.Name()) - } - - var info *plugin.UploadTargetSpecInfo - if target != nil { - info, err = p.ValidateUploadTarget(name, target) - if err != nil { - return nil, err - } - } - return &pluginHandler{ - plugin: p, - name: name, - target: target, - targetinfo: info, - }, nil -} - -func (b *pluginHandler) StoreBlob(blob cpi.BlobAccess, artType, hint string, global cpi.AccessSpec, ctx cpi.StorageContext) (acc cpi.AccessSpec, err error) { - var creds credentials.Credentials - - if b.targetinfo != nil { - if len(b.targetinfo.ConsumerId) > 0 { - creds, err = credentials.CredentialsForConsumer(ctx.GetContext(), b.targetinfo.ConsumerId, hostpath.IdentityMatcher(b.targetinfo.ConsumerId.Type())) - if err != nil { - return nil, err - } - } - } - - target := b.target - - if b.target == nil { - target, err = json.Marshal(ctx.TargetComponentRepository().GetSpecification()) - if err != nil { - return nil, errors.Wrapf(err, "cannot marshal target repo spec") - } - } - - cpi.BlobHandlerLogger(ctx.GetContext()).Debug("plugin blob handler", - "plugin", b.plugin.Name(), - "uploader", b.name, - "arttype", artType, - "mediatype", blob.MimeType(), - "hint", hint, - "target", string(target), - ) - - var creddata json.RawMessage - if creds != nil { - creddata, err = json.Marshal(creds.Properties()) - if err != nil { - return nil, errors.Wrapf(err, "cannot marshal credentials") - } - } - - r := accessio.NewOndemandReader(blob) - defer errors.PropagateError(&err, r.Close) - - return b.plugin.Put(b.name, r, artType, blob.MimeType(), hint, creddata, target) -} diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/plugin/registration.go b/pkg/contexts/ocm/blobhandler/handlers/generic/plugin/registration.go deleted file mode 100644 index 4379f4906..000000000 --- a/pkg/contexts/ocm/blobhandler/handlers/generic/plugin/registration.go +++ /dev/null @@ -1,120 +0,0 @@ -package plugin - -import ( - "encoding/json" - "fmt" - "strings" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugincacheattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin" - "github.com/open-component-model/ocm/pkg/registrations" -) - -type Config = json.RawMessage - -func init() { - cpi.RegisterBlobHandlerRegistrationHandler("plugin", &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) { - path := cpi.NewNamePath(handler) - - if config == nil { - return true, fmt.Errorf("target specification required") - } - - if len(path) < 1 || len(path) > 2 { - return true, fmt.Errorf("plugin handler must be of the form [/]") - } - - opts := cpi.NewBlobHandlerOptions(olist...) - - name := "" - if len(path) > 1 { - name = path[1] - } - - attr, err := registrations.DecodeAnyConfig(config) - if err != nil { - return true, errors.Wrapf(err, "plugin upload handler target config for %s/%s", path[0], name) - } - - _, _, err = RegisterBlobHandler(ctx, path[0], name, opts.ArtifactType, opts.MimeType, attr) - return true, err -} - -func RegisterBlobHandler(ctx ocm.Context, pname, name string, artType, mediaType string, target json.RawMessage) (string, plugin.UploaderKeySet, error) { - set := plugincacheattr.Get(ctx) - if set == nil { - return "", nil, errors.ErrUnknown(plugin.KIND_PLUGIN, pname) - } - - p := set.Get(pname) - if p == nil { - return "", nil, errors.ErrUnknown(plugin.KIND_PLUGIN, pname) - } - - if name != "" { - if p.GetUploaderDescriptor(name) == nil { - return "", nil, fmt.Errorf("uploader %s not found in plugin %q", name, pname) - } - } - keys := plugin.UploaderKeySet{}.Add(plugin.UploaderKey{}.SetArtifact(artType, mediaType)) - d := p.LookupUploader(name, artType, mediaType) - - if len(d) == 0 { - keys = p.LookupUploaderKeys(name, artType, mediaType) - if len(keys) == 0 { - if name == "" { - return "", nil, fmt.Errorf("no uploader found for [art:%q, media:%q]", artType, mediaType) - } - return "", nil, fmt.Errorf("uploader %s not valid for [art:%q, media:%q]", name, artType, mediaType) - } - d = p.LookupUploadersForKeys(name, keys) - } - if len(d) > 1 { - return "", nil, fmt.Errorf("multiple uploaders found for [art:%q, media:%q]: %s", artType, mediaType, strings.Join(d.GetNames(), ", ")) - } - h, err := New(p, d[0].Name, target) - if err != nil { - return d[0].Name, nil, err - } - for k := range keys { - ctx.BlobHandlers().Register(h, cpi.ForArtifactType(k.GetArtifactType()), cpi.ForMimeType(k.GetMediaType())) - } - return d[0].Name, keys, nil -} - -func (r *RegistrationHandler) GetHandlers(ctx cpi.Context) registrations.HandlerInfos { - infos := registrations.NewNodeHandlerInfo("downloaders provided by plugins", - "sub namespace of the form <plugin name>/<handler>") - - set := plugincacheattr.Get(ctx) - if set == nil { - return infos - } - - for _, name := range set.PluginNames() { - p := set.Get(name) - if !p.IsValid() { - continue - } - for _, u := range p.GetDescriptor().Uploaders { - i := registrations.HandlerInfo{ - Name: name + "/" + u.GetName(), - ShortDesc: "", - Description: u.GetDescription(), - } - infos = append(infos, i) - } - } - return infos -} diff --git a/pkg/contexts/ocm/blobhandler/handlers/generic/plugin/upload_test.go b/pkg/contexts/ocm/blobhandler/handlers/generic/plugin/upload_test.go deleted file mode 100644 index a35617506..000000000 --- a/pkg/contexts/ocm/blobhandler/handlers/generic/plugin/upload_test.go +++ /dev/null @@ -1,201 +0,0 @@ -//go:build unix - -package plugin_test - -import ( - "encoding/json" - "fmt" - "os" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/testutils" - . "github.com/open-component-model/ocm/pkg/env/builder" - - "github.com/mandelsoft/filepath/pkg/filepath" - "github.com/mandelsoft/vfs/pkg/osfs" - "github.com/mandelsoft/vfs/pkg/vfs" - - "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/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler" - "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/generic/plugin" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - plugin2 "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/config" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/plugins" - "github.com/open-component-model/ocm/pkg/contexts/ocm/registration" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const PLUGIN = "test" - -const ( - ARCH = "ctf" - OUT = "/tmp/res" - COMP = "github.com/mandelsoft/comp" - VERS = "1.0.0" - PROVIDER = "mandelsoft" - RSCTYPE = "TestArtifact" - MEDIA = "text/plain" -) - -const ( - REPOTYPE = "test/v1" - ACCTYPE = "test/v1" - REPO = "plugin" - CONTENT = "some test content\n" - HINT = "given" -) - -type RepoSpec struct { - runtime.ObjectVersionedType - Path string `json:"path"` -} - -func NewRepoSpec(path string) *RepoSpec { - return &RepoSpec{ - ObjectVersionedType: runtime.ObjectVersionedType{Type: REPOTYPE}, - Path: path, - } -} - -type AccessSpec struct { - runtime.ObjectVersionedType - Path string `json:"path"` - MediaType string `json:"mediaType"` - Repository string `json:"repo"` -} - -func NewAccessSpec(media, path, repo string) *AccessSpec { - return &AccessSpec{ - ObjectVersionedType: runtime.ObjectVersionedType{Type: ACCTYPE}, - MediaType: media, - Path: path, - Repository: repo, - } -} - -var _ = Describe("setup plugin cache", func() { - var ctx ocm.Context - var registry plugins.Set - var repodir string - var env *Builder - var plugins TempPluginDir - - accessSpec := NewAccessSpec(MEDIA, "given", REPO) - repoSpec := NewRepoSpec(REPO) - - BeforeEach(func() { - repodir = Must(os.MkdirTemp(os.TempDir(), "uploadtest-*")) - - env = NewBuilder(nil) - ctx = env.OCMContext() - plugins, registry = Must2(ConfigureTestPlugins2(env, "testdata")) - p := registry.Get("test") - Expect(p).NotTo(BeNil()) - - ctx.ConfigContext().ApplyConfig(config.New(PLUGIN, []byte(fmt.Sprintf(`{"root": "`+repodir+`"}`))), "plugin config") - registration.RegisterExtensions(ctx) - - env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { - env.Component(COMP, func() { - env.Version(VERS, func() { - env.Provider(PROVIDER) - env.Resource("testdata", VERS, RSCTYPE, metav1.LocalRelation, func() { - env.Hint(HINT) - env.BlobStringData(MEDIA, CONTENT) - // env.Access(NewAccessSpec(MEDIA, "given", "dummy")) - }) - }) - }) - }) - }) - - AfterEach(func() { - plugins.Cleanup() - env.Cleanup() - os.RemoveAll(repodir) - }) - - It("uploads artifact", func() { - repo := Must(ctf.Open(ctx, accessobj.ACC_READONLY, ARCH, 0, env)) - defer Close(repo, "source repo") - - cv := Must(repo.LookupComponentVersion(COMP, VERS)) - defer Close(cv, "source version") - - _, _, err := plugin.RegisterBlobHandler(env.OCMContext(), "test", "", RSCTYPE, "", []byte("{}")) - fmt.Printf("error %q\n", err) - MustFailWithMessage(err, "plugin uploader test/testuploader: error processing plugin command upload: path missing in repository spec") - repospec := Must(json.Marshal(repoSpec)) - name, keys, err := plugin.RegisterBlobHandler(env.OCMContext(), "test", "", RSCTYPE, "", repospec) - MustBeSuccessful(err) - Expect(name).To(Equal("testuploader")) - Expect(keys).To(Equal(plugin2.UploaderKeySet{}.Add(plugin2.UploaderKey{}.SetArtifact(RSCTYPE, "")))) - - tgt := Must(ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) - defer Close(tgt, "target repo") - - MustBeSuccessful(transfer.TransferVersion(nil, nil, cv, tgt, Must(standard.New(standard.ResourcesByValue())))) - Expect(env.DirExists(OUT)).To(BeTrue()) - - Expect(vfs.FileExists(osfs.New(), filepath.Join(repodir, REPO, HINT))).To(BeTrue()) - - tcv := Must(tgt.LookupComponentVersion(COMP, VERS)) - defer Close(tcv, "target version") - - r := Must(tcv.GetResourceByIndex(0)) - a := Must(r.Access()) - - var spec AccessSpec - MustBeSuccessful(json.Unmarshal(Must(json.Marshal(a)), &spec)) - Expect(spec).To(Equal(*accessSpec)) - - m := Must(a.AccessMethod(tcv)) - defer Close(m, "method") - - Expect(string(Must(m.Get()))).To(Equal(CONTENT)) - }) - - It("uploads after abstract registration", func() { - repo := Must(ctf.Open(ctx, accessobj.ACC_READONLY, ARCH, 0, env)) - defer Close(repo, "source repo") - - cv := Must(repo.LookupComponentVersion(COMP, VERS)) - defer Close(cv, "source version") - - MustFailWithMessage(blobhandler.RegisterHandlerByName(ctx, "plugin/test", []byte("{}"), blobhandler.ForArtifactType(RSCTYPE)), - "plugin uploader test/testuploader: error processing plugin command upload: path missing in repository spec") - repospec := Must(json.Marshal(repoSpec)) - MustBeSuccessful(blobhandler.RegisterHandlerByName(ctx, "plugin/test", repospec)) - - tgt := Must(ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) - defer Close(tgt, "target repo") - - MustBeSuccessful(transfer.TransferVersion(nil, nil, cv, tgt, Must(standard.New(standard.ResourcesByValue())))) - Expect(env.DirExists(OUT)).To(BeTrue()) - - Expect(vfs.FileExists(osfs.New(), filepath.Join(repodir, REPO, HINT))).To(BeTrue()) - - tcv := Must(tgt.LookupComponentVersion(COMP, VERS)) - defer Close(tcv, "target version") - - r := Must(tcv.GetResourceByIndex(0)) - a := Must(r.Access()) - - var spec AccessSpec - MustBeSuccessful(json.Unmarshal(Must(json.Marshal(a)), &spec)) - Expect(spec).To(Equal(*accessSpec)) - - m := Must(a.AccessMethod(tcv)) - defer Close(m, "method") - - Expect(string(Must(m.Get()))).To(Equal(CONTENT)) - }) -}) diff --git a/pkg/contexts/ocm/blobhandler/handlers/init.go b/pkg/contexts/ocm/blobhandler/handlers/init.go deleted file mode 100644 index 3fd4df281..000000000 --- a/pkg/contexts/ocm/blobhandler/handlers/init.go +++ /dev/null @@ -1,9 +0,0 @@ -package handlers - -import ( - _ "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" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/ocm/comparch" -) diff --git a/pkg/contexts/ocm/blobhandler/handlers/oci/ctx.go b/pkg/contexts/ocm/blobhandler/handlers/oci/ctx.go deleted file mode 100644 index 44c5a7d1b..000000000 --- a/pkg/contexts/ocm/blobhandler/handlers/oci/ctx.go +++ /dev/null @@ -1,76 +0,0 @@ -package oci - -import ( - "reflect" - - ociv1 "github.com/opencontainers/image-spec/specs-go/v1" - - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - ocmcpi "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/genericocireg/componentmapping" -) - -// StorageContext is the context information passed for Blobhandler -// registered for context type oci.CONTEXT_TYPE. -type StorageContext struct { - ocmcpi.DefaultStorageContext - Repository cpi.Repository - Namespace cpi.NamespaceAccess - Manifest cpi.ManifestAccess -} - -var _ ocmcpi.StorageContext = (*StorageContext)(nil) - -func New(compname string, repo ocmcpi.Repository, impltyp string, ocirepo oci.Repository, namespace oci.NamespaceAccess, manifest oci.ManifestAccess) *StorageContext { - return &StorageContext{ - DefaultStorageContext: *ocmcpi.NewDefaultStorageContext( - repo, - compname, - ocmcpi.ImplementationRepositoryType{ - ContextType: cpi.CONTEXT_TYPE, - RepositoryType: impltyp, - }, - ), - Repository: ocirepo, - Namespace: namespace, - Manifest: manifest, - } -} - -func (s *StorageContext) TargetComponentRepository() ocmcpi.Repository { - return s.ComponentRepository -} - -func (s *StorageContext) TargetComponentName() string { - return s.ComponentName -} - -func (s *StorageContext) AssureLayer(blob cpi.BlobAccess) error { - return AssureLayer(s.Manifest.GetDescriptor(), blob) -} - -func AssureLayer(desc *artdesc.Manifest, blob cpi.BlobAccess) error { - d := artdesc.DefaultBlobDescriptor(blob) - - found := -1 - for i, l := range desc.Layers { - if reflect.DeepEqual(&desc.Layers[i], d) { - return nil - } - if l.Digest == blob.Digest() { - found = i - } - } - if found > 0 { // ignore layer 0 used for component descriptor - desc.Layers[found] = *d - } else { - if len(desc.Layers) == 0 { - // fake descriptor layer - desc.Layers = append(desc.Layers, ociv1.Descriptor{MediaType: componentmapping.ComponentDescriptorConfigMimeType}) - } - desc.Layers = append(desc.Layers, *d) - } - return nil -} diff --git a/pkg/contexts/ocm/blobhandler/handlers/oci/ocirepo/blobhandler.go b/pkg/contexts/ocm/blobhandler/handlers/oci/ocirepo/blobhandler.go deleted file mode 100644 index 3f81c334d..000000000 --- a/pkg/contexts/ocm/blobhandler/handlers/oci/ocirepo/blobhandler.go +++ /dev/null @@ -1,347 +0,0 @@ -package ocirepo - -import ( - "fmt" - "path" - "strings" - - . "github.com/mandelsoft/goutils/finalizer" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/sliceutils" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common/accessobj" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/grammar" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/contexts/oci/transfer" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localociblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/compatattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/keepblobattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/mapocirepoattr" - storagecontext "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/oci" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" -) - -func init() { - for _, mime := range artdesc.ArchiveBlobTypes() { - cpi.RegisterBlobHandler(NewArtifactHandler(OCIRegBaseFunction), cpi.ForRepo(oci.CONTEXT_TYPE, ocireg.Type), - cpi.ForMimeType(mime)) - cpi.RegisterBlobHandler(NewArtifactHandler(OCIRegBaseFunction), cpi.ForRepo(oci.CONTEXT_TYPE, ocireg.LegacyType), - cpi.ForMimeType(mime)) - cpi.RegisterBlobHandler(NewArtifactHandler(OCIRegBaseFunction), cpi.ForRepo(oci.CONTEXT_TYPE, ocireg.ShortType), - cpi.ForMimeType(mime)) - } - /* - cpi.RegisterBlobHandler(NewBlobHandler(OCIRegBaseFunction), cpi.ForRepo(oci.CONTEXT_TYPE, ocireg.Type)) - cpi.RegisterBlobHandler(NewBlobHandler(OCIRegBaseFunction), cpi.ForRepo(oci.CONTEXT_TYPE, ocireg.LegacyType)) - cpi.RegisterBlobHandler(NewBlobHandler(OCIRegBaseFunction), cpi.ForRepo(oci.CONTEXT_TYPE, ocireg.ShortType)) - */ -} - -//////////////////////////////////////////////////////////////////////////////// - -type BaseFunction func(ctx *storagecontext.StorageContext) string - -func OCIRegBaseFunction(ctx *storagecontext.StorageContext) string { - i, err := ocireg.GetRepositoryImplementation(ctx.Repository) - if err != nil { - panic("ocireg implementation mismatch") - } - return i.GetBaseURL() -} - -// blobHandler is the default handling to store local blobs as local blobs but with an additional -// globally accessible OCIBlob access method. -type blobHandler struct { - base BaseFunction -} - -func (h *blobHandler) GetBaseURL(ctx *storagecontext.StorageContext) string { - if h.base == nil { - return "" - } - return h.base(ctx) -} - -func NewBlobHandler(base BaseFunction) cpi.BlobHandler { - return &blobHandler{base} -} - -func (b *blobHandler) StoreBlob(blob cpi.BlobAccess, artType, hint string, global cpi.AccessSpec, ctx cpi.StorageContext) (cpi.AccessSpec, error) { - ocictx, ok := ctx.(*storagecontext.StorageContext) - if !ok { - return nil, fmt.Errorf("failed to assert type %T to storagecontext.StorageContext", ctx) - } - - values := []interface{}{ - "arttype", artType, - "mediatype", blob.MimeType(), - "hint", hint, - } - if m, ok := blob.(blobaccess.AnnotatedBlobAccess[accspeccpi.AccessMethodView]); ok { - cpi.BlobHandlerLogger(ctx.GetContext()).Debug("oci blob handler with ocm access source", - sliceutils.CopyAppend[any](values, "sourcetype", m.Source().AccessSpec().GetType())..., - ) - } else { - cpi.BlobHandlerLogger(ctx.GetContext()).Debug("oci blob handler", values...) - } - - err := ocictx.Manifest.AddBlob(blob) - if err != nil { - return nil, err - } - err = ocictx.AssureLayer(blob) - if err != nil { - return nil, err - } - if compatattr.Get(ctx.GetContext()) { - return localociblob.New(blob.Digest()), nil - } else { - if global == nil { - base := b.GetBaseURL(ocictx) - if base != "" { - global = ociblob.New(path.Join(base, ocictx.Namespace.GetNamespace()), blob.Digest(), blob.MimeType(), blob.Size()) - } - } - return localblob.New(blob.Digest().String(), "", blob.MimeType(), global), nil - } -} - -//////////////////////////////////////////////////////////////////////////////// - -// artifactHandler stores artifact blobs as OCIArtifacts. -type artifactHandler struct { - blobHandler -} - -func NewArtifactHandler(base BaseFunction) cpi.BlobHandler { - return &artifactHandler{blobHandler{base}} -} - -func (b *artifactHandler) CheckBlob(blob cpi.BlobAccess, artType, hint string, global cpi.AccessSpec, ctx cpi.StorageContext) (bool, bool, error) { - mediaType := blob.MimeType() - - if !artdesc.IsOCIMediaType(mediaType) || (!strings.HasSuffix(mediaType, "+tar") && !strings.HasSuffix(mediaType, "+tar+gzip")) { - return false, false, nil - } - - log := cpi.BlobHandlerLogger(ctx.GetContext()) - - values := []interface{}{ - "arttype", artType, - "mediatype", mediaType, - "hint", hint, - } - - var art oci.ArtifactAccess - var err error - var finalizer Finalizer - defer finalizer.Finalize() - - var namespace oci.NamespaceAccess - var version string - var name string - var tag string - - ocictx, ok := ctx.(*storagecontext.StorageContext) - if !ok { - return false, false, fmt.Errorf("failed to assert type %T to storagecontext.StorageContext", ctx) - } - if hint == "" { - namespace = ocictx.Namespace - } else { - prefix := cpi.RepositoryPrefix(ctx.TargetComponentRepository().GetSpecification()) - i := strings.LastIndex(hint, "@") - if i >= 0 { - hint = hint[:i] // remove digest - } - i = strings.LastIndex(hint, ":") - if i > 0 { - version = hint[i:] - tag = version[1:] // remove colon - name = hint[:i] - } else { - name = hint - } - - hash := mapocirepoattr.Get(ctx.GetContext()) - if hash.Prefix != nil { - prefix = *hash.Prefix - } - orig := name - mapped := hash.Map(name) - name = path.Join(prefix, mapped) - if mapped == orig { - log.Debug("namespace derived from hint", - sliceutils.CopyAppend[any](values, "namespace", name), - ) - } else { - log.Debug("mapped namespace derived from hint", - sliceutils.CopyAppend[any](values, "namespace", name), - ) - } - - namespace, err = ocictx.Repository.LookupNamespace(name) - if err != nil { - return false, false, err - } - defer namespace.Close() - } - - ok, err = namespace.HasArtifact(string(art.Digest())) - if ok { - return true, true, err - } - ok, err = namespace.HasArtifact(tag) - return ok, true, err -} - -func (b *artifactHandler) StoreBlob(blob cpi.BlobAccess, artType, hint string, global cpi.AccessSpec, ctx cpi.StorageContext) (cpi.AccessSpec, error) { - mediaType := blob.MimeType() - - if !artdesc.IsOCIMediaType(mediaType) || (!strings.HasSuffix(mediaType, "+tar") && !strings.HasSuffix(mediaType, "+tar+gzip")) { - return nil, nil - } - - errhint := "[" + hint + "]" - log := cpi.BlobHandlerLogger(ctx.GetContext()) - - values := []interface{}{ - "arttype", artType, - "mediatype", mediaType, - "hint", hint, - } - - var art oci.ArtifactAccess - var err error - var finalizer Finalizer - defer finalizer.Finalize() - - keep := keepblobattr.Get(ctx.GetContext()) - - if m, ok := blob.(blobaccess.AnnotatedBlobAccess[accspeccpi.AccessMethodView]); ok { - // prepare for optimized point to point implementation - log.Debug("oci artifact handler with ocm access source", - sliceutils.CopyAppend[any](values, "sourcetype", m.Source().AccessSpec().GetType())..., - ) - if ocimeth, ok := m.Source().Unwrap().(ociartifact.AccessMethodImpl); !keep && ok { - art, _, err = ocimeth.GetArtifact() - if err != nil { - return nil, errors.Wrapf(err, "cannot access source artifact") - } - if art != nil { - defer art.Close() - } - } - } else { - log.Debug("oci artifact handler", values...) - } - - var namespace oci.NamespaceAccess - var version string - var name string - var tag string - var digest digest.Digest - - ocictx, ok := ctx.(*storagecontext.StorageContext) - if !ok { - return nil, fmt.Errorf("failed to assert type %T to storagecontext.StorageContext", ctx) - } - base := b.GetBaseURL(ocictx) - if hint == "" { - namespace = ocictx.Namespace - } else { - prefix := cpi.RepositoryPrefix(ctx.TargetComponentRepository().GetSpecification()) - i := strings.LastIndex(hint, "@") - if i >= 0 { - hint = hint[:i] // remove digest - } - i = strings.LastIndex(hint, ":") - if i > 0 { - version = hint[i:] - tag = version[1:] // remove colon - name = hint[:i] - } else { - name = hint - } - - hash := mapocirepoattr.Get(ctx.GetContext()) - if hash.Prefix != nil { - prefix = *hash.Prefix - } - orig := name - mapped := hash.Map(name) - name = path.Join(prefix, mapped) - if mapped == orig { - log.Debug("namespace derived from hint", - sliceutils.CopyAppend[any](values, "namespace", name), - ) - } else { - log.Debug("mapped namespace derived from hint", - sliceutils.CopyAppend[any](values, "namespace", name), - ) - } - - namespace, err = ocictx.Repository.LookupNamespace(name) - if err != nil { - return nil, err - } - defer namespace.Close() - } - - errhint += " namespace " + namespace.GetNamespace() - - if art == nil { - log.Debug("using artifact set transfer mode") - set, err := artifactset.OpenFromBlob(accessobj.ACC_READONLY, blob) - if err != nil { - return nil, wrap(err, errhint, "open blob") - } - defer set.Close() - digest = set.GetMain() - art, err = set.GetArtifact(digest.String()) - if err != nil { - return nil, wrap(err, errhint, "get artifact from blob") - } - defer art.Close() - } else { - log.Debug("using direct transfer mode") - digest = art.Digest() - } - - if version == "" { - version = "@" + digest.String() - } - - err = transfer.TransferArtifact(art, namespace, oci.AsTags(tag)...) - if err != nil { - return nil, wrap(err, errhint, "transfer artifact") - } - match := grammar.AnchoredSchemedRegexp.FindStringSubmatch(base) - scheme := "" - if match != nil { - scheme = match[1] - base = match[2] - } - if scheme != "" { - scheme += "://" - } - ref := scheme + path.Join(base, namespace.GetNamespace()) + version - return ociartifact.New(ref), nil -} - -func wrap(err error, msg string, args ...interface{}) error { - for _, a := range args { - msg = fmt.Sprintf("%s: %s", msg, a) - } - return errors.Wrapf(err, "exploding OCI artifact resource blob (%s)", msg) -} diff --git a/pkg/contexts/ocm/blobhandler/handlers/oci/ocirepo/handler_test.go b/pkg/contexts/ocm/blobhandler/handlers/oci/ocirepo/handler_test.go deleted file mode 100644 index 339b8bde1..000000000 --- a/pkg/contexts/ocm/blobhandler/handlers/oci/ocirepo/handler_test.go +++ /dev/null @@ -1,245 +0,0 @@ -package ocirepo_test - -import ( - "encoding/json" - "fmt" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/oci/testhelper" - . "github.com/open-component-model/ocm/pkg/env/builder" - - "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/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - ocictf "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/keepblobattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/mapocirepoattr" - storagecontext "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/oci" - "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/oci/ocirepo" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/genericocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" - "github.com/open-component-model/ocm/pkg/mime" -) - -const ( - ARCH = "/tmp/ctf" - ARCH2 = "/tmp/ctf2" - PROVIDER = "mandelsoft" - VERSION = "v1" - COMPONENT = "github.com/mandelsoft/test" - COMPONENT2 = "github.com/mandelsoft/test2" - OUT = "/tmp/res" - OCIPATH = "/tmp/oci" - OCIHOST = "alias" -) - -func FakeOCIRegBaseFunction(ctx *storagecontext.StorageContext) string { - return "baseurl.io" -} - -var _ = Describe("oci artifact transfer", func() { - var env *Builder - var ldesc *artdesc.Descriptor - - BeforeEach(func() { - env = NewBuilder() - - FakeOCIRepo(env, OCIPATH, OCIHOST) - - env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { - ldesc = OCIManifest1(env) - }) - - env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { - env.Component(COMPONENT, func() { - env.Version(VERSION, func() { - env.Provider(PROVIDER) - env.Resource("testdata", "", "PlainText", metav1.LocalRelation, func() { - env.BlobStringData(mime.MIME_TEXT, "testdata") - }) - env.Resource("artifact", "", resourcetypes.OCI_IMAGE, metav1.LocalRelation, func() { - env.Access( - ociartifact.New(oci.StandardOCIRef(OCIHOST+".alias", OCINAMESPACE, OCIVERSION)), - ) - }) - }) - }) - }) - - _ = ldesc - }) - - AfterEach(func() { - env.Cleanup() - }) - - It("it should copy a resource by value and export the OCI image but keep the local blob", func() { - env.OCMContext().BlobHandlers().Register(ocirepo.NewArtifactHandler(FakeOCIRegBaseFunction), - cpi.ForRepo(oci.CONTEXT_TYPE, ocictf.Type), cpi.ForMimeType(artdesc.ToContentMediaType(artdesc.MediaTypeImageManifest))) - keepblobattr.Set(env.OCMContext(), true) - - src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) - cv := Must(src.LookupComponentVersion(COMPONENT, VERSION)) - tgt := Must(ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) - defer tgt.Close() - - opts := &standard.Options{} - opts.SetResourcesByValue(true) - handler := standard.NewDefaultHandler(opts) - - MustBeSuccessful(transfer.TransferVersion(nil, nil, cv, tgt, handler)) - Expect(env.DirExists(OUT)).To(BeTrue()) - - list := Must(tgt.ComponentLister().GetComponents("", true)) - Expect(list).To(Equal([]string{COMPONENT})) - comp := Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) - Expect(len(comp.GetDescriptor().Resources)).To(Equal(2)) - data := Must(json.Marshal(comp.GetDescriptor().Resources[1].Access)) - - fmt.Printf("%s\n", string(data)) - Expect(string(data)).To(StringEqualWithContext(`{"globalAccess":{"imageReference":"baseurl.io/ocm/value:v2.0","type":"ociArtifact"},"localReference":"sha256:b0692bcec00e0a875b6b280f3209d6776f3eca128adcb7e81e82fd32127c0c62","mediaType":"application/vnd.oci.image.manifest.v1+tar+gzip","referenceName":"ocm/value:v2.0","type":"localBlob"}`)) - ocirepo := genericocireg.GetOCIRepository(tgt) - Expect(ocirepo).NotTo(BeNil()) - - art := Must(ocirepo.LookupArtifact(OCINAMESPACE, OCIVERSION)) - defer Close(art, "artifact") - - man := MustBeNonNil(art.ManifestAccess()) - Expect(len(man.GetDescriptor().Layers)).To(Equal(1)) - Expect(man.GetDescriptor().Layers[0].Digest).To(Equal(ldesc.Digest)) - - blob := Must(man.GetBlob(ldesc.Digest)) - data = Must(blob.Get()) - Expect(string(data)).To(Equal(OCILAYER)) - }) - - It("it should copy a resource by value and export the OCI image", func() { - env.OCMContext().BlobHandlers().Register(ocirepo.NewArtifactHandler(FakeOCIRegBaseFunction), - cpi.ForRepo(oci.CONTEXT_TYPE, ocictf.Type), cpi.ForMimeType(artdesc.ToContentMediaType(artdesc.MediaTypeImageManifest))) - - src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) - cv := Must(src.LookupComponentVersion(COMPONENT, VERSION)) - tgt := Must(ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) - defer tgt.Close() - - opts := &standard.Options{} - opts.SetResourcesByValue(true) - handler := standard.NewDefaultHandler(opts) - - MustBeSuccessful(transfer.TransferVersion(nil, nil, cv, tgt, handler)) - Expect(env.DirExists(OUT)).To(BeTrue()) - - list := Must(tgt.ComponentLister().GetComponents("", true)) - Expect(list).To(Equal([]string{COMPONENT})) - comp := Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) - Expect(len(comp.GetDescriptor().Resources)).To(Equal(2)) - data := Must(json.Marshal(comp.GetDescriptor().Resources[1].Access)) - - fmt.Printf("%s\n", string(data)) - Expect(string(data)).To(StringEqualWithContext("{\"imageReference\":\"baseurl.io/ocm/value:v2.0\",\"type\":\"ociArtifact\"}")) - - ocirepo := genericocireg.GetOCIRepository(tgt) - art := Must(ocirepo.LookupArtifact(OCINAMESPACE, OCIVERSION)) - defer Close(art, "artifact") - - man := MustBeNonNil(art.ManifestAccess()) - Expect(len(man.GetDescriptor().Layers)).To(Equal(1)) - Expect(man.GetDescriptor().Layers[0].Digest).To(Equal(ldesc.Digest)) - - blob := Must(man.GetBlob(ldesc.Digest)) - data = Must(blob.Get()) - Expect(string(data)).To(Equal(OCILAYER)) - }) - - It("it should copy a resource by value and export the OCI image with hashed repo name", func() { - env.OCMContext().BlobHandlers().Register(ocirepo.NewArtifactHandler(FakeOCIRegBaseFunction), - cpi.ForRepo(oci.CONTEXT_TYPE, ocictf.Type), cpi.ForMimeType(artdesc.ToContentMediaType(artdesc.MediaTypeImageManifest))) - - src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) - cv := Must(src.LookupComponentVersion(COMPONENT, VERSION)) - tgt := Must(ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) - defer tgt.Close() - - opts := &standard.Options{} - opts.SetResourcesByValue(true) - handler := standard.NewDefaultHandler(opts) - mapocirepoattr.Set(env.OCMContext(), &mapocirepoattr.Attribute{Mode: mapocirepoattr.ShortHashMode, Always: true}) - rdigest := "e9b6af2174cb2fb78b2882a1f487b01295b8f6bfa7e4c1ceb350440104c9ce65" - - MustBeSuccessful(transfer.TransferVersion(nil, nil, cv, tgt, handler)) - Expect(env.DirExists(OUT)).To(BeTrue()) - - list := Must(tgt.ComponentLister().GetComponents("", true)) - Expect(list).To(Equal([]string{COMPONENT})) - comp := Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) - Expect(len(comp.GetDescriptor().Resources)).To(Equal(2)) - data := Must(json.Marshal(comp.GetDescriptor().Resources[1].Access)) - - fmt.Printf("%s\n", string(data)) - Expect(string(data)).To(StringEqualWithContext("{\"imageReference\":\"baseurl.io/" + rdigest[:8] + "/value:v2.0\",\"type\":\"ociArtifact\"}")) - - namespace := rdigest[:8] + "/value" - ocirepo := genericocireg.GetOCIRepository(tgt) - art := Must(ocirepo.LookupArtifact(namespace, OCIVERSION)) - defer Close(art, "artifact") - - man := MustBeNonNil(art.ManifestAccess()) - Expect(len(man.GetDescriptor().Layers)).To(Equal(1)) - Expect(man.GetDescriptor().Layers[0].Digest).To(Equal(ldesc.Digest)) - - blob := Must(man.GetBlob(ldesc.Digest)) - data = Must(blob.Get()) - Expect(string(data)).To(Equal(OCILAYER)) - }) - - It("it should copy a resource by value and export the OCI image with hashed repo name and prefix", func() { - env.OCMContext().BlobHandlers().Register(ocirepo.NewArtifactHandler(FakeOCIRegBaseFunction), - cpi.ForRepo(oci.CONTEXT_TYPE, ocictf.Type), cpi.ForMimeType(artdesc.ToContentMediaType(artdesc.MediaTypeImageManifest))) - - src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) - cv := Must(src.LookupComponentVersion(COMPONENT, VERSION)) - tgt := Must(ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) - defer tgt.Close() - - opts := &standard.Options{} - opts.SetResourcesByValue(true) - handler := standard.NewDefaultHandler(opts) - prefix := "ocm" - mapocirepoattr.Set(env.OCMContext(), &mapocirepoattr.Attribute{Mode: mapocirepoattr.ShortHashMode, Always: true, Prefix: &prefix}) - rdigest := "e9b6af2174cb2fb78b2882a1f487b01295b8f6bfa7e4c1ceb350440104c9ce65" - - MustBeSuccessful(transfer.TransferVersion(nil, nil, cv, tgt, handler)) - Expect(env.DirExists(OUT)).To(BeTrue()) - - list := Must(tgt.ComponentLister().GetComponents("", true)) - Expect(list).To(Equal([]string{COMPONENT})) - comp := Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) - Expect(len(comp.GetDescriptor().Resources)).To(Equal(2)) - data := Must(json.Marshal(comp.GetDescriptor().Resources[1].Access)) - - fmt.Printf("%s\n", string(data)) - Expect(string(data)).To(StringEqualWithContext("{\"imageReference\":\"baseurl.io/ocm/" + rdigest[:8] + "/value:v2.0\",\"type\":\"ociArtifact\"}")) - - namespace := "ocm/" + rdigest[:8] + "/value" - ocirepo := genericocireg.GetOCIRepository(tgt) - art := Must(ocirepo.LookupArtifact(namespace, OCIVERSION)) - defer Close(art, "artifact") - - man := MustBeNonNil(art.ManifestAccess()) - Expect(len(man.GetDescriptor().Layers)).To(Equal(1)) - Expect(man.GetDescriptor().Layers[0].Digest).To(Equal(ldesc.Digest)) - - blob := Must(man.GetBlob(ldesc.Digest)) - data = Must(blob.Get()) - Expect(string(data)).To(Equal(OCILAYER)) - }) -}) diff --git a/pkg/contexts/ocm/blobhandler/handlers/ocm/comparch/blobhandler.go b/pkg/contexts/ocm/blobhandler/handlers/ocm/comparch/blobhandler.go deleted file mode 100644 index 69de1eb50..000000000 --- a/pkg/contexts/ocm/blobhandler/handlers/ocm/comparch/blobhandler.go +++ /dev/null @@ -1,50 +0,0 @@ -package comparch - -import ( - "fmt" - - "github.com/mandelsoft/goutils/errors" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/common" - "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/attrs/compatattr" - storagecontext "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/comparch" -) - -func init() { - cpi.RegisterBlobHandler(NewBlobHandler(), cpi.ForRepo(cpi.CONTEXT_TYPE, comparch.Type)) -} - -//////////////////////////////////////////////////////////////////////////////// - -// blobHandler is the default handling to store local blobs as local blobs. -type blobHandler struct{} - -func NewBlobHandler() cpi.BlobHandler { - return &blobHandler{} -} - -func (b *blobHandler) StoreBlob(blob cpi.BlobAccess, artType, hint string, global cpi.AccessSpec, ctx cpi.StorageContext) (cpi.AccessSpec, error) { - ocmctx, ok := ctx.(storagecontext.StorageContext) - if !ok { - return nil, fmt.Errorf("failed to assert type %T to storagecontext.StorageContext", ctx) - } - - if blob == nil { - return nil, errors.New("a resource has to be defined") - } - ref, err := ocmctx.AddBlob(blob) - if err != nil { - return nil, err - } - path := common.DigestToFileName(digest.Digest(ref)) - if compatattr.Get(ctx.GetContext()) { - return localfsblob.New(path, blob.MimeType()), nil - } else { - return localblob.New(path, hint, blob.MimeType(), global), nil - } -} diff --git a/pkg/contexts/ocm/blobhandler/handlers/ocm/comparch/blobhandler_test.go b/pkg/contexts/ocm/blobhandler/handlers/ocm/comparch/blobhandler_test.go deleted file mode 100644 index a86ea2710..000000000 --- a/pkg/contexts/ocm/blobhandler/handlers/ocm/comparch/blobhandler_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package comparch_test - -import ( - "encoding/json" - "reflect" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/common/accessio" - "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/attrs/compatattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "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" -) - -const ARCHIVE = "archive" - -var _ = Describe("blobhandler", func() { - Context("regular", func() { - var b *builder.Builder - - BeforeEach(func() { - b = builder.NewBuilder() - }) - - AfterEach(func() { - b.Cleanup() - }) - - It("uses generic local access", func() { - b.ComponentArchive(ARCHIVE, accessio.FormatDirectory, "github.com/mandelsoft/test", "1.0.0", func() { - b.Resource("test", "1.0.0", "Test", v1.LocalRelation, func() { - b.BlobStringData(mime.MIME_TEXT, "testdata") - }) - }) - data := Must(b.ReadFile(vfs.Join(b, ARCHIVE, compdesc.ComponentDescriptorFileName))) - cd := Must(compdesc.Decode(data)) - Expect(cd.Resources[0].Access.GetType()).To(Equal(localblob.Type)) - - data = Must(json.Marshal(cd.Resources[0].Access)) - found := Must(localblob.Decode(data)) - spec := localblob.New("sha256.810ff2fb242a5dee4220f2cb0e6a519891fb67f2f828a6cab4ef8894633b1f50", "", mime.MIME_TEXT, nil) - Expect(found).To(Equal(spec)) - }) - }) - Context("legacy", func() { - var b *builder.Builder - BeforeEach(func() { - b = builder.NewBuilder(env.NewEnvironment()) - Expect(b.ConfigContext().GetAttributes().SetAttribute(compatattr.ATTR_KEY, true)).To(Succeed()) - }) - AfterEach(func() { - b.Cleanup() - }) - It("uses generic local access", func() { - b.ComponentArchive(ARCHIVE, accessio.FormatDirectory, "github.com/mandelsoft/test", "1.0.0", func() { - b.Resource("test", "1.0.0", "Test", v1.LocalRelation, func() { - b.BlobStringData(mime.MIME_TEXT, "testdata") - }) - }) - data := Must(b.ReadFile(vfs.Join(b, ARCHIVE, compdesc.ComponentDescriptorFileName))) - cd := Must(compdesc.Decode(data)) - Expect(cd.Resources[0].Access.GetType()).To(Equal(localfsblob.Type)) - - data = Must(json.Marshal(cd.Resources[0].Access)) - found := Must(localfsblob.Decode(data)) - - spec := localfsblob.New("sha256.810ff2fb242a5dee4220f2cb0e6a519891fb67f2f828a6cab4ef8894633b1f50", mime.MIME_TEXT) - reflect.DeepEqual(found, spec) - Expect(found).To(Equal(spec)) - }) - }) -}) diff --git a/pkg/contexts/ocm/blobhandler/handlers/ocm/ctx.go b/pkg/contexts/ocm/blobhandler/handlers/ocm/ctx.go deleted file mode 100644 index 8bad848bf..000000000 --- a/pkg/contexts/ocm/blobhandler/handlers/ocm/ctx.go +++ /dev/null @@ -1,36 +0,0 @@ -package ocm - -import ( - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/utils" -) - -type BlobSink interface { - AddBlob(blob blobaccess.BlobAccess) (string, error) -} - -// StorageContext is the context information passed for Blobhandler -// registered for context type oci.CONTEXT_TYPE. -type StorageContext interface { - cpi.StorageContext - BlobSink -} - -type DefaultStorageContext struct { - cpi.DefaultStorageContext - Sink BlobSink - Payload interface{} -} - -func New(repo cpi.Repository, compname string, access BlobSink, impltyp string, payload ...interface{}) StorageContext { - return &DefaultStorageContext{ - DefaultStorageContext: *cpi.NewDefaultStorageContext(repo, compname, cpi.ImplementationRepositoryType{cpi.CONTEXT_TYPE, impltyp}), - Sink: access, - Payload: utils.Optional(payload...), - } -} - -func (c *DefaultStorageContext) AddBlob(blob blobaccess.BlobAccess) (string, error) { - return c.Sink.AddBlob(blob) -} diff --git a/pkg/contexts/ocm/blobhandler/interface.go b/pkg/contexts/ocm/blobhandler/interface.go deleted file mode 100644 index dc3694dc2..000000000 --- a/pkg/contexts/ocm/blobhandler/interface.go +++ /dev/null @@ -1,17 +0,0 @@ -package blobhandler - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" -) - -type ( - HandlerConfig = cpi.BlobHandlerConfig - HandlerOption = cpi.BlobHandlerOption - HandlerOptions = cpi.BlobHandlerOptions - HandlerRegistry = cpi.BlobHandlerRegistry - HandlerKey = cpi.BlobHandlerKey -) - -func For(ctx cpi.ContextProvider) cpi.BlobHandlerRegistry { - return ctx.OCMContext().BlobHandlers() -} diff --git a/pkg/contexts/ocm/blobhandler/registration.go b/pkg/contexts/ocm/blobhandler/registration.go deleted file mode 100644 index ca354dd69..000000000 --- a/pkg/contexts/ocm/blobhandler/registration.go +++ /dev/null @@ -1,34 +0,0 @@ -package blobhandler - -import ( - "fmt" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" -) - -func RegisterHandlerByName(ctx cpi.ContextProvider, name string, config HandlerConfig, opts ...HandlerOption) error { - o, err := For(ctx).RegisterByName(name, ctx.OCMContext(), config, opts...) - if err != nil { - return err - } - if !o { - return fmt.Errorf("no matching handler found for %q", name) - } - return nil -} - -func WithPrio(prio int) HandlerOption { - return cpi.WithPrio(prio) -} - -func ForArtifactType(t string) HandlerOption { - return cpi.ForArtifactType(t) -} - -func ForMimeType(t string) HandlerOption { - return cpi.ForMimeType(t) -} - -func ForRepo(ctxtype string, repotype string) HandlerOption { - return cpi.ForRepo(ctxtype, repotype) -} diff --git a/pkg/contexts/ocm/builder.go b/pkg/contexts/ocm/builder.go deleted file mode 100644 index 6a80b8c0b..000000000 --- a/pkg/contexts/ocm/builder.go +++ /dev/null @@ -1,50 +0,0 @@ -package ocm - -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/compdesc/accessors.go b/pkg/contexts/ocm/compdesc/accessors.go deleted file mode 100644 index 51215f722..000000000 --- a/pkg/contexts/ocm/compdesc/accessors.go +++ /dev/null @@ -1,84 +0,0 @@ -package compdesc - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/equivalent" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/accessors" -) - -// NameAccessor describes a accessor for a named object. -type NameAccessor interface { - // GetName returns the name of the object. - GetName() string - // SetName sets the name of the object. - SetName(name string) -} - -// VersionAccessor describes a accessor for a versioned object. -type VersionAccessor interface { - // GetVersion returns the version of the object. - GetVersion() string - // SetVersion sets the version of the object. - SetVersion(version string) -} - -// LabelsAccessor describes a accessor for a labeled object. -type LabelsAccessor interface { - // GetLabels returns the labels of the object. - GetLabels() metav1.Labels - // SetLabels sets the labels of the object. - SetLabels(labels []metav1.Label) -} - -// ObjectMetaAccessor describes a accessor for named and versioned object. -type ObjectMetaAccessor interface { - NameAccessor - VersionAccessor - LabelsAccessor -} - -// ElementMetaAccessor provides generic access an elements meta information. -type ElementMetaAccessor interface { - ElementMetaProvider - Equivalent(ElementMetaAccessor) equivalent.EqualState -} - -// ElementAccessor provides generic access to list of elements. -type ElementAccessor interface { - Len() int - Get(i int) ElementMetaAccessor -} - -type ElementMetaProvider interface { - GetMeta() *ElementMeta -} - -// ElementArtifactAccessor provides access to generic artifact information of an element. -type ElementArtifactAccessor interface { - ElementMetaAccessor - GetType() string - GetAccess() AccessSpec - SetAccess(a AccessSpec) -} - -type ElementDigestAccessor interface { - GetDigest() *metav1.DigestSpec - SetDigest(*metav1.DigestSpec) -} - -// ArtifactAccessor provides generic access to list of artifacts. -// There are resources or sources. -type ArtifactAccessor interface { - ElementAccessor - GetArtifact(i int) ElementArtifactAccessor -} - -// AccessSpec is an abstract specification of an access method -// The outbound object is typicall a runtime.UnstructuredTypedObject. -// Inbound any serializable AccessSpec implementation is possible. -type AccessSpec = accessors.AccessSpec - -// AccessProvider provides access to an access specification of elements. -type AccessProvider interface { - GetAccess() AccessSpec -} diff --git a/pkg/contexts/ocm/compdesc/componentdescriptor.go b/pkg/contexts/ocm/compdesc/componentdescriptor.go deleted file mode 100644 index e4b1d592f..000000000 --- a/pkg/contexts/ocm/compdesc/componentdescriptor.go +++ /dev/null @@ -1,844 +0,0 @@ -package compdesc - -import ( - "fmt" - "reflect" - "strings" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/equivalent" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/accessors" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/semverutils" -) - -const InternalSchemaVersion = "internal" - -var NotFound = errors.ErrNotFound() - -const KIND_REFERENCE = "component reference" - -const ComponentDescriptorFileName = "component-descriptor.yaml" - -// Metadata defines the configured metadata of the component descriptor. -// It is taken from the original serialization format. It can be set -// to define a default serialization version. -type Metadata struct { - ConfiguredVersion string `json:"configuredSchemaVersion"` -} - -// ComponentDescriptor defines a versioned component with a source and dependencies. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type ComponentDescriptor struct { - // Metadata specifies the schema version of the component. - Metadata Metadata `json:"meta"` - // Spec contains the specification of the component. - ComponentSpec `json:"component"` - // Signatures contains a list of signatures for the ComponentDescriptor - Signatures metav1.Signatures `json:"signatures,omitempty"` - - // NestedDigets describe calculated resource digests for aggregated - // component versions, which might not be persisted, but incorporated - // into signatures of the actual component version - NestedDigests metav1.NestedDigests `json:"nestedDigests,omitempty"` -} - -func New(name, version string) *ComponentDescriptor { - return DefaultComponent(&ComponentDescriptor{ - Metadata: Metadata{ - ConfiguredVersion: "v2", - }, - ComponentSpec: ComponentSpec{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Version: version, - Provider: metav1.Provider{ - Name: "acme", - }, - }, - }, - }) -} - -// SchemaVersion returns the scheme version configured in the representation. -func (cd *ComponentDescriptor) SchemaVersion() string { - return cd.Metadata.ConfiguredVersion -} - -func (cd *ComponentDescriptor) Copy() *ComponentDescriptor { - out := &ComponentDescriptor{ - Metadata: cd.Metadata, - ComponentSpec: ComponentSpec{ - ObjectMeta: *cd.ObjectMeta.Copy(), - RepositoryContexts: cd.RepositoryContexts.Copy(), - Sources: cd.Sources.Copy(), - References: cd.References.Copy(), - Resources: cd.Resources.Copy(), - }, - Signatures: cd.Signatures.Copy(), - NestedDigests: cd.NestedDigests.Copy(), - } - return out -} - -func (cd *ComponentDescriptor) Reset() { - cd.Provider.Name = "" - cd.Provider.Labels = nil - cd.Resources = nil - cd.Sources = nil - cd.References = nil - cd.RepositoryContexts = nil - cd.Signatures = nil - cd.Labels = nil - DefaultComponent(cd) -} - -// ComponentSpec defines a virtual component with -// a repository context, source and dependencies. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type ComponentSpec struct { - metav1.ObjectMeta `json:",inline"` - // RepositoryContexts defines the previous repositories of the component - RepositoryContexts runtime.UnstructuredTypedObjectList `json:"repositoryContexts"` - // Sources defines sources that produced the component - Sources Sources `json:"sources"` - // References references component dependencies that can be resolved in the current context. - References References `json:"componentReferences"` - // Resources defines all resources that are created by the component and by a third party. - Resources Resources `json:"resources"` -} - -const ( - SystemIdentityName = metav1.SystemIdentityName - SystemIdentityVersion = metav1.SystemIdentityVersion -) - -type ElementMetaAccess interface { - GetName() string - GetVersion() string - GetIdentity(accessor ElementAccessor) metav1.Identity - GetLabels() metav1.Labels -} - -type ArtifactMetaAccess interface { - ElementMetaAccess - GetType() string - SetType(string) -} - -// ArtifactMetaPointer is a pointer to an artifact meta object. -type ArtifactMetaPointer[P any] interface { - ArtifactMetaAccess - *P -} - -// ElementMeta defines a object that is uniquely identified by its identity. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type ElementMeta struct { - // Name is the context unique name of the object. - Name string `json:"name"` - // Version is the semver version of the object. - Version string `json:"version"` - // ExtraIdentity is the identity of an object. - // An additional label with key "name" ist not allowed - ExtraIdentity metav1.Identity `json:"extraIdentity,omitempty"` - // Labels defines an optional set of additional labels - // describing the object. - // +optional - Labels metav1.Labels `json:"labels,omitempty"` -} - -// GetName returns the name of the object. -func (o *ElementMeta) GetName() string { - return o.Name -} - -// GetMeta returns the element meta. -func (r *ElementMeta) GetMeta() *ElementMeta { - return r -} - -// GetExtraIdentity returns the extra identity of the object. -func (o *ElementMeta) GetExtraIdentity() metav1.Identity { - if o.ExtraIdentity == nil { - return metav1.Identity{} - } - return o.ExtraIdentity.Copy() -} - -// SetName sets the name of the object. -func (o *ElementMeta) SetName(name string) { - o.Name = name -} - -// GetVersion returns the version of the object. -func (o *ElementMeta) GetVersion() string { - return o.Version -} - -// SetVersion sets the version of the object. -func (o *ElementMeta) SetVersion(version string) { - o.Version = version -} - -// GetLabels returns the label of the object. -func (o *ElementMeta) GetLabels() metav1.Labels { - return o.Labels -} - -// SetLabels sets the labels of the object. -func (o *ElementMeta) SetLabels(labels []metav1.Label) { - o.Labels = labels -} - -// SetLabel sets a single label to an effective value. -// If the value is no byte slice, it is marshaled. -func (o *ElementMeta) SetLabel(name string, value interface{}, opts ...metav1.LabelOption) error { - return o.Labels.Set(name, value, opts...) -} - -// RemoveLabel removes a single label. -func (o *ElementMeta) RemoveLabel(name string) bool { - return o.Labels.Remove(name) -} - -// SetExtraIdentity sets the identity of the object. -func (o *ElementMeta) SetExtraIdentity(identity metav1.Identity) { - o.ExtraIdentity = identity -} - -func (o *ElementMeta) AddExtraIdentity(identity metav1.Identity) { - if o.ExtraIdentity == nil { - o.ExtraIdentity = identity - } else { - o.ExtraIdentity = o.ExtraIdentity.Copy() - for k, v := range identity { - o.ExtraIdentity[k] = v - } - } -} - -// GetIdentity returns the identity of the object. -func (o *ElementMeta) GetIdentity(accessor ElementAccessor) metav1.Identity { - identity := o.ExtraIdentity.Copy() - if identity == nil { - identity = metav1.Identity{} - } - identity[SystemIdentityName] = o.Name - if accessor != nil { - found := false - l := accessor.Len() - for i := 0; i < l; i++ { - m := accessor.Get(i).GetMeta() - if m.Name == o.Name && m.ExtraIdentity.Equals(o.ExtraIdentity) { - if found { - identity[SystemIdentityVersion] = o.Version - - break - } - found = true - } - } - } - return identity -} - -// GetIdentityForContext returns the identity of the object. -func (o *ElementMeta) GetIdentityForContext(accessor accessors.ElementListAccessor) metav1.Identity { - identity := o.ExtraIdentity.Copy() - if identity == nil { - identity = metav1.Identity{} - } - identity[SystemIdentityName] = o.Name - if accessor != nil { - found := false - l := accessor.Len() - for i := 0; i < l; i++ { - m := accessor.Get(i).GetMeta() - if m.GetName() == o.GetName() && m.GetExtraIdentity().Equals(o.ExtraIdentity) { - if found { - identity[SystemIdentityVersion] = o.Version - - break - } - found = true - } - } - } - return identity -} - -// GetRawIdentity returns the identity plus version. -func (o *ElementMeta) GetRawIdentity() metav1.Identity { - identity := o.ExtraIdentity.Copy() - if identity == nil { - identity = metav1.Identity{} - } - identity[SystemIdentityName] = o.Name - if o.Version != "" { - identity[SystemIdentityVersion] = o.Version - } - return identity -} - -// GetMatchBaseIdentity returns all possible identity attributes for resource matching. -func (o *ElementMeta) GetMatchBaseIdentity() metav1.Identity { - identity := o.ExtraIdentity.Copy() - if identity == nil { - identity = metav1.Identity{} - } - identity[SystemIdentityName] = o.Name - identity[SystemIdentityVersion] = o.Version - - return identity -} - -// GetIdentityDigest returns the digest of the object's identity. -func (o *ElementMeta) GetIdentityDigest(accessor ElementAccessor) []byte { - return o.GetIdentity(accessor).Digest() -} - -func (o *ElementMeta) Copy() *ElementMeta { - if o == nil { - return nil - } - return &ElementMeta{ - Name: o.Name, - Version: o.Version, - ExtraIdentity: o.ExtraIdentity.Copy(), - Labels: o.Labels.Copy(), - } -} - -func (o *ElementMeta) Equivalent(a *ElementMeta) equivalent.EqualState { - if o == a { - return equivalent.StateEquivalent() - } - if o == nil { - o, a = a, o - } - if a == nil { - return o.Labels.Equivalent(nil) - } - - state := equivalent.StateLocalHashEqual(a.Name == o.Name && a.Version == o.Version && reflect.DeepEqual(a.ExtraIdentity, o.ExtraIdentity)) - return state.Apply(o.Labels.Equivalent(a.Labels)) -} - -func GetByIdentity(a ElementAccessor, id metav1.Identity) ElementMetaAccessor { - l := a.Len() - for i := 0; i < l; i++ { - e := a.Get(i) - if e.GetMeta().GetIdentity(a).Equals(id) { - return e - } - } - return nil -} - -func GetIndexByIdentity(a ElementAccessor, id metav1.Identity) int { - l := a.Len() - for i := 0; i < l; i++ { - e := a.Get(i) - if e.GetMeta().GetIdentity(a).Equals(id) { - return i - } - } - return -1 -} - -// ArtifactAccess provides access to a dedicated kind of artifact set -// in the component descriptor (resources or sources). -type ArtifactAccess func(cd *ComponentDescriptor) ArtifactAccessor - -// GenericAccessSpec returns a generic AccessSpec implementation for an unstructured object. -// It can always be used instead of a dedicated access spec implementation. The core -// methods will map these spec into effective ones before an access is returned to the caller. -func GenericAccessSpec(un *runtime.UnstructuredTypedObject) AccessSpec { - return &runtime.UnstructuredVersionedTypedObject{ - *un.DeepCopy(), - } -} - -// Sources describes a set of source specifications. -type Sources []Source - -var _ ElementAccessor = Sources{} - -func SourceArtifacts(cd *ComponentDescriptor) ArtifactAccessor { - return cd.Sources -} - -func (r Sources) Equivalent(o Sources) equivalent.EqualState { - return EquivalentElems(r, o) -} - -func (s Sources) Len() int { - return len(s) -} - -func (s Sources) Get(i int) ElementMetaAccessor { - return &s[i] -} - -func (s Sources) GetArtifact(i int) ElementArtifactAccessor { - return &s[i] -} - -func (s Sources) Copy() Sources { - if s == nil { - return nil - } - out := make(Sources, len(s)) - for i, v := range s { - out[i] = *v.Copy() - } - return out -} - -// Source is the definition of a component's source. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type Source struct { - SourceMeta `json:",inline"` - Access AccessSpec `json:"access"` -} - -func (s *Source) GetAccess() AccessSpec { - return s.Access -} - -func (r *Source) SetAccess(a AccessSpec) { - r.Access = a -} - -func (r *Source) Equivalent(e ElementMetaAccessor) equivalent.EqualState { - if o, ok := e.(*Source); !ok { - return equivalent.StateNotLocalHashEqual() - } else { - state := equivalent.StateLocalHashEqual(r.Type == o.Type) - return state.Apply( - r.ElementMeta.Equivalent(&o.ElementMeta), - ) - } -} - -func (s *Source) Copy() *Source { - return &Source{ - SourceMeta: *s.SourceMeta.Copy(), - Access: s.Access, - } -} - -// SourceMeta is the definition of the meta data of a source. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type SourceMeta struct { - ElementMeta - // Type describes the type of the object. - Type string `json:"type"` -} - -// GetType returns the type of the object. -func (o *SourceMeta) GetType() string { - return o.Type -} - -// SetType sets the type of the object. -func (o *SourceMeta) SetType(ttype string) { - o.Type = ttype -} - -// Copy copies a source meta. -func (o *SourceMeta) Copy() *SourceMeta { - if o == nil { - return nil - } - return &SourceMeta{ - ElementMeta: *o.ElementMeta.Copy(), - Type: o.Type, - } -} - -func (o *SourceMeta) WithVersion(v string) *SourceMeta { - r := *o - r.Version = v - return &r -} - -func (o *SourceMeta) WithExtraIdentity(extras ...string) *SourceMeta { - r := *o - r.AddExtraIdentity(NewExtraIdentity(extras...)) - return &r -} - -func (o *SourceMeta) WithLabel(l *Label) *SourceMeta { - r := *o - r.Labels.SetDef(l.Name, l) - return &r -} - -func NewSourceMeta(name, typ string) *SourceMeta { - return &SourceMeta{ - ElementMeta: ElementMeta{Name: name}, - Type: typ, - } -} - -// SourceRef defines a reference to a source -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type SourceRef struct { - // IdentitySelector defines the identity that is used to match a source. - IdentitySelector metav1.StringMap `json:"identitySelector,omitempty"` - // Labels defines an optional set of additional labels - // describing the object. - // +optional - Labels metav1.Labels `json:"labels,omitempty"` -} - -// Copy copy a source ref. -func (r *SourceRef) Copy() *SourceRef { - if r == nil { - return nil - } - return &SourceRef{ - IdentitySelector: r.IdentitySelector.Copy(), - Labels: r.Labels.Copy(), - } -} - -type SourceRefs []SourceRef - -// Copy copies a list of source refs. -func (r SourceRefs) Copy() SourceRefs { - if r == nil { - return nil - } - - result := make(SourceRefs, len(r)) - for i, v := range r { - result[i] = *v.Copy() - } - return result -} - -// Resources describes a set of resource specifications. -type Resources []Resource - -var _ ElementAccessor = Resources{} - -func ResourceArtifacts(cd *ComponentDescriptor) ArtifactAccessor { - return cd.Resources -} - -func (r Resources) Equivalent(o Resources) equivalent.EqualState { - return EquivalentElems(r, o) -} - -func (r Resources) Len() int { - return len(r) -} - -func (r Resources) Get(i int) ElementMetaAccessor { - return &r[i] -} - -func (r Resources) GetArtifact(i int) ElementArtifactAccessor { - return &r[i] -} - -func (r Resources) Copy() Resources { - if r == nil { - return nil - } - out := make(Resources, len(r)) - for i, v := range r { - out[i] = *v.Copy() - } - return out -} - -func (r Resources) HaveDigests() bool { - for _, e := range r { - if e.Digest == nil { - return false - } - } - return true -} - -// Resource describes a resource dependency of a component. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type Resource struct { - ResourceMeta `json:",inline"` - // Access describes the type specific method to - // access the defined resource. - Access AccessSpec `json:"access"` -} - -func (r *Resource) GetAccess() AccessSpec { - return r.Access -} - -func (r *Resource) SetAccess(a AccessSpec) { - r.Access = a -} - -func (r *Resource) GetDigest() *metav1.DigestSpec { - return r.Digest -} - -func (r *Resource) SetDigest(d *metav1.DigestSpec) { - r.Digest = d -} - -func (r *Resource) GetRelation() metav1.ResourceRelation { - return r.Relation -} - -func (r *Resource) Equivalent(e ElementMetaAccessor) equivalent.EqualState { - if o, ok := e.(*Resource); !ok { - state := equivalent.StateNotLocalHashEqual() - if r.Digest.IsExcluded() || IsNoneAccess(r.Access) { - return state - } else { - state = state.Apply(equivalent.StateNotArtifactEqual(r.Digest != nil)) - } - return state - } else { - // not delegated to ResourceMeta, because the significance of digests can only be determined at the Resource level. - state := equivalent.StateLocalHashEqual(r.Type == o.Type && r.Relation == o.Relation && reflect.DeepEqual(r.SourceRefs, o.SourceRefs)) - - if !IsNoneAccess(r.Access) || !IsNoneAccess(o.Access) { - state = state.Apply(r.Digest.Equivalent(o.Digest)) - } - return state.Apply(r.ElementMeta.Equivalent(&o.ElementMeta)) - } -} - -func (r *Resource) Copy() *Resource { - return &Resource{ - ResourceMeta: *r.ResourceMeta.Copy(), - Access: r.Access, - } -} - -// ResourceMeta describes the meta data of a resource. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type ResourceMeta struct { - ElementMeta `json:",inline"` - - // Type describes the type of the object. - Type string `json:"type"` - - // Relation describes the relation of the resource to the component. - // Can be a local or external resource - Relation metav1.ResourceRelation `json:"relation,omitempty"` - - // SourceRefs defines a list of source names. - // These entries reference the sources defined in the - // component.sources. - SourceRefs SourceRefs `json:"srcRefs,omitempty"` - - // Digest is the optional digest of the referenced resource. - // +optional - Digest *metav1.DigestSpec `json:"digest,omitempty"` -} - -// Fresh returns a digest-free copy. -func (o *ResourceMeta) Fresh() *ResourceMeta { - n := o.Copy() - n.Digest = nil - return n -} - -// GetType returns the type of the object. -func (o *ResourceMeta) GetType() string { - return o.Type -} - -// SetType sets the type of the object. -func (o *ResourceMeta) SetType(ttype string) { - o.Type = ttype -} - -// SetDigest sets the digest of the object. -func (o *ResourceMeta) SetDigest(d *metav1.DigestSpec) { - o.Digest = d -} - -// Copy copies a resource meta. -func (o *ResourceMeta) Copy() *ResourceMeta { - if o == nil { - return nil - } - r := &ResourceMeta{ - ElementMeta: *o.ElementMeta.Copy(), - Type: o.Type, - Relation: o.Relation, - SourceRefs: o.SourceRefs.Copy(), - Digest: o.Digest.Copy(), - } - return r -} - -func (o *ResourceMeta) WithVersion(v string) *ResourceMeta { - r := *o - r.Version = v - return &r -} - -func (o *ResourceMeta) WithExtraIdentity(extras ...string) *ResourceMeta { - r := *o - r.AddExtraIdentity(NewExtraIdentity(extras...)) - return &r -} - -func (o *ResourceMeta) WithLabel(l *Label) *ResourceMeta { - r := *o - r.Labels.SetDef(l.Name, l) - return &r -} - -func NewResourceMeta(name string, typ string, relation metav1.ResourceRelation) *ResourceMeta { - return &ResourceMeta{ - ElementMeta: ElementMeta{Name: name}, - Type: typ, - Relation: relation, - } -} - -type References []ComponentReference - -func (r References) Equivalent(o References) equivalent.EqualState { - return EquivalentElems(r, o) -} - -func (r References) Len() int { - return len(r) -} - -func (r References) Get(i int) ElementMetaAccessor { - return &r[i] -} - -func (r References) Swap(i, j int) { - r[i], r[j] = r[j], r[i] -} - -func (r References) Less(i, j int) bool { - c := strings.Compare(r[i].Name, r[j].Name) - if c != 0 { - return c < 0 - } - return semverutils.Compare(r[i].Version, r[j].Version) < 0 -} - -func (r References) Copy() References { - if r == nil { - return nil - } - out := make(References, len(r)) - for i, v := range r { - out[i] = *v.Copy() - } - return out -} - -// ComponentReference describes the reference to another component in the registry. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type ComponentReference struct { - ElementMeta `json:",inline"` - // ComponentName describes the remote name of the referenced object - ComponentName string `json:"componentName"` - // Digest is the optional digest of the referenced component. - // +optional - Digest *metav1.DigestSpec `json:"digest,omitempty"` -} - -func NewComponentReference(name, componentName, version string, extraIdentity metav1.Identity) *ComponentReference { - return &ComponentReference{ - ElementMeta: ElementMeta{ - Name: name, - Version: version, - ExtraIdentity: extraIdentity, - }, - ComponentName: componentName, - } -} - -func (r ComponentReference) String() string { - return fmt.Sprintf("%s[%s:%s]", r.Name, r.ComponentName, r.Version) -} - -// WithVersion returns a new reference with a dedicated version. -func (o *ComponentReference) WithVersion(v string) *ComponentReference { - n := o.Copy() - n.Version = v - return n -} - -// WithExtraIdentity returns a new reference with a dedicated version. -func (o *ComponentReference) WithExtraIdentity(extras ...string) *ComponentReference { - n := o.Copy() - n.AddExtraIdentity(NewExtraIdentity(extras...)) - return n -} - -// Fresh returns a digest-free copy. -func (o *ComponentReference) Fresh() *ComponentReference { - n := o.Copy() - n.Digest = nil - return n -} - -func (r *ComponentReference) GetDigest() *metav1.DigestSpec { - return r.Digest -} - -func (r *ComponentReference) SetDigest(d *metav1.DigestSpec) { - r.Digest = d -} - -func (r *ComponentReference) Equivalent(e ElementMetaAccessor) equivalent.EqualState { - if o, ok := e.(*ComponentReference); !ok { - state := equivalent.StateNotLocalHashEqual() - if r.Digest != nil { - state = state.Apply(equivalent.StateNotArtifactEqual(true)) - } - return state - } else { - state := equivalent.StateLocalHashEqual(r.Name == o.Name && r.Version == o.Version && r.ComponentName == o.ComponentName) - // TODO: how to handle digests - if r.Digest != nil && o.Digest != nil { // hmm, digest described more than the local component, should we use it at all? - state = state.Apply(r.Digest.Equivalent(o.Digest)) - } else if r.Digest != o.Digest { // not both are nil - state = state.NotEquivalent() - } - - return state.Apply( - r.ElementMeta.Equivalent(&o.ElementMeta), - ) - } -} - -func (r *ComponentReference) GetComponentName() string { - return r.ComponentName -} - -func (r *ComponentReference) Copy() *ComponentReference { - return &ComponentReference{ - ElementMeta: *r.ElementMeta.Copy(), - ComponentName: r.ComponentName, - Digest: r.Digest.Copy(), - } -} diff --git a/pkg/contexts/ocm/compdesc/default.go b/pkg/contexts/ocm/compdesc/default.go deleted file mode 100644 index af549cfed..000000000 --- a/pkg/contexts/ocm/compdesc/default.go +++ /dev/null @@ -1,52 +0,0 @@ -package compdesc - -import ( - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/runtime" -) - -// DefaultComponent applies defaults to a component. -func DefaultComponent(component *ComponentDescriptor) *ComponentDescriptor { - if component.RepositoryContexts == nil { - component.RepositoryContexts = make([]*runtime.UnstructuredTypedObject, 0) - } - if component.Sources == nil { - component.Sources = make([]Source, 0) - } - if component.References == nil { - component.References = make([]ComponentReference, 0) - } - if component.Resources == nil { - component.Resources = make([]Resource, 0) - } - - if component.Metadata.ConfiguredVersion == "" { - component.Metadata.ConfiguredVersion = DefaultSchemeVersion - } - // DefaultResources(component) - return component -} - -// DefaultResources defaults a list of resources. -// The version of the component is defaulted for local resources that do not contain a version. -// adds the version as identity if the resource identity would clash otherwise. -func DefaultResources(component *ComponentDescriptor) { - for i, res := range component.Resources { - if res.Relation == v1.LocalRelation && len(res.Version) == 0 { - component.Resources[i].Version = component.GetVersion() - } - - id := res.GetIdentity(component.Resources) - if v, ok := id[SystemIdentityVersion]; ok { - if res.ExtraIdentity == nil { - res.ExtraIdentity = v1.Identity{ - SystemIdentityVersion: v, - } - } else { - if _, ok := res.ExtraIdentity[SystemIdentityVersion]; !ok { - res.ExtraIdentity[SystemIdentityVersion] = v - } - } - } - } -} diff --git a/pkg/contexts/ocm/compdesc/deprecated.go b/pkg/contexts/ocm/compdesc/deprecated.go deleted file mode 100644 index 669150bb9..000000000 --- a/pkg/contexts/ocm/compdesc/deprecated.go +++ /dev/null @@ -1,287 +0,0 @@ -package compdesc - -import ( - "bytes" - "fmt" - - "github.com/mandelsoft/goutils/sliceutils" - - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/utils/selector" -) - -// GetResourceAccessByIdentity returns a pointer to the resource that matches the given identity. -// -// Deprecated: use GetResourceByIdentity. -func (cd *ComponentDescriptor) GetResourceAccessByIdentity(id v1.Identity) *Resource { - dig := id.Digest() - for i, res := range cd.Resources { - if bytes.Equal(res.GetIdentityDigest(cd.Resources), dig) { - return &cd.Resources[i] - } - } - return nil -} - -// GetResourceByRegexSelector returns resources that match the given selectors. -// -// Deprecated: use GetResources with appropriate selectors. -func (cd *ComponentDescriptor) GetResourceByRegexSelector(sel interface{}) (Resources, error) { - identitySelector, err := selector.ParseRegexSelector(sel) - if err != nil { - return nil, fmt.Errorf("unable to parse selector: %w", err) - } - return cd.GetResourcesByIdentitySelectors(identitySelector) -} - -// GetResourcesByIdentitySelectors returns resources that match the given identity selectors. -// Deprecated: use GetResources with appropriate selectors. -func (cd *ComponentDescriptor) GetResourcesByIdentitySelectors(selectors ...IdentitySelector) (Resources, error) { - return cd.GetResourcesBySelectors(selectors, nil) -} - -// GetResourcesByResourceSelectors returns resources that match the given resource selectors. -// -// Deprecated: use GetResources with appropriate selectors. -func (cd *ComponentDescriptor) GetResourcesByResourceSelectors(selectors ...ResourceSelector) (Resources, error) { - return cd.GetResourcesBySelectors(nil, selectors) -} - -// GetResourcesBySelectors returns resources that match the given selector. -// -// Deprecated: use GetResources with appropriate selectors. -func (cd *ComponentDescriptor) GetResourcesBySelectors(selectors []IdentitySelector, resourceSelectors []ResourceSelector) (Resources, error) { - resources := make(Resources, 0) - for i := range cd.Resources { - selctx := NewResourceSelectionContext(i, cd.Resources) - if len(selectors) > 0 { - ok, err := selector.MatchSelectors(selctx.Identity(), selectors...) - if err != nil { - return nil, fmt.Errorf("unable to match selector for resource %s: %w", selctx.Name, err) - } - if !ok { - continue - } - } - ok, err := MatchResourceByResourceSelector(selctx, resourceSelectors...) - if err != nil { - return nil, fmt.Errorf("unable to match selector for resource %s: %w", selctx.Name, err) - } - if !ok { - continue - } - resources = append(resources, *selctx.Resource) - } - if len(resources) == 0 { - return resources, NotFound - } - return resources, nil -} - -// GetExternalResources returns external resource with the given type, name and version. -// -// Deprecated: use GetResources with appropriate selectors. -func (cd *ComponentDescriptor) GetExternalResources(rtype, name, version string) (Resources, error) { - return cd.GetResourcesBySelectors( - []selector.Interface{ - ByName(name), - ByVersion(version), - }, - []ResourceSelector{ - ByResourceType(rtype), - ByRelation(v1.ExternalRelation), - }) -} - -// GetExternalResource returns external resource with the given type, name and version. -// -// If multiple resources match, the first one is returned. -// Deprecated: use GetResources with appropriate selectors. -func (cd *ComponentDescriptor) GetExternalResource(rtype, name, version string) (Resource, error) { - resources, err := cd.GetExternalResources(rtype, name, version) - if err != nil { - return Resource{}, err - } - // at least one resource must be defined, otherwise the getResourceBySelectors functions returns a NotFound err. - return resources[0], nil -} - -// GetLocalResources returns all local resources with the given type, name and version. -func (cd *ComponentDescriptor) GetLocalResources(rtype, name, version string) (Resources, error) { - return cd.GetResourcesBySelectors( - []selector.Interface{ - ByName(name), - ByVersion(version), - }, - []ResourceSelector{ - ByResourceType(rtype), - ByRelation(v1.LocalRelation), - }) -} - -// GetLocalResource returns a local resource with the given type, name and version. -// -// If multiple resources match, the first one is returned. -// Deprecated: use GetResources with appropriate selectors. -func (cd *ComponentDescriptor) GetLocalResource(rtype, name, version string) (Resource, error) { - resources, err := cd.GetLocalResources(rtype, name, version) - if err != nil { - return Resource{}, err - } - // at least one resource must be defined, otherwise the getResourceBySelectors functions returns a NotFound err. - return resources[0], nil -} - -// GetResourcesByType returns all resources that match the given type and selectors. -// -// Deprecated: use GetResources with appropriate selectors. -func (cd *ComponentDescriptor) GetResourcesByType(rtype string, selectors ...IdentitySelector) (Resources, error) { - return cd.GetResourcesBySelectors( - selectors, - []ResourceSelector{ - ByResourceType(rtype), - }) -} - -// GetResourcesByName returns all local and external resources with a name. -// -// Deprecated: use GetResources with appropriate selectors. -func (cd *ComponentDescriptor) GetResourcesByName(name string, selectors ...IdentitySelector) (Resources, error) { - return cd.GetResourcesBySelectors( - sliceutils.CopyAppend[IdentitySelector](selectors, ByName(name)), - nil) -} - -//////////////////////////////////////////////////////////////////////////////// - -// GetSourceAccessByIdentity returns a pointer to the source that matches the given identity. -// -// Deprecated: use GetSourceByIdentity. -func (cd *ComponentDescriptor) GetSourceAccessByIdentity(id v1.Identity) *Source { - dig := id.Digest() - for i, res := range cd.Sources { - if bytes.Equal(res.GetIdentityDigest(cd.Sources), dig) { - return &cd.Sources[i] - } - } - return nil -} - -// GetSourcesByIdentitySelectors returns references that match the given selector. -// -// Deprecated: use GetSources with appropriate selectors. -func (cd *ComponentDescriptor) GetSourcesByIdentitySelectors(selectors ...IdentitySelector) (Sources, error) { - srcs := make(Sources, 0) - for _, src := range cd.Sources { - ok, err := selector.MatchSelectors(src.GetIdentity(cd.Sources), selectors...) - if err != nil { - return nil, fmt.Errorf("unable to match selector for source %s: %w", src.Name, err) - } - if ok { - srcs = append(srcs, src) - } - } - if len(srcs) == 0 { - return srcs, NotFound - } - return srcs, nil -} - -// GetSourcesByName returns all sources with a name. -// -// Deprecated: use GetSources with appropriate selectors. -func (cd *ComponentDescriptor) GetSourcesByName(name string, selectors ...IdentitySelector) (Sources, error) { - return cd.GetSourcesByIdentitySelectors( - sliceutils.CopyAppend[IdentitySelector](selectors, ByName(name))...) -} - -//////////////////////////////////////////////////////////////////////////////// - -// GetComponentReferences returns all component references that matches the given selectors. -// -// Deprectated: use GetReferences with appropriate selectors. -func (cd *ComponentDescriptor) GetComponentReferences(selectors ...IdentitySelector) ([]ComponentReference, error) { - refs := make([]ComponentReference, 0) - for _, ref := range cd.References { - ok, err := selector.MatchSelectors(ref.GetIdentity(cd.References), selectors...) - if err != nil { - return nil, fmt.Errorf("unable to match selector for resource %s: %w", ref.Name, err) - } - if ok { - refs = append(refs, ref) - } - } - if len(refs) == 0 { - return refs, NotFound - } - return refs, nil -} - -// GetComponentReferenceIndex returns the index of a given component reference. -// If the index is not found -1 is returned. -// Deprecated: use GetReferenceIndex. -func (cd *ComponentDescriptor) GetComponentReferenceIndex(ref ComponentReference) int { - return cd.GetReferenceIndex(ref.GetMeta()) -} - -// GetReferenceAccessByIdentity returns a pointer to the reference that matches the given identity. -// Deprectated: use GetReferenceByIdentity. -func (cd *ComponentDescriptor) GetReferenceAccessByIdentity(id v1.Identity) *ComponentReference { - dig := id.Digest() - for i, ref := range cd.References { - if bytes.Equal(ref.GetIdentityDigest(cd.Resources), dig) { - return &cd.References[i] - } - } - return nil -} - -// GetReferencesByIdentitySelectors returns resources that match the given identity selectors. -// Deprectated: use GetReferences with appropriate selectors. -func (cd *ComponentDescriptor) GetReferencesByIdentitySelectors(selectors ...IdentitySelector) (References, error) { - return cd.GetReferencesBySelectors(selectors, nil) -} - -// GetReferencesByReferenceSelectors returns resources that match the given resource selectors. -// Deprectated: use GetReferences with appropriate selectors. -func (cd *ComponentDescriptor) GetReferencesByReferenceSelectors(selectors ...ReferenceSelector) (References, error) { - return cd.GetReferencesBySelectors(nil, selectors) -} - -// GetReferencesBySelectors returns resources that match the given selector. -// Deprectated: use GetReferences with appropriate selectors. -func (cd *ComponentDescriptor) GetReferencesBySelectors(selectors []IdentitySelector, referenceSelectors []ReferenceSelector) (References, error) { - references := make(References, 0) - for i := range cd.References { - selctx := NewReferenceSelectionContext(i, cd.References) - if len(selectors) > 0 { - ok, err := selector.MatchSelectors(selctx.Identity(), selectors...) - if err != nil { - return nil, fmt.Errorf("unable to match selector for resource %s: %w", selctx.Name, err) - } - if !ok { - continue - } - } - ok, err := MatchReferencesByReferenceSelector(selctx, referenceSelectors...) - if err != nil { - return nil, fmt.Errorf("unable to match selector for resource %s: %w", selctx.Name, err) - } - if !ok { - continue - } - references = append(references, *selctx.ComponentReference) - } - if len(references) == 0 { - return references, NotFound - } - return references, nil -} - -// GetReferencesByName returns references that match the given name. -// Deprectated: use GetReferences with appropriate selectors. -func (cd *ComponentDescriptor) GetReferencesByName(name string, selectors ...IdentitySelector) (References, error) { - return cd.GetReferencesBySelectors( - sliceutils.CopyAppend[IdentitySelector](selectors, ByName(name)), - nil) -} diff --git a/pkg/contexts/ocm/compdesc/equivalent/testhelper/helper.go b/pkg/contexts/ocm/compdesc/equivalent/testhelper/helper.go deleted file mode 100644 index a639d72c4..000000000 --- a/pkg/contexts/ocm/compdesc/equivalent/testhelper/helper.go +++ /dev/null @@ -1,57 +0,0 @@ -package testhelper - -import ( - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/equivalent" -) - -func CheckEquivalent(eq equivalent.EqualState) { - ExpectWithOffset(1, eq).To(Equal(equivalent.StateEquivalent())) - - Expect(eq.IsEquivalent()).To(BeTrue()) - Expect(eq.IsHashEqual()).To(BeTrue()) - Expect(eq.IsLocalHashEqual()).To(BeTrue()) - Expect(eq.IsArtifactEqual()).To(BeTrue()) - Expect(eq.IsArtifactDetectable()).To(BeTrue()) -} - -func CheckNotEquivalent(eq equivalent.EqualState) { - ExpectWithOffset(1, eq).To(Equal(equivalent.StateNotEquivalent())) - - Expect(eq.IsEquivalent()).To(BeFalse()) - Expect(eq.IsHashEqual()).To(BeTrue()) - Expect(eq.IsLocalHashEqual()).To(BeTrue()) - Expect(eq.IsArtifactEqual()).To(BeTrue()) - Expect(eq.IsArtifactDetectable()).To(BeTrue()) -} - -func CheckNotLocalHashEqual(eq equivalent.EqualState) { - ExpectWithOffset(1, eq).To(Equal(equivalent.StateNotLocalHashEqual())) - - Expect(eq.IsEquivalent()).To(BeFalse()) - Expect(eq.IsHashEqual()).To(BeFalse()) - Expect(eq.IsLocalHashEqual()).To(BeFalse()) - Expect(eq.IsArtifactEqual()).To(BeTrue()) - Expect(eq.IsArtifactDetectable()).To(BeTrue()) -} - -func CheckNotDetectable(eq equivalent.EqualState) { - ExpectWithOffset(1, eq).To(Equal(equivalent.StateNotArtifactEqual(false))) - - Expect(eq.IsEquivalent()).To(BeFalse()) - Expect(eq.IsHashEqual()).To(BeFalse()) - Expect(eq.IsLocalHashEqual()).To(BeTrue()) - Expect(eq.IsArtifactEqual()).To(BeFalse()) - Expect(eq.IsArtifactDetectable()).To(BeFalse()) -} - -func CheckNotArtifactEqual(eq equivalent.EqualState) { - ExpectWithOffset(1, eq).To(Equal(equivalent.StateNotArtifactEqual(true))) - - Expect(eq.IsEquivalent()).To(BeFalse()) - Expect(eq.IsHashEqual()).To(BeFalse()) - Expect(eq.IsLocalHashEqual()).To(BeTrue()) - Expect(eq.IsArtifactEqual()).To(BeFalse()) - Expect(eq.IsArtifactDetectable()).To(BeTrue()) -} diff --git a/pkg/contexts/ocm/compdesc/helper.go b/pkg/contexts/ocm/compdesc/helper.go deleted file mode 100644 index f394b098f..000000000 --- a/pkg/contexts/ocm/compdesc/helper.go +++ /dev/null @@ -1,239 +0,0 @@ -package compdesc - -import ( - "bytes" - "fmt" - - "github.com/mandelsoft/goutils/errors" - - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/refsel" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/rscsel" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/srcsel" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils/selector" -) - -// GetEffectiveRepositoryContext returns the currently active repository context. -func (cd *ComponentDescriptor) GetEffectiveRepositoryContext() *runtime.UnstructuredTypedObject { - if len(cd.RepositoryContexts) == 0 { - return nil - } - return cd.RepositoryContexts[len(cd.RepositoryContexts)-1] -} - -// AddRepositoryContext appends the given repository context to components descriptor repository history. -// The context is not appended if the effective repository context already matches the current context. -func (cd *ComponentDescriptor) AddRepositoryContext(repoCtx runtime.TypedObject) error { - effective, err := runtime.ToUnstructuredTypedObject(cd.GetEffectiveRepositoryContext()) - if err != nil { - return err - } - uRepoCtx, err := runtime.ToUnstructuredTypedObject(repoCtx) - if err != nil { - return err - } - if !runtime.UnstructuredTypesEqual(effective, uRepoCtx) { - cd.RepositoryContexts = append(cd.RepositoryContexts, uRepoCtx) - } - return nil -} - -func (cd *ComponentDescriptor) SelectResources(sel ...rscsel.Selector) ([]Resource, error) { - err := selectors.ValidateSelectors(sel...) - if err != nil { - return nil, err - } - - list := MapToSelectorElementList(cd.Resources) - result := []Resource{} - for _, r := range cd.Resources { - if len(sel) > 0 { - mr := MapToSelectorResource(&r) - for _, s := range sel { - if !s.MatchResource(list, mr) { - continue - } - } - } - result = append(result, r) - } - return result, nil -} - -func (cd *ComponentDescriptor) GetResources() []Resource { - result := []Resource{} - for _, r := range cd.Resources { - result = append(result, r) - } - return result -} - -// GetResourceByIdentity returns resource that matches the given identity. -func (cd *ComponentDescriptor) GetResourceByIdentity(id v1.Identity) (Resource, error) { - dig := id.Digest() - for _, res := range cd.Resources { - if bytes.Equal(res.GetIdentityDigest(cd.Resources), dig) { - return res, nil - } - } - return Resource{}, NotFound -} - -// GetResourceIndexByIdentity returns the index of the resource that matches the given identity. -func (cd *ComponentDescriptor) GetResourceIndexByIdentity(id v1.Identity) int { - dig := id.Digest() - for i, res := range cd.Resources { - if bytes.Equal(res.GetIdentityDigest(cd.Resources), dig) { - return i - } - } - return -1 -} - -// GetResourceByJSONScheme returns resources that match the given selectors. -func (cd *ComponentDescriptor) GetResourceByJSONScheme(src interface{}) (Resources, error) { - sel, err := selector.NewJSONSchemaSelectorFromGoStruct(src) - if err != nil { - return nil, err - } - return cd.GetResourcesByIdentitySelectors(sel) -} - -// GetResourceByDefaultSelector returns resources that match the given selectors. -func (cd *ComponentDescriptor) GetResourceByDefaultSelector(sel interface{}) (Resources, error) { - identitySelector, err := selector.ParseDefaultSelector(sel) - if err != nil { - return nil, fmt.Errorf("unable to parse selector: %w", err) - } - return cd.GetResourcesByIdentitySelectors(identitySelector) -} - -// GetResourceIndex returns the index of a given resource. -// If the index is not found -1 is returned. -func (cd *ComponentDescriptor) GetResourceIndex(res *ResourceMeta) int { - return ElementIndex(cd.Resources, res) -} - -func (cd *ComponentDescriptor) SelectSources(sel ...srcsel.Selector) ([]Source, error) { - err := selectors.ValidateSelectors(sel...) - if err != nil { - return nil, err - } - - list := MapToSelectorElementList(cd.Sources) - result := []Source{} - for _, r := range cd.Sources { - if len(sel) > 0 { - mr := MapToSelectorSource(&r) - for _, s := range sel { - if !s.MatchSource(list, mr) { - continue - } - } - } - result = append(result, r) - } - return result, nil -} - -func (cd *ComponentDescriptor) GetSources() []Source { - result := []Source{} - for _, r := range cd.Sources { - result = append(result, r) - } - return result -} - -// GetSourceByIdentity returns source that match the given identity. -func (cd *ComponentDescriptor) GetSourceByIdentity(id v1.Identity) (Source, error) { - dig := id.Digest() - for _, res := range cd.Sources { - if bytes.Equal(res.GetIdentityDigest(cd.Sources), dig) { - return res, nil - } - } - return Source{}, NotFound -} - -// GetSourceIndexByIdentity returns the index of the source that matches the given identity. -func (cd *ComponentDescriptor) GetSourceIndexByIdentity(id v1.Identity) int { - dig := id.Digest() - for i, res := range cd.Sources { - if bytes.Equal(res.GetIdentityDigest(cd.Sources), dig) { - return i - } - } - return -1 -} - -// GetSourceIndex returns the index of a given source. -// If the index is not found -1 is returned. -func (cd *ComponentDescriptor) GetSourceIndex(src *SourceMeta) int { - return ElementIndex(cd.Sources, src) -} - -// GetReferenceByIdentity returns reference that matches the given identity. -func (cd *ComponentDescriptor) GetReferenceByIdentity(id v1.Identity) (ComponentReference, error) { - dig := id.Digest() - for _, ref := range cd.References { - if bytes.Equal(ref.GetIdentityDigest(cd.Resources), dig) { - return ref, nil - } - } - return ComponentReference{}, errors.ErrNotFound(KIND_REFERENCE, id.String()) -} - -func (cd *ComponentDescriptor) SelectReferences(sel ...refsel.Selector) ([]ComponentReference, error) { - err := selectors.ValidateSelectors(sel...) - if err != nil { - return nil, err - } - - list := MapToSelectorElementList(cd.References) - result := []ComponentReference{} - for _, r := range cd.References { - if len(sel) > 0 { - mr := MapToSelectorReference(&r) - for _, s := range sel { - if !s.MatchReference(list, mr) { - continue - } - } - } - result = append(result, r) - } - return result, nil -} - -func (cd *ComponentDescriptor) GetReferences() []ComponentReference { - result := []ComponentReference{} - for _, r := range cd.References { - result = append(result, r) - } - return result -} - -// GetReferenceIndexByIdentity returns the index of the reference that matches the given identity. -func (cd *ComponentDescriptor) GetReferenceIndexByIdentity(id v1.Identity) int { - dig := id.Digest() - for i, ref := range cd.References { - if bytes.Equal(ref.GetIdentityDigest(cd.Resources), dig) { - return i - } - } - return -1 -} - -// GetReferenceIndex returns the index of a given source. -// If the index is not found -1 is returned. -func (cd *ComponentDescriptor) GetReferenceIndex(src ElementMetaProvider) int { - return ElementIndex(cd.References, src) -} - -// GetSignatureIndex returns the index of the signature with the given name -// If the index is not found -1 is returned. -func (cd *ComponentDescriptor) GetSignatureIndex(name string) int { - return cd.Signatures.GetIndex(name) -} diff --git a/pkg/contexts/ocm/compdesc/init.go b/pkg/contexts/ocm/compdesc/init.go deleted file mode 100644 index 8bb239f40..000000000 --- a/pkg/contexts/ocm/compdesc/init.go +++ /dev/null @@ -1,6 +0,0 @@ -package compdesc - -import ( - _ "github.com/open-component-model/ocm/pkg/signing/handlers" - _ "github.com/open-component-model/ocm/pkg/signing/hasher" -) diff --git a/pkg/contexts/ocm/compdesc/logging.go b/pkg/contexts/ocm/compdesc/logging.go deleted file mode 100644 index ea3a6b56e..000000000 --- a/pkg/contexts/ocm/compdesc/logging.go +++ /dev/null @@ -1,10 +0,0 @@ -package compdesc - -import ( - "github.com/open-component-model/ocm/pkg/logging" -) - -var ( - REALM = logging.DefineSubRealm("component descriptor handling", "compdesc") - Logger = logging.DynamicLogger(REALM) -) diff --git a/pkg/contexts/ocm/compdesc/meta/v1/identity.go b/pkg/contexts/ocm/compdesc/meta/v1/identity.go deleted file mode 100644 index efcd5e319..000000000 --- a/pkg/contexts/ocm/compdesc/meta/v1/identity.go +++ /dev/null @@ -1,164 +0,0 @@ -package v1 - -import ( - "encoding/json" - "fmt" - "sort" - - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/equivalent" - "github.com/open-component-model/ocm/pkg/logging" -) - -// Identity describes the identity of an object. -// Only ascii characters are allowed -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type Identity map[string]string - -func NewExtraIdentity(extras ...string) Identity { - if len(extras) == 0 { - return nil - } - id := Identity{} - i := 0 - for i < len(extras) { - if i+1 < len(extras) { - id[extras[i]] = extras[i+1] - } else { - id[extras[i]] = "" - } - i += 2 - } - return id -} - -// NewIdentity return a simple name identity. -func NewIdentity(name string, extras ...string) Identity { - id := NewExtraIdentity(extras...) - if id == nil { - return Identity{SystemIdentityName: name} - } - id[SystemIdentityName] = name - return id -} - -// Digest returns the object digest of an identity. -func (i Identity) Digest() []byte { - data, err := json.Marshal(i) - if err != nil { - logging.Logger().LogError(err, "corrupted digest") - } - - return data -} - -// Equals compares two identities. -func (i Identity) Equals(o Identity) bool { - if len(i) != len(o) { - return false - } - - for k, v := range i { - if v2, ok := o[k]; !ok || v != v2 { - return false - } - } - return true -} - -func (i Identity) Equivalent(o Identity) equivalent.EqualState { - if len(i) != len(o) { - return equivalent.StateNotLocalHashEqual() - } - - for k, v := range i { - if v2, ok := o[k]; !ok || v != v2 { - return equivalent.StateNotLocalHashEqual() - } - } - return equivalent.StateEquivalent() -} - -func (i *Identity) Set(name, value string) { - if *i == nil { - *i = Identity{name: value} - } else { - (*i)[name] = value - } -} - -func (i Identity) Get(name string) string { - if i != nil { - return i[name] - } - return "" -} - -func (i Identity) Remove(name string) bool { - if i != nil { - delete(i, name) - } - return false -} - -func (i Identity) String() string { - if i == nil { - return "" - } - - var keys []string - for k := range i { - keys = append(keys, k) - } - sort.Strings(keys) - s := "" - sep := "" - for _, k := range keys { - s = fmt.Sprintf("%s%s%q=%q", s, sep, k, i[k]) - sep = "," - } - return s -} - -// Match implements the selector interface. -func (i Identity) Match(obj map[string]string) (bool, error) { - for k, v := range i { - if obj[k] != v { - return false, nil - } - } - return true, nil -} - -// Copy copies identity. -func (i Identity) Copy() Identity { - if i == nil { - return nil - } - n := Identity{} - for k, v := range i { - n[k] = v - } - return n -} - -// ValidateIdentity validates the identity of object. -func ValidateIdentity(fldPath *field.Path, id Identity) field.ErrorList { - allErrs := field.ErrorList{} - - for key := range id { - if key == SystemIdentityName { - allErrs = append(allErrs, field.Forbidden(fldPath.Key(SystemIdentityName), "name is a reserved system identity label")) - } - - if !IsASCII(key) { - allErrs = append(allErrs, field.Forbidden(fldPath.Key(key), "key contains non-ascii characters")) - } - if !IsIdentity(key) { - allErrs = append(allErrs, field.Invalid(fldPath.Key(key), key, IdentityKeyValidationErrMsg)) - } - } - return allErrs -} diff --git a/pkg/contexts/ocm/compdesc/meta/v1/identity_test.go b/pkg/contexts/ocm/compdesc/meta/v1/identity_test.go deleted file mode 100644 index 32c8980c7..000000000 --- a/pkg/contexts/ocm/compdesc/meta/v1/identity_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package v1_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/equivalent/testhelper" - - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" -) - -var _ = Describe("identity", func() { - Context("equivalence", func() { - var a v1.Identity - var b v1.Identity - - BeforeEach(func() { - a = v1.NewIdentity("name", "extra", "extra") - b = a.Copy() - }) - - It("detects equal", func() { - CheckEquivalent(a.Equivalent(b)) - }) - - It("detects different value", func() { - b["name"] = "X" - CheckNotLocalHashEqual(a.Equivalent(b)) - CheckNotLocalHashEqual(b.Equivalent(a)) - }) - - It("detects additional attr", func() { - b["X"] = "X" - CheckNotLocalHashEqual(a.Equivalent(b)) - CheckNotLocalHashEqual(b.Equivalent(a)) - }) - - It("detects replaced attr", func() { - b["X"] = "extra" - delete(b, "extra") - Expect(len(a)).To(Equal(len(b))) - CheckNotLocalHashEqual(a.Equivalent(b)) - CheckNotLocalHashEqual(b.Equivalent(a)) - }) - }) -}) diff --git a/pkg/contexts/ocm/compdesc/meta/v1/labels.go b/pkg/contexts/ocm/compdesc/meta/v1/labels.go deleted file mode 100644 index 34343d3b6..000000000 --- a/pkg/contexts/ocm/compdesc/meta/v1/labels.go +++ /dev/null @@ -1,392 +0,0 @@ -package v1 - -import ( - "encoding/json" - "reflect" - "regexp" - - "github.com/mandelsoft/goutils/errors" - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/equivalent" - "github.com/open-component-model/ocm/pkg/listformat" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - KIND_LABEL = "label" - KIND_VALUE_MERGE_ALGORITHM = "label merge algorithm" -) - -type MergeAlgorithmSpecification struct { - // Algorithm optionally described the Merge algorithm used to - // merge the label value during a transfer. - Algorithm string `json:"algorithm"` - // eConfig contains optional config for the merge algorithm. - Config json.RawMessage `json:"config,omitempty"` -} - -var _ listformat.DirectDescriptionSource = (*MergeAlgorithmSpecification)(nil) - -func (s *MergeAlgorithmSpecification) Description() string { - return s.Algorithm -} - -func NewMergeAlgorithmSpecification(algo string, spec interface{}) (*MergeAlgorithmSpecification, error) { - m, err := runtime.AsRawMessage(spec) - if err != nil { - return nil, err - } - return &MergeAlgorithmSpecification{ - Algorithm: algo, - Config: m, - }, nil -} - -//////////////////////////////////////////////////////////////////////////////// - -// Label is a label that can be set on objects. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type Label struct { - // Name is the unique name of the label. - Name string `json:"name"` - // Value is the json/yaml data of the label - // +kubebuilder:pruning:PreserveUnknownFields - // +kubebuilder:validation:Schemaless - Value json.RawMessage `json:"value"` - - // Version is the optional specification version of the attribute value - Version string `json:"version,omitempty"` - // Signing describes whether the label should be included into the signature - Signing bool `json:"signing,omitempty"` - - // MergeAlgorithm optionally describes the desired merge handling used to - // merge the label value during a transfer. - Merge *MergeAlgorithmSpecification `json:"merge,omitempty"` -} - -// DeepCopyInto copies labels. -func (in *Label) DeepCopyInto(out *Label) { - *out = *in - out.Value = append(out.Value[:0:0], in.Value...) -} - -// GetValue returns the label value with the given name as parsed object. -func (in *Label) GetValue(dest interface{}) error { - return json.Unmarshal(in.Value, dest) -} - -// SetValue sets the label value by marshalling the given object. -// A passed byte slice is validated to be valid json. -func (in *Label) SetValue(value interface{}) error { - var v runtime.RawValue - err := v.SetValue(value) - if err != nil { - return err - } - in.Value = v.RawMessage - return nil -} - -var versionRegex = regexp.MustCompile("^v[0-9]+$") - -func NewLabel(name string, value interface{}, opts ...LabelOption) (*Label, error) { - l := Label{Name: name} - err := l.SetValue(value) - if err != nil { - return nil, err - } - - for _, o := range opts { - if err := o.ApplyToLabel(&l); err != nil { - return nil, errors.Wrapf(err, "label %q", name) - } - } - return &l, nil -} - -// Labels describe a list of labels -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type Labels []Label - -// GetIndex returns the index of the given label or -1 if not found. -func (l Labels) GetIndex(name string) int { - for i, label := range l { - if label.Name == name { - return i - } - } - return -1 -} - -// GetDef returns the label definition of the given label. -func (l Labels) GetDef(name string) *Label { - for i, label := range l { - if label.Name == name { - return &l[i] - } - } - return nil -} - -// SetDef sets a label definition. -func (l *Labels) SetDef(name string, value *Label) { - n := *value - n.Name = name - for i, label := range *l { - if label.Name == name { - (*l)[i] = n - return - } - } - *l = append(*l, n) -} - -// Get returns the label value with the given name as json string. -func (l Labels) Get(name string) ([]byte, bool) { - for _, label := range l { - if label.Name == name { - return label.Value, true - } - } - return nil, false -} - -// GetValue returns the label value with the given name as parsed object. -func (l Labels) GetValue(name string, dest interface{}) (bool, error) { - for _, label := range l { - if label.Name == name { - return true, label.GetValue(dest) - } - } - return false, nil -} - -// Set sets or modifies a label including its meta data. -func (l *Labels) Set(name string, value interface{}, opts ...LabelOption) error { - newLabel, err := NewLabel(name, value, opts...) - if err != nil { - return err - } - for i, label := range *l { - if label.Name == name { - (*l)[i] = *newLabel - return nil - } - } - *l = append(*l, *newLabel) - return nil -} - -// Set sets or modifies the label meta data. -func (l *Labels) SetOptions(name string, opts ...LabelOption) error { - newLabel, err := NewLabel(name, nil, opts...) - if err != nil { - return err - } - for i, label := range *l { - if label.Name == name { - newLabel.Value = label.Value - (*l)[i] = *newLabel - return nil - } - } - return errors.ErrNotFound(KIND_LABEL, name) -} - -// SetValue sets or modifies the value of a label, the label metadata -// is not touched. -func (l *Labels) SetValue(name string, value interface{}) error { - newLabel, err := NewLabel(name, value) - if err != nil { - return err - } - for i, label := range *l { - if label.Name == name { - (*l)[i].Value = newLabel.Value - return nil - } - } - *l = append(*l, *newLabel) - return nil -} - -func (l *Labels) Remove(name string) bool { - for i, label := range *l { - if label.Name == name { - *l = append((*l)[:i], (*l)[i+1:]...) - return true - } - } - return false -} - -func (l *Labels) Clear() { - *l = nil -} - -func (l Labels) Equivalent(o Labels) equivalent.EqualState { - state := equivalent.StateEquivalent() - - for _, ol := range o { - ll := l.GetDef(ol.Name) - if ol.Signing { - if ll == nil || !reflect.DeepEqual(&ol, ll) { - state = state.NotLocalHashEqual() - } - } else { - if ll != nil { - if ll.Signing { - state = state.NotLocalHashEqual() - } - if !reflect.DeepEqual(&ol, ll) { - state = state.NotEquivalent() - } - } else { - state = state.NotEquivalent() - } - } - } - for _, ll := range l { - ol := o.GetDef(ll.Name) - if ol == nil { - if ll.Signing { - state = state.NotLocalHashEqual() - } - state = state.NotEquivalent() - } - } - return state -} - -// AsMap return an unmarshalled map representation. -func (l *Labels) AsMap() map[string]interface{} { - labels := map[string]interface{}{} - if l != nil { - for _, label := range *l { - var m interface{} - json.Unmarshal(label.Value, &m) - labels[label.Name] = m - } - } - return labels -} - -// Copy copies labels. -func (l Labels) Copy() Labels { - if l == nil { - return nil - } - n := make(Labels, len(l)) - copy(n, l) - return n -} - -// ValidateLabels validates a list of labels. -func ValidateLabels(fldPath *field.Path, labels Labels) field.ErrorList { - allErrs := field.ErrorList{} - labelNames := make(map[string]struct{}) - for i, label := range labels { - labelPath := fldPath.Index(i) - if len(label.Name) == 0 { - allErrs = append(allErrs, field.Required(labelPath.Child("name"), "must specify a name")) - continue - } - - if _, ok := labelNames[label.Name]; ok { - allErrs = append(allErrs, field.Duplicate(labelPath, "duplicate label name")) - continue - } - labelNames[label.Name] = struct{}{} - } - return allErrs -} - -type LabelOption interface { - ApplyToLabel(l *Label) error -} - -type labelOptVersion struct { - version string -} - -var _ LabelOption = (*labelOptVersion)(nil) - -func WithVersion(v string) LabelOption { - return &labelOptVersion{v} -} - -func CheckLabelVersion(v string) bool { - return versionRegex.MatchString(v) -} - -func (o labelOptVersion) ApplyToLabel(l *Label) error { - if !CheckLabelVersion(o.version) { - return errors.ErrInvalid("version", o.version) - } - l.Version = o.version - return nil -} - -//////////////////////////////////////////////////////////////////////////////// - -type labelOptSigning struct { - sign bool -} - -var _ LabelOption = (*labelOptSigning)(nil) - -func WithSigning(b ...bool) LabelOption { - s := true - for _, o := range b { - s = o - } - return &labelOptSigning{s} -} - -func (o *labelOptSigning) ApplyToLabel(l *Label) error { - l.Signing = o.sign - return nil -} - -//////////////////////////////////////////////////////////////////////////////// - -// LabelMergeHandlerConfig must be label merge handler config. but cannot be checked -// because of cyclic package dependencies. -type LabelMergeHandlerConfig interface{} - -type labelOptMerge struct { - cfg json.RawMessage - algo string -} - -var _ LabelOption = (*labelOptMerge)(nil) - -func WithMerging(algo string, cfg LabelMergeHandlerConfig) LabelOption { - var data []byte - if cfg != nil { - var err error - data, err = json.Marshal(cfg) - if err != nil { - return nil - } - } - return &labelOptMerge{algo: algo, cfg: data} -} - -func (o *labelOptMerge) ApplyToLabel(l *Label) error { - if o.algo != "" || len(o.cfg) > 0 { - l.Merge = &MergeAlgorithmSpecification{} - if o.algo != "" { - l.Merge.Algorithm = o.algo - } - if len(o.cfg) > 0 { - l.Merge.Config = o.cfg - } - } else { - l.Merge = nil - } - return nil -} diff --git a/pkg/contexts/ocm/compdesc/meta/v1/types.go b/pkg/contexts/ocm/compdesc/meta/v1/types.go deleted file mode 100644 index 11ee47c3f..000000000 --- a/pkg/contexts/ocm/compdesc/meta/v1/types.go +++ /dev/null @@ -1,276 +0,0 @@ -package v1 - -import ( - "reflect" - "time" - - "github.com/mandelsoft/goutils/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/equivalent" -) - -// These constants describe identity attributes predefined by the -// model used to identify elements (resources, sources and references) -// in a component version. -const ( - // SystemIdentityName is the name attribute of an element in - // a component version. It is always present. - SystemIdentityName = "name" - // SystemIdentityVersion is the version attribute optionally - // added to the identity of an element in a component version. - // It is required, if the name and the other explicitly defined - // extra identity attributes are not unique for a dedicated - // kind of element in the context of a component version. - SystemIdentityVersion = "version" -) - -// Metadata defines the metadata of the component descriptor. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type Metadata struct { - // Version is the schema version of the component descriptor. - Version string `json:"schemaVersion"` -} - -// ProviderName describes the provider type of component in the origin's context. -// Defines whether the component is created by a third party or internally. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type ProviderName string - -// ResourceRelation describes the type of a resource. -// Defines whether the component is created by a third party or internally. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type ResourceRelation string - -const ( - // LocalRelation defines a internal relation - // which describes a internally maintained resource in the origin's context. - LocalRelation ResourceRelation = "local" - // ExternalRelation defines a external relation - // which describes a resource maintained by a third party vendor in the origin's context. - ExternalRelation ResourceRelation = "external" -) - -func ValidateRelation(fldPath *field.Path, relation ResourceRelation) *field.Error { - if len(relation) == 0 { - return field.Required(fldPath, "relation must be set") - } - if relation != LocalRelation && relation != ExternalRelation { - return field.NotSupported(fldPath, relation, []string{string(LocalRelation), string(ExternalRelation)}) - } - return nil -} - -const ( - GROUP = "ocm.software" - KIND = "ComponentVersion" -) - -// TypeMeta describes the schema of a descriptor. -type TypeMeta struct { - APIVersion string `json:"apiVersion"` - Kind string `json:"kind"` -} - -// ObjectMeta defines the metadata of the component descriptor. -type ObjectMeta struct { - // Name is the name of the component. - Name string `json:"name"` - // Version is the version of the component. - Version string `json:"version"` - // Labels describe additional properties of the component version - Labels Labels `json:"labels,omitempty"` - // Provider described the component provider - Provider Provider `json:"provider"` - // CreationTime is the creation time of component version - // +optional - CreationTime *Timestamp `json:"creationTime,omitempty"` -} - -func (o *ObjectMeta) Equal(obj interface{}) bool { - if e, ok := obj.(*ObjectMeta); ok { - if o.Name == e.Name && - o.Version == e.Version && - reflect.DeepEqual(o.Provider, e.Provider) && - reflect.DeepEqual(o.Labels, e.Labels) { - return true - } - // check Creation time ? - } - return false -} - -func (o ObjectMeta) Equivalent(a ObjectMeta) equivalent.EqualState { - state := equivalent.StateLocalHashEqual(o.Name == a.Name && o.Version == a.Version) - return state.Apply( - o.Provider.Equivalent(a.Provider), - o.Labels.Equivalent(a.Labels), - ) -} - -// GetName returns the name of the object. -func (o *ObjectMeta) GetName() string { - return o.Name -} - -// SetName sets the name of the object. -func (o *ObjectMeta) SetName(name string) { - o.Name = name -} - -// GetVersion returns the version of the object. -func (o ObjectMeta) GetVersion() string { - return o.Version -} - -// SetVersion sets the version of the object. -func (o *ObjectMeta) SetVersion(version string) { - o.Version = version -} - -// GetLabels returns the label of the object. -func (o ObjectMeta) GetLabels() Labels { - return o.Labels -} - -// SetLabels sets the labels of the object. -func (o *ObjectMeta) SetLabels(labels []Label) { - o.Labels = labels -} - -// GetName returns the name of the object. -func (o *ObjectMeta) Copy() *ObjectMeta { - return &ObjectMeta{ - Name: o.Name, - Version: o.Version, - Labels: o.Labels.Copy(), - Provider: *o.Provider.Copy(), - CreationTime: o.CreationTime.DeepCopy(), - } -} - -//////////////////////////////////////////////////////////////////////////////// - -// Provider describes the provider information of a component version. -type Provider struct { - Name ProviderName `json:"name"` - // Labels describe additional properties of provider - Labels Labels `json:"labels,omitempty"` -} - -// GetName returns the name of the provider. -func (o Provider) GetName() ProviderName { - return o.Name -} - -// SetName sets the name of the provider. -func (o *Provider) SetName(name ProviderName) { - o.Name = name -} - -// GetLabels returns the label of the provider. -func (o Provider) GetLabels() Labels { - return o.Labels -} - -// SetLabels sets the labels of the provider. -func (o *Provider) SetLabels(labels []Label) { - o.Labels = labels -} - -// Copy copies the provider info. -func (o *Provider) Copy() *Provider { - return &Provider{ - Name: o.Name, - Labels: o.Labels.Copy(), - } -} - -func (o Provider) Equivalent(a Provider) equivalent.EqualState { - state := equivalent.StateLocalHashEqual(o.Name == a.Name) - return state.Apply(o.Labels.Equivalent(a.Labels)) -} - -//////////////////////////////////////////////////////////////////////////////// - -type _time = v1.Time - -// Timestamp is time rounded to seconds. -// +k8s:deepcopy-gen=true -type Timestamp struct { - _time `json:",inline"` -} - -func NewTimestamp() Timestamp { - return Timestamp{ - _time: v1.NewTime(time.Now().UTC().Round(time.Second)), - } -} - -func NewTimestampP() *Timestamp { - return &Timestamp{ - _time: v1.NewTime(time.Now().UTC().Round(time.Second)), - } -} - -func NewTimestampFor(t time.Time) Timestamp { - return Timestamp{ - _time: v1.NewTime(t.UTC().Round(time.Second)), - } -} - -func NewTimestampPFor(t time.Time) *Timestamp { - return &Timestamp{ - _time: v1.NewTime(t.UTC().Round(time.Second)), - } -} - -// MarshalJSON implements the json.Marshaler interface. -// The time is a quoted string in RFC 3339 format, with sub-second precision added if present. -func (t Timestamp) MarshalJSON() ([]byte, error) { - if y := t.Year(); y < 0 || y >= 10000 { - // RFC 3339 is clear that years are 4 digits exactly. - // See golang.org/issue/4556#c15 for more discussion. - return nil, errors.New("Time.MarshalJSON: year outside of range [0,9999]") - } - - b := make([]byte, 0, len(time.RFC3339)+2) - b = append(b, '"') - b = t.AppendFormat(b, time.RFC3339) - b = append(b, '"') - return b, nil -} - -// UnmarshalJSON implements the json.Unmarshaler interface. -// The time is expected to be a quoted string in RFC 3339 format. -func (t *Timestamp) UnmarshalJSON(data []byte) error { - // Ignore null, like in the main JSON package. - if string(data) == "null" { - return nil - } - - // Fractional seconds are handled implicitly by Parse. - tt, err := time.Parse(`"`+time.RFC3339+`"`, string(data)) - *t = NewTimestampFor(tt) - return err -} - -func (t Timestamp) String() string { - return t.Format(time.RFC3339) -} - -func (t *Timestamp) Time() time.Time { - return t._time.Time -} - -func (t *Timestamp) Equal(o Timestamp) bool { - return t._time.Equal(&o._time) -} - -func (t *Timestamp) Add(d time.Duration) Timestamp { - return NewTimestampFor(t._time.Add(d)) -} diff --git a/pkg/contexts/ocm/compdesc/normalizations/init.go b/pkg/contexts/ocm/compdesc/normalizations/init.go deleted file mode 100644 index 1cd917a1a..000000000 --- a/pkg/contexts/ocm/compdesc/normalizations/init.go +++ /dev/null @@ -1,6 +0,0 @@ -package versions - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/normalizations/jsonv1" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/normalizations/jsonv2" -) diff --git a/pkg/contexts/ocm/compdesc/normalizations/jsonv1/norm.go b/pkg/contexts/ocm/compdesc/normalizations/jsonv1/norm.go deleted file mode 100644 index e13971dba..000000000 --- a/pkg/contexts/ocm/compdesc/normalizations/jsonv1/norm.go +++ /dev/null @@ -1,34 +0,0 @@ -// Package jsonv1 provides a normalization which uses schema specific -// normalizations. -// It creates the requested schema for the component descriptor -// and just forwards the normalization to this version. -package jsonv1 - -import ( - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - "github.com/open-component-model/ocm/pkg/errkind" -) - -const Algorithm = compdesc.JsonNormalisationV1 - -func init() { - compdesc.Normalizations.Register(Algorithm, normalization{}) -} - -type normalization struct{} - -func (m normalization) Normalize(cd *compdesc.ComponentDescriptor) ([]byte, error) { - cv := compdesc.DefaultSchemes[cd.SchemaVersion()] - if cv == nil { - if cv == nil { - return nil, errors.ErrNotSupported(errkind.KIND_SCHEMAVERSION, cd.SchemaVersion()) - } - } - v, err := cv.ConvertFrom(cd) - if err != nil { - return nil, err - } - return v.Normalize(Algorithm) -} diff --git a/pkg/contexts/ocm/compdesc/normalizations/jsonv2/norm.go b/pkg/contexts/ocm/compdesc/normalizations/jsonv2/norm.go deleted file mode 100644 index 67f880990..000000000 --- a/pkg/contexts/ocm/compdesc/normalizations/jsonv2/norm.go +++ /dev/null @@ -1,64 +0,0 @@ -// Package jsonv2 provides a normalization which is completely based on the -// abstract (internal) version of the component descriptor and is therefore -// agnostic of the final serialization format. Signatures using this algorithm -// can be transferred among different schema versions, as long as is able to -// handle the complete information using for the normalization. -// Older format might omit some info, therefore the signatures cannot be -// validated for such representations, if the original component descriptor -// has used such parts. -package jsonv2 - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/normalizations/rules" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/norm/jcs" -) - -const Algorithm = compdesc.JsonNormalisationV2 - -func init() { - compdesc.Normalizations.Register(Algorithm, normalization{}) -} - -type normalization struct{} - -func (m normalization) Normalize(cd *compdesc.ComponentDescriptor) ([]byte, error) { - data, err := signing.Normalize(jcs.Type, cd, CDExcludes) - return data, err -} - -// CDExcludes describes the fields relevant for Signing -// ATTENTION: if changed, please adapt the Equivalent Functions -// in the generic part, accordingly. -var CDExcludes = signing.MapExcludes{ - "meta": nil, - "component": signing.MapExcludes{ - "repositoryContexts": nil, - "provider": signing.MapExcludes{ - "labels": rules.LabelExcludes, - }, - "labels": rules.LabelExcludes, - "resources": signing.DynamicArrayExcludes{ - ValueMapper: rules.MapResourcesWithNoneAccess, - Continue: signing.MapExcludes{ - "access": nil, - "srcRefs": nil, - "labels": rules.LabelExcludes, - }, - }, - "sources": signing.ArrayExcludes{ - Continue: signing.MapExcludes{ - "access": nil, - "labels": rules.LabelExcludes, - }, - }, - "references": signing.ArrayExcludes{ - signing.MapExcludes{ - "labels": rules.LabelExcludes, - }, - }, - }, - "signatures": nil, - "nestedDigests": nil, -} diff --git a/pkg/contexts/ocm/compdesc/signing.go b/pkg/contexts/ocm/compdesc/signing.go deleted file mode 100644 index 9b9a50da3..000000000 --- a/pkg/contexts/ocm/compdesc/signing.go +++ /dev/null @@ -1,246 +0,0 @@ -package compdesc - -import ( - "encoding/hex" - "fmt" - "hash" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/general" - - "github.com/open-component-model/ocm/pkg/contexts/credentials" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/signutils" -) - -const ( - KIND_HASH_ALGORITHM = signutils.KIND_HASH_ALGORITHM - KIND_SIGN_ALGORITHM = signutils.KIND_SIGN_ALGORITHM - KIND_NORM_ALGORITHM = signutils.KIND_NORM_ALGORITHM - KIND_VERIFY_ALGORITHM = signutils.KIND_VERIFY_ALGORITHM - KIND_PUBLIC_KEY = signutils.KIND_PUBLIC_KEY - KIND_PRIVATE_KEY = signutils.KIND_PRIVATE_KEY - KIND_SIGNATURE = signutils.KIND_SIGNATURE - KIND_DIGEST = signutils.KIND_DIGEST -) - -// IsNormalizeable checks if componentReferences and resources contain digest. -// Resources are allowed to omit the digest, if res.access.type == None or res.access == nil. -// Does NOT verify if the digests are correct. -func (cd *ComponentDescriptor) IsNormalizeable() error { - // check for digests on component references - for _, reference := range cd.References { - if reference.Digest == nil || reference.Digest.HashAlgorithm == "" || reference.Digest.NormalisationAlgorithm == "" || reference.Digest.Value == "" { - return fmt.Errorf("missing digest in componentReference for %s:%s", reference.Name, reference.Version) - } - } - for _, res := range cd.Resources { - if (res.Access != nil && res.Access.GetType() != "None") && res.Digest == nil { - return fmt.Errorf("missing digest in resource for %s:%s", res.Name, res.Version) - } - if (res.Access == nil || res.Access.GetType() == "None") && res.Digest != nil { - return fmt.Errorf("digest for resource with empty (None) access not allowed %s:%s", res.Name, res.Version) - } - } - return nil -} - -// Hash return the hash for the component-descriptor, if it is normalizeable -// (= componentReferences and resources contain digest field). -func Hash(cd *ComponentDescriptor, normAlgo string, hash hash.Hash) (string, error) { - if hash == nil { - return metav1.NoDigest, nil - } - - normalized, err := Normalize(cd, normAlgo) - if err != nil { - return "", fmt.Errorf("failed normalising component descriptor %w", err) - } - // fmt.Printf("NORM %s:%s: %s\n", cd.Name, cd.Version, string(normalized)) - hash.Reset() - if _, err = hash.Write(normalized); err != nil { - return "", fmt.Errorf("failed hashing the normalisedComponentDescriptorJson: %w", err) - } - - return hex.EncodeToString(hash.Sum(nil)), nil -} - -type CompDescDigest struct { - normAlgo string - hashAlgo string - normalized []byte - digest string -} - -type CompDescDigests struct { - cd *ComponentDescriptor - digests []*CompDescDigest -} - -func NewCompDescDigests(cd *ComponentDescriptor) *CompDescDigests { - return &CompDescDigests{ - cd: cd, - } -} - -func (d *CompDescDigests) Descriptor() *ComponentDescriptor { - return d.cd -} - -func (d *CompDescDigests) Get(normAlgo string, hasher signing.Hasher) ([]byte, string, error) { - var normalized []byte - - for _, e := range d.digests { - if e.normAlgo == normAlgo { - normalized = e.normalized - if e.hashAlgo == hasher.Algorithm() { - return e.normalized, e.digest, nil - } - } - } - - var err error - if normalized == nil { - normalized, err = Normalize(d.cd, normAlgo) - if err != nil { - return nil, "", fmt.Errorf("failed normalising component descriptor %w", err) - } - } - hash := hasher.Create() - if _, err = hash.Write(normalized); err != nil { - return nil, "", fmt.Errorf("failed hashing the normalisedComponentDescriptorJson: %w", err) - } - - e := &CompDescDigest{ - normAlgo: normAlgo, - hashAlgo: hasher.Algorithm(), - normalized: normalized, - digest: hex.EncodeToString(hash.Sum(nil)), - } - d.digests = append(d.digests, e) - return normalized, e.digest, nil -} - -func NormHash(cd *ComponentDescriptor, normAlgo string, hash hash.Hash) ([]byte, string, error) { - if hash == nil { - return nil, metav1.NoDigest, nil - } - - normalized, err := Normalize(cd, normAlgo) - if err != nil { - return nil, "", fmt.Errorf("failed normalising component descriptor %w", err) - } - hash.Reset() - if _, err = hash.Write(normalized); err != nil { - return nil, "", fmt.Errorf("failed hashing the normalisedComponentDescriptorJson: %w", err) - } - - return normalized, hex.EncodeToString(hash.Sum(nil)), nil -} - -// Sign signs the given component-descriptor with the signer. -// The component-descriptor has to contain digests for componentReferences and resources. -func Sign(cctx credentials.Context, cd *ComponentDescriptor, privateKey signutils.GenericPrivateKey, signer signing.Signer, hasher signing.Hasher, signatureName, issuer string) error { - digest, err := Hash(cd, JsonNormalisationV1, hasher.Create()) - if err != nil { - return fmt.Errorf("failed getting hash for cd: %w", err) - } - - iss, err := signutils.ParseDN(issuer) - if err != nil { - return err - } - - sctx := &signing.DefaultSigningContext{ - Hash: hasher.Crypto(), - PrivateKey: privateKey, - PublicKey: nil, - RootCerts: nil, - Issuer: iss, - } - - signature, err := signer.Sign(cctx, digest, sctx) - if err != nil { - return fmt.Errorf("failed signing hash of normalised component descriptor, %w", err) - } - signature.Issuer = issuer - cd.Signatures = append(cd.Signatures, metav1.Signature{ - Name: signatureName, - Digest: metav1.DigestSpec{ - HashAlgorithm: hasher.Algorithm(), - NormalisationAlgorithm: JsonNormalisationV1, - Value: digest, - }, - Signature: metav1.SignatureSpec{ - Algorithm: signature.Algorithm, - Value: signature.Value, - MediaType: signature.MediaType, - Issuer: signature.Issuer, - }, - }) - return nil -} - -// Verify verifies the signature (selected by signatureName) and hash of the component-descriptor (as specified in the signature). -// Does NOT resolve resources or referenced component-descriptors. -// Returns error if verification fails. -func Verify(cd *ComponentDescriptor, registry signing.Registry, signatureName string, rootCA ...signutils.GenericCertificatePool) error { - // find matching signature - matchingSignature := cd.SelectSignatureByName(signatureName) - if matchingSignature == nil { - return errors.ErrNotFound(KIND_SIGNATURE, signatureName) - } - verifier := registry.GetVerifier(matchingSignature.Signature.Algorithm) - if verifier == nil { - return errors.ErrUnknown(KIND_SIGN_ALGORITHM, matchingSignature.Signature.Algorithm) - } - publicKey := registry.GetPublicKey(signatureName) - if publicKey == nil { - return errors.ErrNotFound(KIND_PUBLIC_KEY, signatureName) - } - hasher := registry.GetHasher(matchingSignature.Digest.HashAlgorithm) - if hasher == nil { - return errors.ErrUnknown(KIND_HASH_ALGORITHM, matchingSignature.Digest.HashAlgorithm) - } - // Verify author of signature - sctx := &signing.DefaultSigningContext{ - Hash: hasher.Crypto(), - PublicKey: publicKey, - RootCerts: general.Optional(rootCA), - Issuer: registry.GetIssuer(signatureName), - } - err := verifier.Verify(matchingSignature.Digest.Value, matchingSignature.ConvertToSigning(), sctx) - if err != nil { - return fmt.Errorf("failed verifying: %w", err) - } - - hash := hasher.Create() - // Verify normalised cd to given (and verified) hash - calculatedDigest, err := Hash(cd, matchingSignature.Digest.NormalisationAlgorithm, hash) - if err != nil { - return fmt.Errorf("failed hashing cd %s:%s: %w", cd.Name, cd.Version, err) - } - - if calculatedDigest != matchingSignature.Digest.Value { - return fmt.Errorf("normalised component-descriptor does not match hash from signature") - } - - return nil -} - -// SelectSignatureByName returns the Signature (Digest and SigantureSpec) matching the given name. -func (cd *ComponentDescriptor) SelectSignatureByName(signatureName string) *metav1.Signature { - for _, signature := range cd.Signatures { - if signature.Name == signatureName { - return &signature - } - } - return nil -} - -//////////////////////////////////////////////////////////////////////////////// - -func (cd *ComponentDescriptor) HasResourceDigests() bool { - return cd.Resources.HaveDigests() -} diff --git a/pkg/contexts/ocm/compdesc/versions/init.go b/pkg/contexts/ocm/compdesc/versions/init.go deleted file mode 100644 index bc607a797..000000000 --- a/pkg/contexts/ocm/compdesc/versions/init.go +++ /dev/null @@ -1,6 +0,0 @@ -package versions - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/versions/v2" -) diff --git a/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/componentdescriptor.go b/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/componentdescriptor.go deleted file mode 100644 index b4626f75b..000000000 --- a/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/componentdescriptor.go +++ /dev/null @@ -1,283 +0,0 @@ -package v3alpha1 - -import ( - "errors" - - "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/runtime" -) - -var ErrNotFound = errors.New("NotFound") - -// ComponentDescriptor defines a versioned component with a source and dependencies. -type ComponentDescriptor struct { - // TypeMeta specifies the schema version of the component. - metav1.TypeMeta `json:",inline"` - // Spec contains the specification of the component. - metav1.ObjectMeta `json:"metadata"` - - // RepositoryContexts defines the previous repositories of the component - RepositoryContexts runtime.UnstructuredTypedObjectList `json:"repositoryContexts"` - - Spec ComponentVersionSpec `json:"spec"` - // Signatures contains a list of signatures for the ComponentDescriptor - Signatures metav1.Signatures `json:"signatures,omitempty"` - // NestedDigests described digest information of resources in aggregated - // omponent versions. - NestedDigests metav1.NestedDigests `json:"nestedDigests,omitempty"` -} - -var _ compdesc.ComponentDescriptorVersion = (*ComponentDescriptor)(nil) - -// SchemeVersion returns the actual scheme version of this component descriptor -// representation. -func (cd *ComponentDescriptor) SchemaVersion() string { - if cd.APIVersion == "" { - return SchemaVersion - } - return cd.APIVersion -} - -func (cd *ComponentDescriptor) GetName() string { - return cd.Name -} - -// ComponentVersionSpec defines a virtual component with -// a repository context, source and dependencies. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type ComponentVersionSpec struct { - // Sources defines sources that produced the component - Sources Sources `json:"sources,omitempty"` - // References references component version dependencies that can be resolved in the current context. - References References `json:"references,omitempty"` - // Resources defines all resources that are created by the component and by a third party. - Resources Resources `json:"resources,omitempty"` -} - -const ( - SystemIdentityName = metav1.SystemIdentityName - SystemIdentityVersion = metav1.SystemIdentityVersion -) - -// ElementMetaAccessor provides generic access an elements meta information. -type ElementMetaAccessor interface { - GetMeta() *ElementMeta -} - -// ElementAccessor provides generic access to list of elements. -type ElementAccessor interface { - Len() int - Get(i int) ElementMetaAccessor -} - -// ElementMeta defines a object that is uniquely identified by its identity. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type ElementMeta struct { - // Name is the context unique name of the object. - Name string `json:"name"` - // Version is the semver version of the object. - Version string `json:"version"` - // ExtraIdentity is the identity of an object. - // An additional label with key "name" ist not allowed - ExtraIdentity metav1.Identity `json:"extraIdentity,omitempty"` - // Labels defines an optional set of additional labels - // describing the object. - // +optional - Labels metav1.Labels `json:"labels,omitempty"` -} - -// GetName returns the name of the object. -func (o *ElementMeta) GetName() string { - return o.Name -} - -// GetMeta returns the element meta. -func (o *ElementMeta) GetMeta() *ElementMeta { - return o -} - -// SetName sets the name of the object. -func (o *ElementMeta) SetName(name string) { - o.Name = name -} - -// GetVersion returns the version of the object. -func (o *ElementMeta) GetVersion() string { - return o.Version -} - -// SetVersion sets the version of the object. -func (o *ElementMeta) SetVersion(version string) { - o.Version = version -} - -// GetLabels returns the label of the object. -func (o *ElementMeta) GetLabels() metav1.Labels { - return o.Labels -} - -// SetLabels sets the labels of the object. -func (o *ElementMeta) SetLabels(labels []metav1.Label) { - o.Labels = labels -} - -// SetExtraIdentity sets the identity of the object. -func (o *ElementMeta) SetExtraIdentity(identity metav1.Identity) { - o.ExtraIdentity = identity -} - -// GetIdentity returns the identity of the object. -func (o *ElementMeta) GetIdentity(accessor ElementAccessor) metav1.Identity { - identity := o.ExtraIdentity.Copy() - if identity == nil { - identity = metav1.Identity{} - } - identity[SystemIdentityName] = o.Name - if accessor != nil { - found := false - l := accessor.Len() - for i := 0; i < l; i++ { - m := accessor.Get(i).GetMeta() - if m.Name == o.Name && m.ExtraIdentity.Equals(o.ExtraIdentity) { - if found { - identity[SystemIdentityVersion] = o.Version - break - } - found = true - } - } - } - return identity -} - -// GetIdentityDigest returns the digest of the object's identity. -func (o *ElementMeta) GetIdentityDigest(accessor ElementAccessor) []byte { - return o.GetIdentity(accessor).Digest() -} - -// Sources describes a set of source specifications. -type Sources []Source - -func (r Sources) Len() int { - return len(r) -} - -func (r Sources) Get(i int) ElementMetaAccessor { - return &r[i] -} - -// Source is the definition of a component's source. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type Source struct { - SourceMeta `json:",inline"` - - Access *runtime.UnstructuredTypedObject `json:"access"` -} - -// SourceMeta is the definition of the metadata of a source. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type SourceMeta struct { - ElementMeta `json:",inline"` - // Type describes the type of the object. - Type string `json:"type"` -} - -// GetType returns the type of the object. -func (o SourceMeta) GetType() string { - return o.Type -} - -// SetType sets the type of the object. -func (o *SourceMeta) SetType(ttype string) { - o.Type = ttype -} - -// SourceRef defines a reference to a source -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type SourceRef struct { - // IdentitySelector defines the identity that is used to match a source. - IdentitySelector metav1.StringMap `json:"identitySelector,omitempty"` - // Labels defines an optional set of additional labels - // describing the object. - // +optional - Labels metav1.Labels `json:"labels,omitempty"` -} - -// Resources describes a set of resource specifications. -type Resources []Resource - -func (r Resources) Len() int { - return len(r) -} - -func (r Resources) Get(i int) ElementMetaAccessor { - return &r[i] -} - -// Resource describes a resource dependency of a component. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type Resource struct { - ElementMeta `json:",inline"` - - // Type describes the type of the object. - Type string `json:"type"` - - // Relation describes the relation of the resource to the component. - // Can be a local or external resource - Relation metav1.ResourceRelation `json:"relation,omitempty"` - - // SourceRefs defines a list of source names. - // These entries reference the sources defined in the - // component.sources. - SourceRefs []SourceRef `json:"srcRefs,omitempty"` - // SourceRef is for deserialization compatibility, only. - // The usage of this field in external formats is deprecated. - SourceRef []SourceRef `json:"srcRef,omitempty"` - - // Access describes the type specific method to - // access the defined resource. - Access *runtime.UnstructuredTypedObject `json:"access"` - - // Digest is the optional digest of the referenced resource. - // +optional - Digest *metav1.DigestSpec `json:"digest,omitempty"` -} - -// GetType returns the type of the object. -func (r Resource) GetType() string { - return r.Type -} - -// SetType sets the type of the object. -func (r *Resource) SetType(ttype string) { - r.Type = ttype -} - -type References []Reference - -func (r References) Len() int { - return len(r) -} - -func (r References) Get(i int) ElementMetaAccessor { - return &r[i] -} - -// Reference describes the reference to another component in the registry. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type Reference struct { - ElementMeta `json:",inline"` - // ComponentName describes the remote name of the referenced object - ComponentName string `json:"componentName"` - // Digest is the optional digest of the referenced component. - // +optional - Digest *metav1.DigestSpec `json:"digest,omitempty"` -} diff --git a/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/default.go b/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/default.go deleted file mode 100644 index 9534ce1f9..000000000 --- a/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/default.go +++ /dev/null @@ -1,49 +0,0 @@ -package v3alpha1 - -import ( - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/runtime" -) - -// Default applies defaults to a component. -func (cd *ComponentDescriptor) Default() error { - if cd.RepositoryContexts == nil { - cd.RepositoryContexts = make([]*runtime.UnstructuredTypedObject, 0) - } - if cd.Spec.Sources == nil { - cd.Spec.Sources = make([]Source, 0) - } - if cd.Spec.References == nil { - cd.Spec.References = make([]Reference, 0) - } - if cd.Spec.Resources == nil { - cd.Spec.Resources = make([]Resource, 0) - } - - DefaultResources(cd) - return nil -} - -// DefaultResources defaults a list of resources. -// The version of the component is defaulted for local resources that do not contain a version. -// adds the version as identity if the resource identity would clash otherwise. -func DefaultResources(component *ComponentDescriptor) { - for i, res := range component.Spec.Resources { - if res.Relation == v1.LocalRelation && len(res.Version) == 0 { - component.Spec.Resources[i].Version = component.GetVersion() - } - - id := res.GetIdentity(component.Spec.Resources) - if v, ok := id[SystemIdentityVersion]; ok { - if res.ExtraIdentity == nil { - res.ExtraIdentity = v1.Identity{ - SystemIdentityVersion: v, - } - } else { - if _, ok := res.ExtraIdentity[SystemIdentityVersion]; !ok { - res.ExtraIdentity[SystemIdentityVersion] = v - } - } - } - } -} diff --git a/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/jsonscheme/bindata.go b/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/jsonscheme/bindata.go deleted file mode 100644 index c12ba7457..000000000 --- a/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/jsonscheme/bindata.go +++ /dev/null @@ -1,265 +0,0 @@ -// Code generated by go-bindata. (@generated) DO NOT EDIT. - -//Package jsonscheme generated by go-bindata.// sources: -// ../../../../../../../../resources/component-descriptor-ocm-v3-schema.yaml -package jsonscheme - -import ( - "bytes" - "compress/gzip" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "strings" - "time" -) - -func bindataRead(data []byte, name string) ([]byte, error) { - gz, err := gzip.NewReader(bytes.NewBuffer(data)) - if err != nil { - return nil, fmt.Errorf("read %q: %v", name, err) - } - - var buf bytes.Buffer - _, err = io.Copy(&buf, gz) - clErr := gz.Close() - - if err != nil { - return nil, fmt.Errorf("read %q: %v", name, err) - } - if clErr != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -type asset struct { - bytes []byte - info os.FileInfo -} - -type bindataFileInfo struct { - name string - size int64 - mode os.FileMode - modTime time.Time -} - -// Name return file name -func (fi bindataFileInfo) Name() string { - return fi.name -} - -// Size return file size -func (fi bindataFileInfo) Size() int64 { - return fi.size -} - -// Mode return file mode -func (fi bindataFileInfo) Mode() os.FileMode { - return fi.mode -} - -// ModTime return file modify time -func (fi bindataFileInfo) ModTime() time.Time { - return fi.modTime -} - -// IsDir return file whether a directory -func (fi bindataFileInfo) IsDir() bool { - return fi.mode&os.ModeDir != 0 -} - -// Sys return file is sys mode -func (fi bindataFileInfo) Sys() interface{} { - return nil -} - -var _ResourcesComponentDescriptorOcmV3SchemaYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xe4\x19\x5f\x6f\xdb\xb6\xf3\xdd\x9f\xe2\x80\x04\x90\xdd\x44\x76\xe2\xa2\x0f\xd5\x4b\x50\xb4\x2f\x3f\xfc\xb6\x75\x58\x8b\x3d\x2c\xf5\x02\x46\x3a\xd9\x4c\x25\x52\x23\x69\x27\x5e\x9b\xef\x3e\x90\x14\x25\x4a\x96\x1c\xcb\x49\x1f\x86\xbd\x24\xe2\xf1\xee\x78\xbc\xff\x47\x9f\xd2\x24\x82\x60\xa5\x54\x21\xa3\xd9\x6c\x49\x44\x82\x0c\xc5\x34\xce\xf8\x3a\x99\xc9\x78\x85\x39\x91\xb3\x98\xe7\x05\x67\xc8\x54\x98\xa0\x8c\x05\x2d\x14\x17\x21\x8f\xf3\x70\xf3\x9a\x64\xc5\x8a\x5c\x06\xa3\x53\x8b\xeb\xf1\xba\x93\x9c\x85\x16\x3a\xe5\x62\x39\x4b\x04\x49\xd5\x6c\x7e\x31\xbf\x08\x2f\xe7\x25\xeb\x60\xe4\x18\x52\xce\x22\x08\x3e\xbe\xff\x19\xde\xbb\xc3\xe0\x43\x75\x18\x6c\x5e\x83\xa3\x38\x4d\x30\x95\xd1\x08\x20\x47\x45\xf4\x7f\x00\xb5\x2d\x30\x82\x80\xdf\xde\x61\xac\x02\x03\x6a\xf2\xad\x2e\x00\x1b\x14\x92\x72\x66\x88\x13\xa2\x88\xc5\x16\xf8\xd7\x9a\x0a\x4c\x2c\x3b\x80\x10\x02\x46\x72\x0c\xea\x65\x49\x67\x21\x24\x49\xa8\xe6\x4c\xb2\x5f\x05\x2f\x50\x28\x8a\x32\x82\x94\x64\x12\xcd\x7e\x51\x43\x4b\x0e\x9a\x9b\xfb\x06\x38\x15\x98\x46\x10\x9c\xcc\xcc\x5d\x6a\xf5\xfe\xe2\x9d\x59\x1e\xd8\x4b\x24\x30\x23\x0f\x98\x7c\xc2\x7c\x83\xc2\x11\x65\xe4\x16\x33\xd9\x4b\x63\xb7\x1d\x72\x21\xf8\x86\x26\x28\x7a\xd1\x1d\x82\x23\x88\x05\x12\x7d\xed\xcf\xd4\xbf\x8c\x55\xbe\x54\x82\xb2\x65\x05\x4c\xb9\xc8\x89\x8a\x20\x21\x0a\x43\x45\x73\x1c\x19\x83\x89\x25\xf6\x5a\x6c\x57\x69\x24\x5b\x72\x41\xd5\x2a\xaf\x0f\x2b\x88\x52\x28\xb4\x49\xff\xbc\x26\xe1\xdf\x0b\xfd\xe7\x22\x7c\x3b\xbb\x09\x17\x67\xa7\x95\x9c\x9c\xa5\x74\x19\xc1\x37\x78\x3c\xc0\x5c\xbe\xce\x4a\xb1\x88\x10\x64\x6b\xb9\x51\x85\x79\x25\x50\x97\x3a\x03\xc7\xa2\xf7\x62\x07\x38\x17\xc9\xd6\xd8\xa7\x85\xa6\xeb\x74\x68\xdb\x50\x47\xf0\xed\xb1\xcf\x73\x3c\xa5\x6d\xae\x2f\xc2\xb7\x9e\xaa\x24\x5d\x32\xca\x96\x6d\xfe\xc1\x2d\xe7\x19\x12\xe6\xd0\x3c\xcb\x75\xe8\xc1\xec\x3e\x1d\x19\x23\x6d\x19\xcf\xd3\x1b\x0a\xb3\x37\xb2\x4c\x72\xf2\xf0\x13\xb2\xa5\x5a\x45\x30\x7f\xf3\x66\xd4\x69\xf7\xd0\x1a\x7e\xf1\x6a\x7c\x3d\x5d\xb4\x40\x93\x57\x0e\xf6\x6d\x7e\xfe\x38\x9e\x35\xb6\x6f\x3a\x48\x6e\x34\xcd\x44\x6b\x65\x04\x40\x13\x64\x8a\xaa\xed\x3b\xa5\x04\xbd\x5d\x2b\xfc\x3f\x6e\xad\xa8\x39\x65\x95\x5c\x5d\x52\xe9\xc3\xc7\xd7\xe1\xcd\x99\x13\xc4\x01\x27\x57\x96\x75\x23\x66\x2d\xcf\x13\x50\xe4\x2b\x32\x48\x05\xcf\x41\x9a\x0d\x9d\x2d\x81\xb0\x04\x48\x72\xb7\x96\x0a\x13\x50\x1c\x48\x96\xf1\x7b\x20\x0c\x78\x61\xf5\x0b\x19\x92\x84\xb2\x25\x04\x9b\xe0\x1c\x72\x72\xa7\x53\x32\xcb\xb6\xe7\x86\xd4\xac\xa7\x39\x65\x25\xd4\x9d\xb5\xa2\x12\x72\x24\x4c\x82\x5a\x21\xa4\x5c\x73\xd5\x4c\xac\xfa\x25\x10\x81\xfa\x28\xed\x53\x34\x69\xca\x2b\x9d\xc0\x97\xd3\xf9\xf4\xb5\xff\x1d\xa6\x9c\x9f\xdd\x12\x51\xc2\x36\x3e\xc2\xa6\x0b\xe3\x72\x3a\x77\x5f\x15\x9a\x87\x5f\x7d\x36\xc8\x7c\x65\x6f\x16\x57\xe3\x8b\xef\xd7\x97\xe1\xdb\xc5\x97\xe4\xd5\x64\x7c\x15\x7d\x99\xfa\x80\xc9\x55\x37\x28\x1c\x8f\xaf\xa2\x1a\xf8\xfd\x4b\x62\x6c\xf4\x2e\xfc\x23\x5c\xe8\xc8\x70\xdf\x8e\xe5\x81\xc8\x13\x77\xe2\xd9\xd8\xdf\x38\x33\x4c\x1a\x10\x83\x59\x46\x5f\xcb\xf3\xbb\x5c\xef\xa9\x64\xb9\xd5\x71\x24\x75\xa6\x6b\x85\x64\x97\x13\x07\xf0\x68\x9d\xb0\xe0\x92\x2a\x2e\xb6\xef\x39\x53\xf8\xa0\x86\x24\x2e\x8d\xd5\x97\xa8\x0c\x87\x76\x22\xf1\x6e\x47\xe2\x18\xa5\x3c\xb0\x62\xdf\x12\x89\x06\x4b\x97\x92\x92\x14\x25\x8c\xf5\x0a\x1f\x14\x32\x9d\xe2\xe4\xe4\x09\x41\x47\x00\x92\xaf\x45\x8c\x1f\x30\xa5\xcc\x64\xa6\x01\xb7\xd5\x99\xb7\x5a\x94\x59\xb5\x5a\x6b\x0e\xd5\xc2\xca\x37\x20\x81\x37\xf2\x5d\x47\x4a\xed\xb4\x5f\x89\x8c\x0f\x4a\x90\xff\x95\x08\xbd\x49\x79\x87\xc3\xb3\x1a\x8b\xbd\xb6\xb5\xc0\x41\xbd\x87\xef\x0b\x1d\xc8\x76\xdb\xd8\x2f\xa1\x4b\x94\xea\x53\x81\xf1\x00\xcb\xad\x88\x5c\xbd\x73\xdd\x43\x6d\x4f\xdd\x94\x64\x54\x9a\x26\x66\x77\xdb\xd4\xd1\x23\xfb\xbb\xc6\x81\x7b\xab\x75\xb7\x10\x07\x14\xf8\x6e\x8c\x11\x80\x6e\xaf\xa4\x22\x79\xd1\x56\x92\xd5\x51\x8f\xc4\xfb\x98\x96\xa0\x23\xdb\x3c\xdd\x53\x10\xb5\x16\x38\xd0\x68\x64\x8f\x45\xf4\x2a\xc7\x84\x92\xcf\x2e\xec\x86\xdb\xa8\xa3\x9d\x1c\xa8\x6c\x0b\xaa\xe4\xa8\xb1\x9a\xb9\xeb\xf3\x0a\x2d\x92\x4d\x60\x3c\x35\xc5\xb6\x52\x0b\x78\xed\x5e\x97\x3d\x2b\xc4\x63\x53\x95\x0d\x99\x6a\x59\xf1\x7b\x91\xd9\xa5\x43\x21\xf6\xbc\xde\x60\xae\x23\xd8\xef\x3a\xbd\x1b\x76\xd0\x34\x7c\x28\xf0\x1c\xd2\x38\x7a\x2f\x59\x23\x14\x4c\xfa\x60\xa8\x3b\xa8\x0f\xc7\x24\x91\x3e\x9d\x1e\xd3\xa2\xb7\x73\x6e\x07\xce\x33\xd3\xfa\x00\x23\x54\x6a\xa9\x46\x6d\xab\x9f\xfe\xda\x3c\xb0\x36\xb6\x1c\x50\x60\x59\x84\xed\x29\x2f\xe2\x86\x3f\x6c\x84\x1e\xec\xcc\xad\xdb\xed\x54\x49\x6f\x98\x84\xf6\x40\xd9\x71\x40\xdb\x61\x6d\x13\x23\xe2\xdf\x30\x3d\xb0\x75\x22\x20\x30\x45\x81\x2c\x46\x33\x39\xc0\xb8\x7e\xbf\xc9\x78\x4c\xb2\x49\xd9\x14\x1d\xfb\x98\xe1\x7c\xf0\x13\x66\x18\x2b\xde\xff\x80\xd0\xeb\xac\x07\xf6\x0a\xa6\x5b\x2d\xaf\x72\xec\xe5\xab\xbb\x1f\x3a\x8c\x77\xba\xd2\xf3\x5f\x80\x3a\x46\xdf\x43\xfd\x78\x5f\x03\x09\x27\x40\x62\xb5\x26\x59\xb6\x8d\xea\x33\x42\x53\x78\xee\x67\x20\x0b\x8c\x29\xc9\xb4\x93\x2a\x41\x63\x2d\xb2\xfc\xb7\xf4\x9c\x83\x1a\xca\x76\xd8\x72\x86\x1f\x53\x3f\xcc\x42\xa7\x38\xb6\xce\xb2\xa0\xb1\xb1\x3f\x55\x56\xf1\xfd\xf4\x08\xb1\x6f\x84\x71\x6c\xe4\xd0\x17\x47\x38\x31\xf4\x26\x72\x6b\x2e\xe7\xe5\x2c\xbf\x96\x0a\x72\xa2\xe2\x95\xe7\xe8\x72\x27\x21\x7b\x73\x9b\x59\x6a\x7d\xab\xca\x99\x0d\xc8\xb5\xdc\xdd\xde\xfb\x1f\x99\x61\x6c\x9e\x7d\x76\x0e\xb7\x6c\xea\x02\x61\x95\xfd\xa4\xfe\x90\xad\xf3\x08\xae\x03\x63\xea\xe0\x1c\x02\x3d\xe8\x0a\x46\xb2\x60\x71\x4c\x48\x1c\x38\x63\xfd\xe8\xf8\x69\x3e\x33\x0f\x7d\x1f\x7d\x99\x76\xf5\xb8\x51\x55\x17\xde\x7d\x2d\x63\x33\xdc\x4d\xaa\x4d\x69\x6c\x6c\xed\x9a\xfe\x98\x33\x85\x4c\xe9\xa5\x57\x8a\x9c\xff\xaa\x63\xef\x58\x26\x81\x67\xfb\x69\x2b\xb1\xd5\x1e\x5b\x96\xd0\x67\x9f\x50\x71\x6a\x77\x4b\x2f\xc0\x79\x57\xfa\x51\xcb\x48\xbe\x67\xe9\x14\x57\xd0\xdf\xeb\x22\x1e\x42\xf0\x95\xb2\xa4\xfc\xf4\x7f\x0f\x0a\xad\x31\x83\x51\x53\xf1\x35\x79\xef\xcb\x75\x19\xc1\x10\xf0\x38\x9f\xb6\x7e\x4c\xab\x7e\x2b\x3b\xb7\xdb\x92\xa7\xea\x9e\x08\xac\x37\x40\x87\xb9\x96\xa9\x97\x7f\xcc\x99\x54\x11\x04\x55\xe3\xee\xdd\xc7\xdd\xc0\x12\xef\x3c\xd1\xdb\xab\xed\x3c\xfe\x1d\xf5\xc3\xc7\x0e\x17\x5d\xa4\xe2\xb5\x10\xc8\x54\xb6\x3d\x87\x7b\x04\xce\xb2\x6d\xf9\x68\x6d\x0a\x15\x67\xd8\x08\xa7\xb6\x27\x96\x0d\x75\x35\xf7\x1d\x25\x57\x45\x1d\xb4\x26\xbf\xa3\xb8\x75\xcf\x48\xc1\x3f\x01\x00\x00\xff\xff\x5a\x12\x6d\xad\x32\x1d\x00\x00") - -func ResourcesComponentDescriptorOcmV3SchemaYamlBytes() ([]byte, error) { - return bindataRead( - _ResourcesComponentDescriptorOcmV3SchemaYaml, - "../../../../../../../../resources/component-descriptor-ocm-v3-schema.yaml", - ) -} - -func ResourcesComponentDescriptorOcmV3SchemaYaml() (*asset, error) { - bytes, err := ResourcesComponentDescriptorOcmV3SchemaYamlBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "../../../../../../../../resources/component-descriptor-ocm-v3-schema.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info} - return a, nil -} - -// Asset loads and returns the asset for the given name. -// It returns an error if the asset could not be found or -// could not be loaded. -func Asset(name string) ([]byte, error) { - cannonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[cannonicalName]; ok { - a, err := f() - if err != nil { - return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) - } - return a.bytes, nil - } - return nil, fmt.Errorf("Asset %s not found", name) -} - -// MustAsset is like Asset but panics when Asset would return an error. -// It simplifies safe initialization of global variables. -func MustAsset(name string) []byte { - a, err := Asset(name) - if err != nil { - panic("asset: Asset(" + name + "): " + err.Error()) - } - - return a -} - -// AssetInfo loads and returns the asset info for the given name. -// It returns an error if the asset could not be found or -// could not be loaded. -func AssetInfo(name string) (os.FileInfo, error) { - cannonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[cannonicalName]; ok { - a, err := f() - if err != nil { - return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) - } - return a.info, nil - } - return nil, fmt.Errorf("AssetInfo %s not found", name) -} - -// AssetNames returns the names of the assets. -func AssetNames() []string { - names := make([]string, 0, len(_bindata)) - for name := range _bindata { - names = append(names, name) - } - return names -} - -// _bindata is a table, holding each asset generator, mapped to its name. -var _bindata = map[string]func() (*asset, error){ - "../../../../../../../../resources/component-descriptor-ocm-v3-schema.yaml": ResourcesComponentDescriptorOcmV3SchemaYaml, -} - -// AssetDir returns the file names below a certain -// directory embedded in the file by go-bindata. -// For example if you run go-bindata on data/... and data contains the -// following hierarchy: -// -// data/ -// foo.txt -// img/ -// a.png -// b.png -// -// then AssetDir("data") would return []string{"foo.txt", "img"} -// AssetDir("data/img") would return []string{"a.png", "b.png"} -// AssetDir("foo.txt") and AssetDir("notexist") would return an error -// AssetDir("") will return []string{"data"}. -func AssetDir(name string) ([]string, error) { - node := _bintree - if len(name) != 0 { - cannonicalName := strings.Replace(name, "\\", "/", -1) - pathList := strings.Split(cannonicalName, "/") - for _, p := range pathList { - node = node.Children[p] - if node == nil { - return nil, fmt.Errorf("Asset %s not found", name) - } - } - } - if node.Func != nil { - return nil, fmt.Errorf("Asset %s not found", name) - } - rv := make([]string, 0, len(node.Children)) - for childName := range node.Children { - rv = append(rv, childName) - } - return rv, nil -} - -type bintree struct { - Func func() (*asset, error) - Children map[string]*bintree -} - -var _bintree = &bintree{nil, map[string]*bintree{ - "..": {nil, map[string]*bintree{ - "..": {nil, map[string]*bintree{ - "..": {nil, map[string]*bintree{ - "..": {nil, map[string]*bintree{ - "..": {nil, map[string]*bintree{ - "..": {nil, map[string]*bintree{ - "..": {nil, map[string]*bintree{ - "..": {nil, map[string]*bintree{ - "resources": {nil, map[string]*bintree{ - "component-descriptor-ocm-v3-schema.yaml": {ResourcesComponentDescriptorOcmV3SchemaYaml, map[string]*bintree{}}, - }}, - }}, - }}, - }}, - }}, - }}, - }}, - }}, - }}, -}} - -// RestoreAsset restores an asset under the given directory -func RestoreAsset(dir, name string) error { - data, err := Asset(name) - if err != nil { - return err - } - info, err := AssetInfo(name) - if err != nil { - return err - } - err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) - if err != nil { - return err - } - err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) - if err != nil { - return err - } - err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) - if err != nil { - return err - } - return nil -} - -// RestoreAssets restores an asset under the given directory recursively -func RestoreAssets(dir, name string) error { - children, err := AssetDir(name) - // File - if err != nil { - return RestoreAsset(dir, name) - } - // Dir - for _, child := range children { - err = RestoreAssets(dir, filepath.Join(name, child)) - if err != nil { - return err - } - } - return nil -} - -func _filePath(dir, name string) string { - cannonicalName := strings.Replace(name, "\\", "/", -1) - return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) -} diff --git a/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/jsonscheme/jsonscheme.go b/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/jsonscheme/jsonscheme.go deleted file mode 100644 index 707e17573..000000000 --- a/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/jsonscheme/jsonscheme.go +++ /dev/null @@ -1,55 +0,0 @@ -//go:generate go-bindata -nometadata -pkg jsonscheme ../../../../../../../../resources/component-descriptor-ocm-v3-schema.yaml -//go:generate gofmt -s -w bindata.go - -package jsonscheme - -import ( - "errors" - "fmt" - - "github.com/ghodss/yaml" - "github.com/xeipuuv/gojsonschema" -) - -var Schema *gojsonschema.Schema - -func init() { - dataBytes, err := ResourcesComponentDescriptorOcmV3SchemaYamlBytes() - if err != nil { - panic(err) - } - - data, err := yaml.YAMLToJSON(dataBytes) - if err != nil { - panic(err) - } - - Schema, err = gojsonschema.NewSchema(gojsonschema.NewBytesLoader(data)) - if err != nil { - panic(err) - } -} - -// Validate validates the given data against the component descriptor v2 jsonscheme. -func Validate(src []byte) error { - data, err := yaml.YAMLToJSON(src) - if err != nil { - return err - } - documentLoader := gojsonschema.NewBytesLoader(data) - res, err := Schema.Validate(documentLoader) - if err != nil { - return err - } - - if !res.Valid() { - errs := res.Errors() - errMsg := errs[0].String() - for i := 1; i < len(errs); i++ { - errMsg = fmt.Sprintf("%s;%s", errMsg, errs[i].String()) - } - return errors.New(errMsg) - } - - return nil -} diff --git a/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/signing.go b/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/signing.go deleted file mode 100644 index 652621fb3..000000000 --- a/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/signing.go +++ /dev/null @@ -1,54 +0,0 @@ -package v3alpha1 - -import ( - "fmt" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/normalizations/rules" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/norm/entry" -) - -// CDExcludes describes the fields relevant for Signing -// ATTENTION: if changed, please adapt the HashEqual Functions -// in the generic part, accordingly. -var CDExcludes = signing.MapExcludes{ - "repositoryContexts": nil, - "metadata": signing.MapExcludes{ - "labels": rules.LabelExcludes, - }, - "spec": signing.MapExcludes{ - "provider": signing.MapExcludes{ - "labels": rules.LabelExcludes, - }, - "resources": signing.DynamicArrayExcludes{ - ValueMapper: rules.MapResourcesWithNoneAccess, - Continue: signing.MapExcludes{ - "access": nil, - "srcRefs": nil, - "labels": rules.LabelExcludes, - }, - }, - "sources": signing.ArrayExcludes{ - Continue: signing.MapExcludes{ - "access": nil, - "labels": rules.LabelExcludes, - }, - }, - "references": signing.ArrayExcludes{ - signing.MapExcludes{ - "labels": rules.LabelExcludes, - }, - }, - }, - "signatures": nil, - "nestedDigests": nil, -} - -func (cd *ComponentDescriptor) Normalize(normAlgo string) ([]byte, error) { - if normAlgo != compdesc.JsonNormalisationV1 { - return nil, fmt.Errorf("unsupported cd normalization %q", normAlgo) - } - data, err := signing.Normalize(entry.Type, cd, CDExcludes) - return data, err -} diff --git a/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/validation.go b/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/validation.go deleted file mode 100644 index cec517bfd..000000000 --- a/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/validation.go +++ /dev/null @@ -1,202 +0,0 @@ -package v3alpha1 - -import ( - "fmt" - - "github.com/mandelsoft/goutils/errors" - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" -) - -// Validate validates a parsed v2 component descriptor. -func (cd *ComponentDescriptor) Validate() error { - if err := Validate(nil, cd); err != nil { - return errors.Wrapf(err.ToAggregate(), "%s:%s", cd.Name, cd.Version) - } - return nil -} - -func Validate(fldPath *field.Path, component *ComponentDescriptor) field.ErrorList { - if component == nil { - return nil - } - allErrs := field.ErrorList{} - - if len(component.APIVersion) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("apiVersion"), "must specify a version")) - } - if len(component.Kind) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("kind"), "must specify kind "+Kind)) - } - if component.Kind != Kind { - allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), component.Kind, "must be "+Kind)) - } - - metaPath := fldPath.Child("metadata") - if err := validateProvider(metaPath.Child("provider"), component.Provider); err != nil { - allErrs = append(allErrs, err) - } - - allErrs = append(allErrs, ValidateObjectMeta(metaPath, component)...) - - specPath := fldPath.Child("spec") - srcPath := specPath.Child("sources") - allErrs = append(allErrs, ValidateSources(srcPath, component.Spec.Sources)...) - - refPath := specPath.Child("references") - allErrs = append(allErrs, ValidateComponentReferences(refPath, component.Spec.References)...) - - resourcePath := specPath.Child("resources") - allErrs = append(allErrs, ValidateResources(resourcePath, component.Spec.Resources, component.GetVersion())...) - - return allErrs -} - -// ValidateObjectMeta Validate the metadata of an object. -func ValidateObjectMeta(fldPath *field.Path, om compdesc.ObjectMetaAccessor) field.ErrorList { - allErrs := field.ErrorList{} - if len(om.GetName()) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("name"), "must specify a name")) - } - if len(om.GetVersion()) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("version"), "must specify a version")) - } - if len(om.GetLabels()) != 0 { - allErrs = append(allErrs, metav1.ValidateLabels(fldPath.Child("labels"), om.GetLabels())...) - } - return allErrs -} - -// ValidateSources validates a list of sources. -// It makes sure that no duplicate sources are present. -func ValidateSources(fldPath *field.Path, sources Sources) field.ErrorList { - allErrs := field.ErrorList{} - sourceIDs := make(map[string]struct{}) - for i, src := range sources { - srcPath := fldPath.Index(i) - allErrs = append(allErrs, ValidateSource(srcPath, src, false)...) - - id := src.GetIdentity(sources) - dig := string(id.Digest()) - if _, ok := sourceIDs[dig]; ok { - allErrs = append(allErrs, field.Duplicate(srcPath, fmt.Sprintf("duplicate source %s", id))) - continue - } - sourceIDs[dig] = struct{}{} - } - return allErrs -} - -// ValidateSource validates the a component's source object. -func ValidateSource(fldPath *field.Path, src Source, access bool) field.ErrorList { - allErrs := field.ErrorList{} - if len(src.GetName()) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("name"), "must specify a name")) - } - if len(src.GetType()) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must specify a type")) - } - if src.Access == nil && access { - allErrs = append(allErrs, field.Required(fldPath.Child("access"), "must specify a access")) - } - allErrs = append(allErrs, metav1.ValidateIdentity(fldPath.Child("extraIdentity"), src.ExtraIdentity)...) - return allErrs -} - -// ValidateResource validates a components resource. -func ValidateResource(fldPath *field.Path, res Resource, access bool) field.ErrorList { - allErrs := field.ErrorList{} - allErrs = append(allErrs, ValidateObjectMeta(fldPath, &res)...) - - if err := metav1.ValidateRelation(fldPath.Child("relation"), res.Relation); err != nil { - allErrs = append(allErrs, err) - } - - if !metav1.IsIdentity(res.Name) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), res.Name, metav1.IdentityKeyValidationErrMsg)) - } - - if len(res.GetType()) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must specify a type")) - } - - if res.Access == nil && access { - allErrs = append(allErrs, field.Required(fldPath.Child("access"), "must specify a access")) - } - allErrs = append(allErrs, metav1.ValidateIdentity(fldPath.Child("extraIdentity"), res.ExtraIdentity)...) - return allErrs -} - -func validateProvider(fldPath *field.Path, provider metav1.Provider) *field.Error { - if len(provider.Name) == 0 { - return field.Required(fldPath.Child("name"), "provider name must be set") - } - return nil -} - -// ValidateComponentReference validates a component version reference. -func ValidateComponentReference(fldPath *field.Path, cr Reference) field.ErrorList { - allErrs := field.ErrorList{} - if len(cr.ComponentName) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("componentName"), "must specify a component name")) - } - allErrs = append(allErrs, ValidateObjectMeta(fldPath, &cr)...) - return allErrs -} - -// ValidateComponentReferences validates a list of component version references. -// It makes sure that no duplicate sources are present. -func ValidateComponentReferences(fldPath *field.Path, refs References) field.ErrorList { - allErrs := field.ErrorList{} - refIDs := make(map[string]struct{}) - for i, ref := range refs { - refPath := fldPath.Index(i) - allErrs = append(allErrs, ValidateComponentReference(refPath, ref)...) - - id := ref.GetIdentity(refs) - dig := string(id.Digest()) - if _, ok := refIDs[dig]; ok { - allErrs = append(allErrs, field.Duplicate(refPath, fmt.Sprintf("duplicate component reference %s", id))) - continue - } - refIDs[dig] = struct{}{} - } - return allErrs -} - -// ValidateResources validates a list of resources. -// It makes sure that no duplicate sources are present. -func ValidateResources(fldPath *field.Path, resources Resources, componentVersion string) field.ErrorList { - allErrs := field.ErrorList{} - resourceIDs := make(map[string]struct{}) - for i, res := range resources { - localPath := fldPath.Index(i) - allErrs = append(allErrs, ValidateResource(localPath, res, true)...) - - if err := ValidateSourceRefs(localPath.Child("sourceRef"), res.SourceRefs); err != nil { - allErrs = append(allErrs, err...) - } - - id := res.GetIdentity(resources) - dig := string(id.Digest()) - if _, ok := resourceIDs[dig]; ok { - allErrs = append(allErrs, field.Duplicate(localPath, fmt.Sprintf("duplicate resource %s", id))) - continue - } - resourceIDs[dig] = struct{}{} - } - return allErrs -} - -func ValidateSourceRefs(fldPath *field.Path, srcs []SourceRef) field.ErrorList { - allErrs := field.ErrorList{} - for i, src := range srcs { - localPath := fldPath.Index(i) - if err := metav1.ValidateLabels(localPath.Child("labels"), src.Labels); err != nil { - allErrs = append(allErrs, err...) - } - } - return allErrs -} diff --git a/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/validation_test.go b/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/validation_test.go deleted file mode 100644 index 87393ab06..000000000 --- a/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/validation_test.go +++ /dev/null @@ -1,438 +0,0 @@ -package v3alpha1_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/onsi/gomega/gstruct" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1" - - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - meta "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/testutils" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/jsonscheme" - "github.com/open-component-model/ocm/pkg/runtime" -) - -func TestConfig(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "V2 Test Suite") -} - -var _ = Describe("Validation", func() { - testutils.TestCompName(jsonscheme.ResourcesComponentDescriptorOcmV3SchemaYamlBytes()) - - Context("validator", func() { - var ( - comp *ComponentDescriptor - - ociImage1 *Resource - ociRegistry1 *ociartifact.AccessSpec - ociImage2 *Resource - ociRegistry2 *ociartifact.AccessSpec - ) - - BeforeEach(func() { - ociRegistry1 = ociartifact.New("docker/image1:1.2.3") - - unstrucOCIRegistry1, err := runtime.ToUnstructuredTypedObject(ociRegistry1) - Expect(err).ToNot(HaveOccurred()) - - ociImage1 = &Resource{ - ElementMeta: ElementMeta{ - Name: "image1", - Version: "1.2.3", - }, - Relation: meta.ExternalRelation, - Access: unstrucOCIRegistry1, - } - ociRegistry2 = ociartifact.New("docker/image1:1.2.3") - unstrucOCIRegistry2, err := runtime.ToUnstructuredTypedObject(ociRegistry2) - Expect(err).ToNot(HaveOccurred()) - ociImage2 = &Resource{ - ElementMeta: ElementMeta{ - Name: "image2", - Version: "1.2.3", - }, - Relation: meta.ExternalRelation, - Access: unstrucOCIRegistry2, - } - - comp = &ComponentDescriptor{ - TypeMeta: meta.TypeMeta{ - APIVersion: SchemaVersion, - Kind: Kind, - }, - ObjectMeta: meta.ObjectMeta{ - Name: "my-comp", - Version: "1.2.3", - Provider: meta.Provider{ - Name: "external", - }, - }, - RepositoryContexts: nil, - Spec: ComponentVersionSpec{ - Sources: nil, - References: nil, - Resources: []Resource{*ociImage1, *ociImage2}, - }, - } - }) - - Context("#Metadata", func() { - It("should forbid if the component schemaVersion is missing", func() { - comp := ComponentDescriptor{} - - errList := Validate(nil, &comp) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeRequired), - "Field": Equal("apiVersion"), - })))) - }) - - It("should pass if the component schemaVersion is defined", func() { - errList := Validate(nil, comp) - Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeRequired), - "Field": Equal("apiVersion"), - })))) - }) - }) - - Context("#ObjectMeta", func() { - It("should forbid if the component's version is missing", func() { - comp := ComponentDescriptor{} - errList := Validate(nil, &comp) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeRequired), - "Field": Equal("metadata.name"), - })))) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeRequired), - "Field": Equal("metadata.version"), - })))) - }) - - It("should forbid if the component's name is missing", func() { - comp := ComponentDescriptor{} - errList := Validate(nil, &comp) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeRequired), - "Field": Equal("metadata.name"), - })))) - }) - }) - - Context("#Sources", func() { - It("should forbid if a duplicated component's source is defined", func() { - comp.Spec.Sources = []Source{ - { - SourceMeta: SourceMeta{ - ElementMeta: ElementMeta{ - Name: "a", - }, - }, - Access: runtime.NewEmptyUnstructured("custom"), - }, - { - SourceMeta: SourceMeta{ - ElementMeta: ElementMeta{ - Name: "a", - }, - }, - Access: runtime.NewEmptyUnstructured("custom"), - }, - } - errList := Validate(nil, comp) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeDuplicate), - "Field": Equal("spec.sources[1]"), - })))) - }) - }) - - Context("#ComponentReferences", func() { - It("should pass if a reference is set", func() { - comp.Spec.References = []Reference{ - { - ElementMeta: ElementMeta{ - Name: "test", - Version: "1.2.3", - }, - ComponentName: "test", - }, - } - errList := Validate(nil, comp) - Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeRequired), - "Field": Equal("spec.references[0].name"), - })))) - Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeRequired), - "Field": Equal("spec.references[0].version"), - })))) - }) - - It("should forbid if a reference's name is missing", func() { - comp.Spec.References = []Reference{ - { - ElementMeta: ElementMeta{ - Version: "1.2.3", - }, - ComponentName: "test", - }, - } - errList := Validate(nil, comp) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeRequired), - "Field": Equal("spec.references[0].name"), - })))) - }) - - It("should forbid if a reference's component name is missing", func() { - comp.Spec.References = []Reference{ - { - ElementMeta: ElementMeta{ - Name: "test", - Version: "1.2.3", - }, - }, - } - errList := Validate(nil, comp) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeRequired), - "Field": Equal("spec.references[0].componentName"), - })))) - }) - - It("should forbid if a reference's version is missing", func() { - comp.Spec.References = []Reference{ - { - ElementMeta: ElementMeta{ - Name: "test", - }, - ComponentName: "test", - }, - } - errList := Validate(nil, comp) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeRequired), - "Field": Equal("spec.references[0].version"), - })))) - }) - - It("should forbid if a duplicated component reference is defined", func() { - comp.Spec.References = []Reference{ - { - ElementMeta: ElementMeta{ - Name: "test", - }, - }, - { - ElementMeta: ElementMeta{ - Name: "test", - }, - }, - } - errList := Validate(nil, comp) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeDuplicate), - "Field": Equal("spec.references[1]"), - })))) - }) - }) - - Context("#Resources", func() { - It("should forbid if a resource name contains invalid characters", func() { - comp.Spec.Resources = []Resource{ - { - ElementMeta: ElementMeta{ - Name: "test$", - }, - }, - { - ElementMeta: ElementMeta{ - Name: "testšŸ™…", - }, - }, - } - errList := Validate(nil, comp) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeInvalid), - "Field": Equal("spec.resources[0].name"), - })))) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeInvalid), - "Field": Equal("spec.resources[1].name"), - })))) - }) - - It("should forbid if a duplicated local resource is defined", func() { - comp.Spec.Resources = []Resource{ - { - ElementMeta: ElementMeta{ - Name: "test", - }, - }, - { - ElementMeta: ElementMeta{ - Name: "test", - }, - }, - } - errList := Validate(nil, comp) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeDuplicate), - "Field": Equal("spec.resources[1]"), - })))) - }) - - It("should forbid if a duplicated resource with additional identity labels is defined", func() { - comp.Spec.Resources = []Resource{ - { - ElementMeta: ElementMeta{ - Name: "test", - ExtraIdentity: meta.Identity{ - "my-id": "some-id", - }, - }, - }, - { - ElementMeta: ElementMeta{ - Name: "test", - ExtraIdentity: meta.Identity{ - "my-id": "some-id", - }, - }, - }, - } - errList := Validate(nil, comp) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeDuplicate), - "Field": Equal("spec.resources[1]"), - })))) - }) - - It("should pass if a duplicated resource has the same name but with different additional identity labels", func() { - comp.Spec.Resources = []Resource{ - { - ElementMeta: ElementMeta{ - Name: "test", - ExtraIdentity: meta.Identity{ - "my-id": "some-id", - }, - }, - }, - { - ElementMeta: ElementMeta{ - Name: "test", - }, - }, - } - errList := Validate(nil, comp) - Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeDuplicate), - "Field": Equal("spec.resources[1]"), - })))) - Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeDuplicate), - "Field": Equal("spec.resources[0]"), - })))) - }) - }) - - Context("#labels", func() { - It("should forbid if labels are defined multiple times in the same context", func() { - comp.Spec.References = []Reference{ - { - ElementMeta: ElementMeta{ - Name: "test", - Version: "1.2.3", - Labels: []meta.Label{ - { - Name: "l1", - Value: []byte{}, - }, - { - Name: "l1", - Value: []byte{}, - }, - }, - }, - ComponentName: "test", - }, - } - - errList := Validate(nil, comp) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeDuplicate), - "Field": Equal("spec.references[0].labels[1]"), - })))) - }) - - It("should pass if labels are defined multiple times in the same context with differnet names", func() { - comp.Spec.References = []Reference{ - { - ElementMeta: ElementMeta{ - Name: "test", - Version: "1.2.3", - Labels: []meta.Label{ - { - Name: "l1", - Value: []byte{}, - }, - { - Name: "l2", - Value: []byte{}, - }, - }, - }, - ComponentName: "test", - }, - } - - errList := Validate(nil, comp) - Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeDuplicate), - "Field": Equal("spec.references[0].labels[1]"), - })))) - }) - }) - - Context("#Identity", func() { - It("should pass valid identity labels", func() { - identity := meta.Identity{ - "my-l1": "test", - "my-l2": "test", - } - errList := meta.ValidateIdentity(field.NewPath("identity"), identity) - Expect(errList).To(HaveLen(0)) - }) - - It("should forbid if a identity label define the name", func() { - identity := meta.Identity{ - "name": "test", - } - errList := meta.ValidateIdentity(field.NewPath("identity"), identity) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeForbidden), - "Field": Equal("identity[name]"), - })))) - }) - - It("should forbid if a identity label defines a key with invalid characters", func() { - identity := meta.Identity{ - "my-l1!": "test", - } - errList := meta.ValidateIdentity(field.NewPath("identity"), identity) - Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeForbidden), - "Field": Equal("identity[my-l1!]"), - })))) - }) - }) - }) -}) diff --git a/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/version.go b/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/version.go deleted file mode 100644 index c86f35853..000000000 --- a/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/version.go +++ /dev/null @@ -1,310 +0,0 @@ -package v3alpha1 - -import ( - "github.com/mandelsoft/goutils/errors" - - "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/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/jsonscheme" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - SchemaVersion = GroupVersion - - VersionName = "v3alpha1" - GroupVersion = metav1.GROUP + "/" + VersionName - Kind = metav1.KIND -) - -func init() { - compdesc.RegisterScheme(&DescriptorVersion{}) -} - -type DescriptorVersion struct{} - -var _ compdesc.Scheme = (*DescriptorVersion)(nil) - -func (v *DescriptorVersion) GetVersion() string { - return SchemaVersion -} - -func (v *DescriptorVersion) Decode(data []byte, opts *compdesc.DecodeOptions) (compdesc.ComponentDescriptorVersion, error) { - var cd ComponentDescriptor - if !opts.DisableValidation { - if err := jsonscheme.Validate(data); err != nil { - return nil, err - } - } - var err error - if opts.StrictMode { - err = opts.Codec.DecodeStrict(data, &cd) - } else { - err = opts.Codec.Decode(data, &cd) - } - if err != nil { - return nil, err - } - - if err := cd.Default(); err != nil { - return nil, err - } - - if !opts.DisableValidation { - err = cd.Validate() - if err != nil { - return nil, err - } - } - return &cd, err -} - -//////////////////////////////////////////////////////////////////////////////// -// convert to internal version -//////////////////////////////////////////////////////////////////////////////// - -func (v *DescriptorVersion) ConvertTo(obj compdesc.ComponentDescriptorVersion) (out *compdesc.ComponentDescriptor, err error) { - if obj == nil { - return nil, nil - } - in, ok := obj.(*ComponentDescriptor) - if !ok { - return nil, errors.Newf("%T is no version v2 descriptor", obj) - } - if in.Kind != Kind { - return nil, errors.ErrInvalid("kind", in.Kind) - } - - defer compdesc.CatchConversionError(&err) - out = &compdesc.ComponentDescriptor{ - Metadata: compdesc.Metadata{ConfiguredVersion: in.APIVersion}, - ComponentSpec: compdesc.ComponentSpec{ - ObjectMeta: *in.ObjectMeta.Copy(), - RepositoryContexts: in.RepositoryContexts.Copy(), - Sources: convertSourcesTo(in.Spec.Sources), - Resources: convertResourcesTo(in.Spec.Resources), - References: convertReferencesTo(in.Spec.References), - }, - Signatures: in.Signatures.Copy(), - NestedDigests: in.NestedDigests.Copy(), - } - return out, nil -} - -func convertReferenceTo(in Reference) compdesc.ComponentReference { - return compdesc.ComponentReference{ - ElementMeta: convertElementmetaTo(in.ElementMeta), - ComponentName: in.ComponentName, - Digest: in.Digest.Copy(), - } -} - -func convertReferencesTo(in []Reference) compdesc.References { - out := make(compdesc.References, len(in)) - for i := range in { - out[i] = convertReferenceTo(in[i]) - } - return out -} - -func convertSourceTo(in Source) compdesc.Source { - return compdesc.Source{ - SourceMeta: compdesc.SourceMeta{ - ElementMeta: convertElementmetaTo(in.ElementMeta), - Type: in.Type, - }, - Access: compdesc.GenericAccessSpec(in.Access.DeepCopy()), - } -} - -func convertSourcesTo(in Sources) compdesc.Sources { - if in == nil { - return nil - } - out := make(compdesc.Sources, len(in)) - for i := range in { - out[i] = convertSourceTo(in[i]) - } - return out -} - -func convertElementmetaTo(in ElementMeta) compdesc.ElementMeta { - return compdesc.ElementMeta{ - Name: in.Name, - Version: in.Version, - ExtraIdentity: in.ExtraIdentity.Copy(), - Labels: in.Labels.Copy(), - } -} - -func convertResourceTo(in Resource) compdesc.Resource { - srcRefs := ConvertSourcerefsTo(in.SourceRefs) - if srcRefs == nil { - srcRefs = ConvertSourcerefsTo(in.SourceRef) - } - return compdesc.Resource{ - ResourceMeta: compdesc.ResourceMeta{ - ElementMeta: convertElementmetaTo(in.ElementMeta), - Type: in.Type, - Relation: in.Relation, - SourceRefs: srcRefs, - Digest: in.Digest.Copy(), - }, - Access: compdesc.GenericAccessSpec(in.Access), - } -} - -func convertResourcesTo(in Resources) compdesc.Resources { - if in == nil { - return nil - } - out := make(compdesc.Resources, len(in)) - for i := range in { - out[i] = convertResourceTo(in[i]) - } - return out -} - -func convertSourcerefTo(in SourceRef) compdesc.SourceRef { - return compdesc.SourceRef{ - IdentitySelector: in.IdentitySelector.Copy(), - Labels: in.Labels.Copy(), - } -} - -func ConvertSourcerefsTo(in []SourceRef) []compdesc.SourceRef { - if in == nil { - return nil - } - out := make([]compdesc.SourceRef, len(in)) - for i := range in { - out[i] = convertSourcerefTo(in[i]) - } - return out -} - -//////////////////////////////////////////////////////////////////////////////// -// convert from internal version -//////////////////////////////////////////////////////////////////////////////// - -func (v *DescriptorVersion) ConvertFrom(in *compdesc.ComponentDescriptor) (compdesc.ComponentDescriptorVersion, error) { - if in == nil { - return nil, nil - } - out := &ComponentDescriptor{ - TypeMeta: metav1.TypeMeta{ - APIVersion: SchemaVersion, - Kind: Kind, - }, - ObjectMeta: *in.ObjectMeta.Copy(), - RepositoryContexts: in.RepositoryContexts.Copy(), - Spec: ComponentVersionSpec{ - Sources: convertSourcesFrom(in.Sources), - Resources: convertResourcesFrom(in.Resources), - References: convertReferencesFrom(in.References), - }, - Signatures: in.Signatures.Copy(), - NestedDigests: in.NestedDigests.Copy(), - } - if err := out.Default(); err != nil { - return nil, err - } - return out, nil -} - -func convertReferenceFrom(in compdesc.ComponentReference) Reference { - return Reference{ - ElementMeta: convertElementmetaFrom(in.ElementMeta), - ComponentName: in.ComponentName, - Digest: in.Digest.Copy(), - } -} - -func convertReferencesFrom(in []compdesc.ComponentReference) []Reference { - if in == nil { - return nil - } - out := make([]Reference, len(in)) - for i := range in { - out[i] = convertReferenceFrom(in[i]) - } - return out -} - -func convertSourceFrom(in compdesc.Source) Source { - acc, err := runtime.ToUnstructuredTypedObject(in.Access) - if err != nil { - compdesc.ThrowConversionError(err) - } - return Source{ - SourceMeta: SourceMeta{ - ElementMeta: convertElementmetaFrom(in.ElementMeta), - Type: in.Type, - }, - Access: acc, - } -} - -func convertSourcesFrom(in compdesc.Sources) Sources { - if in == nil { - return nil - } - out := make(Sources, len(in)) - for i := range in { - out[i] = convertSourceFrom(in[i]) - } - return out -} - -func convertElementmetaFrom(in compdesc.ElementMeta) ElementMeta { - return ElementMeta{ - Name: in.Name, - Version: in.Version, - ExtraIdentity: in.ExtraIdentity.Copy(), - Labels: in.Labels.Copy(), - } -} - -func convertResourceFrom(in compdesc.Resource) Resource { - acc, err := runtime.ToUnstructuredTypedObject(in.Access) - if err != nil { - compdesc.ThrowConversionError(err) - } - return Resource{ - ElementMeta: convertElementmetaFrom(in.ElementMeta), - Type: in.Type, - Relation: in.Relation, - SourceRefs: convertSourcerefsFrom(in.SourceRefs), - Access: acc, - Digest: in.Digest.Copy(), - } -} - -func convertResourcesFrom(in compdesc.Resources) Resources { - if in == nil { - return nil - } - out := make(Resources, len(in)) - for i := range in { - out[i] = convertResourceFrom(in[i]) - } - return out -} - -func convertSourcerefFrom(in compdesc.SourceRef) SourceRef { - return SourceRef{ - IdentitySelector: in.IdentitySelector.Copy(), - Labels: in.Labels.Copy(), - } -} - -func convertSourcerefsFrom(in []compdesc.SourceRef) []SourceRef { - if in == nil { - return nil - } - out := make([]SourceRef, len(in)) - for i := range in { - out[i] = convertSourcerefFrom(in[i]) - } - return out -} diff --git a/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/zz_generated.deepcopy.go b/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/zz_generated.deepcopy.go deleted file mode 100644 index 7d13ee1ff..000000000 --- a/pkg/contexts/ocm/compdesc/versions/ocm.software/v3alpha1/zz_generated.deepcopy.go +++ /dev/null @@ -1,199 +0,0 @@ -//go:build !ignore_autogenerated - -// Code generated by controller-gen. DO NOT EDIT. - -package v3alpha1 - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ComponentVersionSpec) DeepCopyInto(out *ComponentVersionSpec) { - *out = *in - if in.Sources != nil { - in, out := &in.Sources, &out.Sources - *out = make(Sources, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.References != nil { - in, out := &in.References, &out.References - *out = make(References, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Resources != nil { - in, out := &in.Resources, &out.Resources - *out = make(Resources, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentVersionSpec. -func (in *ComponentVersionSpec) DeepCopy() *ComponentVersionSpec { - if in == nil { - return nil - } - out := new(ComponentVersionSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ElementMeta) DeepCopyInto(out *ElementMeta) { - *out = *in - if in.ExtraIdentity != nil { - in, out := &in.ExtraIdentity, &out.ExtraIdentity - *out = make(v1.Identity, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(v1.Labels, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ElementMeta. -func (in *ElementMeta) DeepCopy() *ElementMeta { - if in == nil { - return nil - } - out := new(ElementMeta) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Reference) DeepCopyInto(out *Reference) { - *out = *in - in.ElementMeta.DeepCopyInto(&out.ElementMeta) - if in.Digest != nil { - in, out := &in.Digest, &out.Digest - *out = new(v1.DigestSpec) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Reference. -func (in *Reference) DeepCopy() *Reference { - if in == nil { - return nil - } - out := new(Reference) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Resource) DeepCopyInto(out *Resource) { - *out = *in - in.ElementMeta.DeepCopyInto(&out.ElementMeta) - if in.SourceRefs != nil { - in, out := &in.SourceRefs, &out.SourceRefs - *out = make([]SourceRef, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.SourceRef != nil { - in, out := &in.SourceRef, &out.SourceRef - *out = make([]SourceRef, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Access != nil { - in, out := &in.Access, &out.Access - *out = (*in).DeepCopy() - } - if in.Digest != nil { - in, out := &in.Digest, &out.Digest - *out = new(v1.DigestSpec) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Resource. -func (in *Resource) DeepCopy() *Resource { - if in == nil { - return nil - } - out := new(Resource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Source) DeepCopyInto(out *Source) { - *out = *in - in.SourceMeta.DeepCopyInto(&out.SourceMeta) - if in.Access != nil { - in, out := &in.Access, &out.Access - *out = (*in).DeepCopy() - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Source. -func (in *Source) DeepCopy() *Source { - if in == nil { - return nil - } - out := new(Source) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SourceMeta) DeepCopyInto(out *SourceMeta) { - *out = *in - in.ElementMeta.DeepCopyInto(&out.ElementMeta) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceMeta. -func (in *SourceMeta) DeepCopy() *SourceMeta { - if in == nil { - return nil - } - out := new(SourceMeta) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SourceRef) DeepCopyInto(out *SourceRef) { - *out = *in - if in.IdentitySelector != nil { - in, out := &in.IdentitySelector, &out.IdentitySelector - *out = make(v1.StringMap, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(v1.Labels, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceRef. -func (in *SourceRef) DeepCopy() *SourceRef { - if in == nil { - return nil - } - out := new(SourceRef) - in.DeepCopyInto(out) - return out -} diff --git a/pkg/contexts/ocm/compdesc/versions/v2/componentdescriptor.go b/pkg/contexts/ocm/compdesc/versions/v2/componentdescriptor.go deleted file mode 100644 index 37dfaff21..000000000 --- a/pkg/contexts/ocm/compdesc/versions/v2/componentdescriptor.go +++ /dev/null @@ -1,340 +0,0 @@ -package v2 - -import ( - "errors" - - "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/runtime" -) - -var ErrNotFound = errors.New("NotFound") - -// ComponentDescriptor defines a versioned component with a source and dependencies. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type ComponentDescriptor struct { - // Metadata specifies the schema version of the component. - Metadata metav1.Metadata `json:"meta"` - // Spec contains the specification of the component. - ComponentSpec `json:"component"` - // Signatures contains a list of signatures for the ComponentDescriptor - Signatures metav1.Signatures `json:"signatures,omitempty"` - // NestedDigests described digest information of resources in aggregated - // omponent versions. - NestedDigests metav1.NestedDigests `json:"nestedDigests,omitempty"` -} - -var _ compdesc.ComponentDescriptorVersion = (*ComponentDescriptor)(nil) - -// SchemeVersion returns the actual scheme version of this component descriptor -// representation. -func (cd *ComponentDescriptor) SchemaVersion() string { - if cd.Metadata.Version == "" { - return SchemaVersion - } - return cd.Metadata.Version -} - -// ComponentSpec defines a virtual component with -// a repository context, source and dependencies. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type ComponentSpec struct { - ObjectMeta `json:",inline"` - // RepositoryContexts defines the previous repositories of the component - RepositoryContexts runtime.UnstructuredTypedObjectList `json:"repositoryContexts"` - // Provider defines the provider type of a component. - // It can be external or internal. - Provider metav1.ProviderName `json:"provider"` - // Sources defines sources that produced the component - Sources Sources `json:"sources"` - // ComponentReferences references component dependencies that can be resolved in the current context. - ComponentReferences ComponentReferences `json:"componentReferences"` - // Resources defines all resources that are created by the component and by a third party. - Resources Resources `json:"resources"` -} - -// ObjectMeta defines a object that is uniquely identified by its name and version. -// +k8s:deepcopy-gen=true -type ObjectMeta struct { - // Name is the context unique name of the object. - Name string `json:"name"` - // Version is the semver version of the object. - Version string `json:"version"` - // Labels defines an optional set of additional labels - // describing the object. - // +optional - Labels metav1.Labels `json:"labels,omitempty"` - // CreationTime is the creation time of the component version - // +optional - CreationTime *metav1.Timestamp `json:"creationTime,omitempty"` -} - -// GetName returns the name of the object. -func (o ObjectMeta) GetName() string { - return o.Name -} - -// SetName sets the name of the object. -func (o *ObjectMeta) SetName(name string) { - o.Name = name -} - -// GetVersion returns the version of the object. -func (o ObjectMeta) GetVersion() string { - return o.Version -} - -// SetVersion sets the version of the object. -func (o *ObjectMeta) SetVersion(version string) { - o.Version = version -} - -// GetLabels returns the label of the object. -func (o ObjectMeta) GetLabels() metav1.Labels { - return o.Labels -} - -// SetLabels sets the labels of the object. -func (o *ObjectMeta) SetLabels(labels []metav1.Label) { - o.Labels = labels -} - -const ( - SystemIdentityName = metav1.SystemIdentityName - SystemIdentityVersion = metav1.SystemIdentityVersion -) - -// ElementMetaAccessor provides generic access an elements meta information. -type ElementMetaAccessor interface { - GetMeta() *ElementMeta -} - -// ElementAccessor provides generic access to list of elements. -type ElementAccessor interface { - Len() int - Get(i int) ElementMetaAccessor -} - -// ElementMeta defines a object that is uniquely identified by its identity. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type ElementMeta struct { - // Name is the context unique name of the object. - Name string `json:"name"` - // Version is the semver version of the object. - Version string `json:"version"` - // ExtraIdentity is the identity of an object. - // An additional label with key "name" ist not allowed - ExtraIdentity metav1.Identity `json:"extraIdentity,omitempty"` - // Labels defines an optional set of additional labels - // describing the object. - // +optional - Labels metav1.Labels `json:"labels,omitempty"` -} - -// GetName returns the name of the object. -func (o *ElementMeta) GetName() string { - return o.Name -} - -// GetMeta returns the element meta. -func (o *ElementMeta) GetMeta() *ElementMeta { - return o -} - -// SetName sets the name of the object. -func (o *ElementMeta) SetName(name string) { - o.Name = name -} - -// GetVersion returns the version of the object. -func (o ElementMeta) GetVersion() string { - return o.Version -} - -// SetVersion sets the version of the object. -func (o *ElementMeta) SetVersion(version string) { - o.Version = version -} - -// GetLabels returns the label of the object. -func (o ElementMeta) GetLabels() metav1.Labels { - return o.Labels -} - -// SetLabels sets the labels of the object. -func (o *ElementMeta) SetLabels(labels []metav1.Label) { - o.Labels = labels -} - -// SetExtraIdentity sets the identity of the object. -func (o *ElementMeta) SetExtraIdentity(identity metav1.Identity) { - o.ExtraIdentity = identity -} - -// GetIdentity returns the identity of the object. -func (o *ElementMeta) GetIdentity(accessor ElementAccessor) metav1.Identity { - identity := o.ExtraIdentity.Copy() - if identity == nil { - identity = metav1.Identity{} - } - identity[SystemIdentityName] = o.Name - if accessor != nil { - found := false - l := accessor.Len() - for i := 0; i < l; i++ { - m := accessor.Get(i).GetMeta() - if m.Name == o.Name && m.ExtraIdentity.Equals(o.ExtraIdentity) { - if found { - identity[SystemIdentityVersion] = o.Version - break - } - found = true - } - } - } - return identity -} - -// GetIdentityDigest returns the digest of the object's identity. -func (o *ElementMeta) GetIdentityDigest(accessor ElementAccessor) []byte { - return o.GetIdentity(accessor).Digest() -} - -func (o *ElementMeta) GetRawIdentity() metav1.Identity { - identity := o.ExtraIdentity.Copy() - if identity == nil { - identity = metav1.Identity{} - } - identity[SystemIdentityName] = o.Name - if o.Version != "" { - identity[SystemIdentityVersion] = o.Version - } - return identity -} - -// Sources describes a set of source specifications. -type Sources []Source - -func (r Sources) Len() int { - return len(r) -} - -func (r Sources) Get(i int) ElementMetaAccessor { - return &r[i] -} - -// Source is the definition of a component's source. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type Source struct { - SourceMeta `json:",inline"` - - Access *runtime.UnstructuredTypedObject `json:"access"` -} - -// SourceMeta is the definition of the meta data of a source. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type SourceMeta struct { - ElementMeta `json:",inline"` - // Type describes the type of the object. - Type string `json:"type"` -} - -// GetType returns the type of the object. -func (o SourceMeta) GetType() string { - return o.Type -} - -// SetType sets the type of the object. -func (o *SourceMeta) SetType(ttype string) { - o.Type = ttype -} - -// SourceRef defines a reference to a source -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type SourceRef struct { - // IdentitySelector defines the identity that is used to match a source. - IdentitySelector metav1.StringMap `json:"identitySelector,omitempty"` - // Labels defines an optional set of additional labels - // describing the object. - // +optional - Labels metav1.Labels `json:"labels,omitempty"` -} - -// Resources describes a set of resource specifications. -type Resources []Resource - -func (r Resources) Len() int { - return len(r) -} - -func (r Resources) Get(i int) ElementMetaAccessor { - return &r[i] -} - -// Resource describes a resource dependency of a component. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type Resource struct { - ElementMeta `json:",inline"` - - // Type describes the type of the object. - Type string `json:"type"` - - // Relation describes the relation of the resource to the component. - // Can be a local or external resource - Relation metav1.ResourceRelation `json:"relation,omitempty"` - - // SourceRefs defines a list of source names. - // These entries reference the sources defined in the - // component.sources. - SourceRefs []SourceRef `json:"srcRefs,omitempty"` - // SourceRef is for deserialization compatibility, only. - // The usage of this field in external formats is deprecated. - SourceRef []SourceRef `json:"srcRef,omitempty"` - - // Access describes the type specific method to - // access the defined resource. - Access *runtime.UnstructuredTypedObject `json:"access"` - - // Digest is the optional digest of the referenced resource. - // +optional - Digest *metav1.DigestSpec `json:"digest,omitempty"` -} - -// GetType returns the type of the object. -func (o Resource) GetType() string { - return o.Type -} - -// SetType sets the type of the object. -func (o *Resource) SetType(ttype string) { - o.Type = ttype -} - -type ComponentReferences []ComponentReference - -func (r ComponentReferences) Len() int { - return len(r) -} - -func (r ComponentReferences) Get(i int) ElementMetaAccessor { - return &r[i] -} - -// ComponentReference describes the reference to another component in the registry. -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type ComponentReference struct { - ElementMeta `json:",inline"` - // ComponentName describes the remote name of the referenced object - ComponentName string `json:"componentName"` - // Digest is the optional digest of the referenced component. - // +optional - Digest *metav1.DigestSpec `json:"digest,omitempty"` -} diff --git a/pkg/contexts/ocm/compdesc/versions/v2/default.go b/pkg/contexts/ocm/compdesc/versions/v2/default.go deleted file mode 100644 index 4c2c0e113..000000000 --- a/pkg/contexts/ocm/compdesc/versions/v2/default.go +++ /dev/null @@ -1,49 +0,0 @@ -package v2 - -import ( - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/runtime" -) - -// Default applies defaults to a component. -func (cd *ComponentDescriptor) Default() error { - if cd.RepositoryContexts == nil { - cd.RepositoryContexts = make([]*runtime.UnstructuredTypedObject, 0) - } - if cd.Sources == nil { - cd.Sources = make([]Source, 0) - } - if cd.ComponentReferences == nil { - cd.ComponentReferences = make([]ComponentReference, 0) - } - if cd.Resources == nil { - cd.Resources = make([]Resource, 0) - } - - DefaultResources(cd) - return nil -} - -// DefaultResources defaults a list of resources. -// The version of the component is defaulted for local resources that do not contain a version. -// adds the version as identity if the resource identity would clash otherwise. -func DefaultResources(component *ComponentDescriptor) { - for i, res := range component.Resources { - if res.Relation == v1.LocalRelation && len(res.Version) == 0 { - component.Resources[i].Version = component.GetVersion() - } - - id := res.GetIdentity(component.Resources) - if v, ok := id[SystemIdentityVersion]; ok { - if res.ExtraIdentity == nil { - res.ExtraIdentity = v1.Identity{ - SystemIdentityVersion: v, - } - } else { - if _, ok := res.ExtraIdentity[SystemIdentityVersion]; !ok { - res.ExtraIdentity[SystemIdentityVersion] = v - } - } - } - } -} diff --git a/pkg/contexts/ocm/compdesc/versions/v2/jsonscheme/bindata.go b/pkg/contexts/ocm/compdesc/versions/v2/jsonscheme/bindata.go deleted file mode 100644 index 6c71312d9..000000000 --- a/pkg/contexts/ocm/compdesc/versions/v2/jsonscheme/bindata.go +++ /dev/null @@ -1,263 +0,0 @@ -// Code generated by go-bindata. (@generated) DO NOT EDIT. - -//Package jsonscheme generated by go-bindata.// sources: -// ../../../../../../../resources/component-descriptor-v2-schema.yaml -package jsonscheme - -import ( - "bytes" - "compress/gzip" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "strings" - "time" -) - -func bindataRead(data []byte, name string) ([]byte, error) { - gz, err := gzip.NewReader(bytes.NewBuffer(data)) - if err != nil { - return nil, fmt.Errorf("read %q: %v", name, err) - } - - var buf bytes.Buffer - _, err = io.Copy(&buf, gz) - clErr := gz.Close() - - if err != nil { - return nil, fmt.Errorf("read %q: %v", name, err) - } - if clErr != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -type asset struct { - bytes []byte - info os.FileInfo -} - -type bindataFileInfo struct { - name string - size int64 - mode os.FileMode - modTime time.Time -} - -// Name return file name -func (fi bindataFileInfo) Name() string { - return fi.name -} - -// Size return file size -func (fi bindataFileInfo) Size() int64 { - return fi.size -} - -// Mode return file mode -func (fi bindataFileInfo) Mode() os.FileMode { - return fi.mode -} - -// ModTime return file modify time -func (fi bindataFileInfo) ModTime() time.Time { - return fi.modTime -} - -// IsDir return file whether a directory -func (fi bindataFileInfo) IsDir() bool { - return fi.mode&os.ModeDir != 0 -} - -// Sys return file is sys mode -func (fi bindataFileInfo) Sys() interface{} { - return nil -} - -var _ResourcesComponentDescriptorV2SchemaYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xe4\x59\x4f\x6f\x1b\xb7\x12\xbf\xeb\x53\x0c\x60\x03\x94\x62\xaf\x64\xeb\x21\x87\xec\xc5\x08\x92\xcb\xc3\x7b\x6d\x8a\x24\xe8\xa1\x8a\x6a\xd0\xbb\xb3\x12\xdd\x5d\x72\x4b\x52\x8a\xd5\xc4\xdf\xbd\x20\xb9\xdc\xff\x2b\x4b\x72\xd2\xa2\x68\x0e\xb1\x38\x1c\x0e\x87\x33\xbf\xf9\x27\x9d\xb3\x38\x04\xb2\xd6\x3a\x57\xe1\x6c\xb6\xa2\x32\x46\x8e\x72\x1a\xa5\x62\x13\xcf\x54\xb4\xc6\x8c\xaa\x59\x24\xb2\x5c\x70\xe4\x3a\x88\x51\x45\x92\xe5\x5a\xc8\x60\x3b\x27\xa3\x73\xc7\x51\x93\x70\xaf\x04\x0f\x1c\x75\x2a\xe4\x6a\x16\x4b\x9a\xe8\xd9\xfc\x6a\x7e\x15\x5c\xcf\x0b\x81\x64\xe4\xc5\x30\xc1\x43\x20\xef\x72\xe4\xf0\xc6\xdf\x01\x3f\x88\x18\x53\xd8\xce\xc1\x73\x9f\xc7\x98\xa8\x70\x04\x90\xa1\xa6\xe6\x2f\x80\xde\xe5\x18\x02\x11\x77\xf7\x18\x69\x62\x49\x4d\x99\xa5\xca\x50\xa9\x6c\xcf\xc7\x54\x53\x77\x40\xe2\xef\x1b\x26\x31\x76\x12\x01\x02\x20\xee\xc6\x9f\x51\x2a\x26\xb8\xe3\xca\xa5\xc8\x51\x6a\x86\xca\xf3\x35\x98\x3c\xb1\x54\x49\x69\xc9\xf8\x8a\x8c\xac\xba\x72\x85\x83\xfa\x76\x05\xd3\x74\x25\x24\xd3\xeb\xac\x12\x9a\x53\xad\x51\x9a\x07\xfd\xba\xa0\xc1\x1f\x4b\xf3\xdf\x55\xf0\x6a\x76\x1b\x2c\x2f\xce\x49\xc1\x16\x09\x9e\xb0\x55\x08\x5f\xe0\xd1\x52\x68\x1c\x33\x63\x06\x9a\xfe\x54\xdd\x01\x09\x4d\x15\x8e\x00\x52\x7a\x87\xe9\xa0\x56\x3d\x46\xe1\x34\x43\x52\x2d\xb7\x34\xdd\xe0\xd0\x13\x0c\xef\xa0\x49\x1c\xd1\x9e\x0f\xe1\xcb\xa3\x5f\xb7\x0d\x59\x7b\xf3\x76\x71\x15\xbc\xaa\xbd\x54\xb1\x15\x67\x7c\xd5\xb9\xe1\x4e\x88\x14\x29\xf7\x6c\x35\xc3\x9b\x7f\xe7\x12\x93\x10\xc8\xd9\xcc\x02\x69\x66\x77\xad\x83\x4a\x90\xfc\x58\xaa\xdd\xa3\x72\x46\x1f\xfe\x8f\x7c\xa5\xd7\x21\xcc\x5f\xbe\x1c\xf5\xba\x25\x70\x7e\x59\xbe\x18\x2f\xa6\xcb\x16\x69\xf2\xc2\xd3\xbe\xcc\x2f\x1f\xc7\xb3\xc6\xf6\x6d\xcf\x91\x5b\x73\x66\x62\x5e\x3d\x02\x60\x31\x72\xcd\xf4\xee\xb5\xd6\x92\xdd\x6d\x34\xfe\x0f\x77\x4e\xd5\x8c\xf1\x52\xaf\x3e\xad\xcc\xe5\xe3\x45\x70\x7b\xe1\x15\xf1\xc4\xc9\x8d\x13\x2d\x31\xa5\x0f\x18\x7f\xc0\x6c\x8b\xd2\xc9\x3c\x03\x4d\x7f\x43\x0e\x89\x14\x19\x28\xbb\x61\xc2\x18\x28\x8f\x81\xc6\xf7\x1b\xa5\x31\x06\x2d\x80\xa6\xa9\xf8\x0c\x94\x83\xc8\x1d\xd2\x20\x45\x1a\x33\xbe\x02\xb2\x25\x97\x90\xd1\x7b\x21\x03\xc1\xd3\xdd\xa5\x3d\x6a\xd7\xd3\x8c\xf1\x82\xea\xef\x5a\x33\x05\x19\x52\xae\x40\xaf\x11\x12\x61\xa4\x1a\x21\xce\xfc\x0a\xa8\x44\x73\x95\xc1\x0c\x8b\x9b\xfa\x2a\xaf\xf0\xf5\x74\x3e\xfd\x4f\xfd\x73\x90\x08\x71\x71\x47\x65\x41\xdb\xd6\x19\xb6\x7d\x1c\xd7\xd3\xb9\xff\x54\xb2\xd5\xf8\xcb\x8f\x8d\x63\x75\x63\x6f\x97\x37\xe3\xab\xaf\x8b\xeb\xe0\xd5\xf2\x53\xfc\x62\x32\xbe\x09\x3f\x4d\xeb\x84\xc9\x4d\x3f\x29\x18\x8f\x6f\xc2\x8a\xf8\xf5\x53\x6c\x7d\xf4\x3a\xf8\x25\x58\x1a\xe4\xfb\xcf\x5e\xe4\x81\xcc\x13\x7f\xe3\xc5\xb8\xbe\x71\x61\x85\x34\x28\x96\xb3\x88\xae\x6e\xfe\xea\x40\xef\xa9\x5c\xb6\x33\x71\xa4\x4c\x22\x6a\x85\x5c\x1f\x88\x09\x3c\x3a\x10\xe6\x42\x31\x2d\xe4\xee\x8d\xe0\x1a\x1f\xf4\x31\xa9\xc9\x70\x0d\xa5\x22\x2b\x61\x4f\x76\xa6\x51\x84\x4a\x1d\x58\x4e\xee\xa8\x42\xcb\x05\x89\x90\xc5\x51\x54\x30\x36\x2b\x7c\xd0\xc8\x4d\x0a\x53\x93\x27\x14\x1d\x01\x28\xb1\x91\x11\xbe\xc5\x84\x71\x9b\xa3\x8f\x78\xad\xc9\xad\xe5\xa2\xc8\x9a\xe5\xda\x48\x28\x17\x4e\xbf\xd3\x53\x74\x27\x65\xf6\xfa\xaf\x60\xc6\x07\x2d\xe9\x7f\x0b\x86\xc1\xa4\xdb\x91\x40\x86\xd2\x7f\xeb\x60\x23\xe8\xc9\x21\xbe\x75\x44\x5b\xe6\x54\x87\x89\x4a\x49\x77\xd5\x3b\x99\xc6\xac\xc6\xd4\xb9\xdd\x4a\xf1\xec\x75\xc4\xf4\x31\xbb\x7d\xeb\xe6\x98\xad\x50\xe9\x0f\x39\x46\x47\x38\x78\x4d\xd5\xfa\xb5\xef\x01\x2a\xb7\x0b\x99\xd1\x94\x29\x6a\xe0\xd2\xdd\xb6\xe5\x74\xc0\xd5\x0d\x81\x6d\x53\x38\x73\x79\x50\xf4\x5e\xb2\xf7\x88\xab\xe3\xfd\x1c\x23\x00\xcd\x32\x54\x9a\x66\x79\xdb\x08\xce\x06\x03\x1a\xef\x13\x5a\x90\x58\x17\xbe\x0d\x06\x30\x21\x9a\x51\x1d\x42\x4c\x35\x06\x86\xdf\x06\x1e\x5b\x71\xaa\x37\x12\x8f\x74\x0a\xdd\x63\x71\xb3\xca\x30\x66\xf4\xa3\x8f\xbe\x83\x9a\xba\x23\x8d\xe9\x48\xe5\x3d\x15\x57\x33\x45\x7d\x5c\xa3\x63\x72\x79\x4a\x24\xb6\xa6\x96\xcf\x86\x5a\xdf\xd6\xe7\xaf\x92\xf1\xd4\x8c\xe4\x20\x5f\x2e\x4b\x79\x07\xb4\xa4\x87\xa6\xa9\x86\x41\xdc\x7d\x83\x79\xa3\x8a\xc0\x7a\xf3\x58\x7b\x61\xcf\x99\x06\x46\x48\x0d\x70\x16\xc8\x83\xc7\x1a\x50\xb7\xe1\xcf\xd1\x34\x4a\x6f\x4f\x49\x02\xa5\x4d\x4f\x30\x49\x27\x97\xf6\xf0\x3c\x33\x5d\x1f\x61\xf5\xd2\x0e\xe5\x68\xe7\x0c\x32\x5c\x73\x8f\xa8\x79\x4f\x59\xa7\xa5\x57\xa3\xd3\x7f\x56\xe9\x39\x1a\x76\x12\x8b\x9a\x5f\x7f\x3c\x9c\x5c\x91\xda\xd0\x72\x5d\x85\x8c\xde\x63\x72\x60\x2f\x43\x41\x62\x82\x12\x79\x84\xb6\x95\x87\x71\x35\xdf\xa7\x22\xa2\xe9\xa4\xe8\x52\xc8\x89\xa1\xeb\xc1\xf3\x01\x53\x8c\xb4\x90\xc7\xa3\xec\x9b\x16\xef\xfa\xa0\xf7\xde\xbf\xfc\x54\x5b\x95\x92\x0e\x1d\x97\x7b\x91\x67\xc6\xe8\xfa\x97\x0c\xc7\xdb\xb8\x67\x74\x3d\x14\xf6\xfb\x1a\x40\x38\x03\x1a\xe9\x0d\x4d\xd3\x5d\x58\xdd\x11\xd8\x8a\xf2\x79\x06\x2a\xc7\x88\xd1\xd4\x60\x5a\x4b\x16\x19\x95\xd5\x3f\xa5\x67\xfc\x0e\x0d\x61\x3b\x17\x08\x8e\xef\x92\xfa\xe1\xc0\xdf\xc2\x37\x69\x4a\x1a\x1b\xfb\x13\x67\x99\x34\x9e\x1e\x14\xf6\x0d\x2a\x5e\x8c\x3a\xf8\xcb\x9d\x02\x95\x70\x66\xcf\xdb\x74\x50\x49\xb9\x2c\x26\xf6\x8d\xd2\x90\x51\x1d\xad\x6b\xe1\xa0\x3a\x23\x49\x6d\x3a\xb3\x4b\xe3\x15\x5d\x42\xde\x92\x7c\xc7\xdc\x8f\xf1\x7f\xc9\xa4\xe2\x92\xf7\xb3\x91\xe9\xc4\x54\x55\xc7\x19\xfb\x49\xfb\x21\xdf\x64\x21\x2c\x88\x75\x35\xb9\x04\x62\xc6\x59\xc9\x69\x4a\x96\xdf\x2f\x70\x3a\x93\xd4\xd0\x28\xe5\x36\xbf\x5f\x9c\x95\xf8\x3d\xb8\x1e\x1c\x5d\x00\x1a\x89\xbe\x08\x84\xd6\x57\x1e\xaa\xb6\x99\x4b\xb1\x65\x71\x05\xa0\x00\x48\x23\x86\x9b\x45\xa5\xac\x67\xaa\x21\xbf\x71\xe2\x6f\x6b\x95\x22\x89\x16\x83\x1f\x7b\x46\xb6\x85\x07\xe2\x65\xe1\xb4\x65\xc9\xd0\x1d\xdf\x3c\xa4\xdb\x66\x7b\x2e\x26\x3b\x12\xfd\x51\xef\x84\xbf\xfe\x3b\x86\xc2\x73\xcf\x4e\x06\xad\xea\x41\xda\x6d\x43\x05\x9c\xe7\x5e\xd5\x15\xd9\xee\x7c\xbf\x81\x9f\xba\xef\x19\xb5\x62\xb5\x1e\x88\x01\x90\x0c\xdd\xcf\x3c\xf5\x60\x21\xa3\x66\x28\x54\x3f\x27\x75\x7e\x21\x70\x87\x5b\xd9\x61\xe8\xe1\xa4\x3e\x3e\x37\xa7\x9b\xda\x43\x1b\x8f\x1c\x1a\x3c\x49\x6b\x78\x3c\x49\x5a\xff\xd4\x45\xfe\x0c\x00\x00\xff\xff\xd3\x97\x5f\x7a\xeb\x1b\x00\x00") - -func ResourcesComponentDescriptorV2SchemaYamlBytes() ([]byte, error) { - return bindataRead( - _ResourcesComponentDescriptorV2SchemaYaml, - "../../../../../../../resources/component-descriptor-v2-schema.yaml", - ) -} - -func ResourcesComponentDescriptorV2SchemaYaml() (*asset, error) { - bytes, err := ResourcesComponentDescriptorV2SchemaYamlBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "../../../../../../../resources/component-descriptor-v2-schema.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info} - return a, nil -} - -// Asset loads and returns the asset for the given name. -// It returns an error if the asset could not be found or -// could not be loaded. -func Asset(name string) ([]byte, error) { - cannonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[cannonicalName]; ok { - a, err := f() - if err != nil { - return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) - } - return a.bytes, nil - } - return nil, fmt.Errorf("Asset %s not found", name) -} - -// MustAsset is like Asset but panics when Asset would return an error. -// It simplifies safe initialization of global variables. -func MustAsset(name string) []byte { - a, err := Asset(name) - if err != nil { - panic("asset: Asset(" + name + "): " + err.Error()) - } - - return a -} - -// AssetInfo loads and returns the asset info for the given name. -// It returns an error if the asset could not be found or -// could not be loaded. -func AssetInfo(name string) (os.FileInfo, error) { - cannonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[cannonicalName]; ok { - a, err := f() - if err != nil { - return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) - } - return a.info, nil - } - return nil, fmt.Errorf("AssetInfo %s not found", name) -} - -// AssetNames returns the names of the assets. -func AssetNames() []string { - names := make([]string, 0, len(_bindata)) - for name := range _bindata { - names = append(names, name) - } - return names -} - -// _bindata is a table, holding each asset generator, mapped to its name. -var _bindata = map[string]func() (*asset, error){ - "../../../../../../../resources/component-descriptor-v2-schema.yaml": ResourcesComponentDescriptorV2SchemaYaml, -} - -// AssetDir returns the file names below a certain -// directory embedded in the file by go-bindata. -// For example if you run go-bindata on data/... and data contains the -// following hierarchy: -// -// data/ -// foo.txt -// img/ -// a.png -// b.png -// -// then AssetDir("data") would return []string{"foo.txt", "img"} -// AssetDir("data/img") would return []string{"a.png", "b.png"} -// AssetDir("foo.txt") and AssetDir("notexist") would return an error -// AssetDir("") will return []string{"data"}. -func AssetDir(name string) ([]string, error) { - node := _bintree - if len(name) != 0 { - cannonicalName := strings.Replace(name, "\\", "/", -1) - pathList := strings.Split(cannonicalName, "/") - for _, p := range pathList { - node = node.Children[p] - if node == nil { - return nil, fmt.Errorf("Asset %s not found", name) - } - } - } - if node.Func != nil { - return nil, fmt.Errorf("Asset %s not found", name) - } - rv := make([]string, 0, len(node.Children)) - for childName := range node.Children { - rv = append(rv, childName) - } - return rv, nil -} - -type bintree struct { - Func func() (*asset, error) - Children map[string]*bintree -} - -var _bintree = &bintree{nil, map[string]*bintree{ - "..": {nil, map[string]*bintree{ - "..": {nil, map[string]*bintree{ - "..": {nil, map[string]*bintree{ - "..": {nil, map[string]*bintree{ - "..": {nil, map[string]*bintree{ - "..": {nil, map[string]*bintree{ - "..": {nil, map[string]*bintree{ - "resources": {nil, map[string]*bintree{ - "component-descriptor-v2-schema.yaml": {ResourcesComponentDescriptorV2SchemaYaml, map[string]*bintree{}}, - }}, - }}, - }}, - }}, - }}, - }}, - }}, - }}, -}} - -// RestoreAsset restores an asset under the given directory -func RestoreAsset(dir, name string) error { - data, err := Asset(name) - if err != nil { - return err - } - info, err := AssetInfo(name) - if err != nil { - return err - } - err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) - if err != nil { - return err - } - err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) - if err != nil { - return err - } - err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) - if err != nil { - return err - } - return nil -} - -// RestoreAssets restores an asset under the given directory recursively -func RestoreAssets(dir, name string) error { - children, err := AssetDir(name) - // File - if err != nil { - return RestoreAsset(dir, name) - } - // Dir - for _, child := range children { - err = RestoreAssets(dir, filepath.Join(name, child)) - if err != nil { - return err - } - } - return nil -} - -func _filePath(dir, name string) string { - cannonicalName := strings.Replace(name, "\\", "/", -1) - return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) -} diff --git a/pkg/contexts/ocm/compdesc/versions/v2/jsonscheme/jsonscheme.go b/pkg/contexts/ocm/compdesc/versions/v2/jsonscheme/jsonscheme.go deleted file mode 100644 index e7130320c..000000000 --- a/pkg/contexts/ocm/compdesc/versions/v2/jsonscheme/jsonscheme.go +++ /dev/null @@ -1,55 +0,0 @@ -//go:generate go-bindata -nometadata -pkg jsonscheme ../../../../../../../resources/component-descriptor-v2-schema.yaml -//go:generate gofmt -s -w bindata.go - -package jsonscheme - -import ( - "errors" - "fmt" - - "github.com/ghodss/yaml" - "github.com/xeipuuv/gojsonschema" -) - -var Schema *gojsonschema.Schema - -func init() { - dataBytes, err := ResourcesComponentDescriptorV2SchemaYamlBytes() - if err != nil { - panic(err) - } - - data, err := yaml.YAMLToJSON(dataBytes) - if err != nil { - panic(err) - } - - Schema, err = gojsonschema.NewSchema(gojsonschema.NewBytesLoader(data)) - if err != nil { - panic(err) - } -} - -// Validate validates the given data against the component descriptor v2 jsonscheme. -func Validate(src []byte) error { - data, err := yaml.YAMLToJSON(src) - if err != nil { - return err - } - documentLoader := gojsonschema.NewBytesLoader(data) - res, err := Schema.Validate(documentLoader) - if err != nil { - return err - } - - if !res.Valid() { - errs := res.Errors() - errMsg := errs[0].String() - for i := 1; i < len(errs); i++ { - errMsg = fmt.Sprintf("%s;%s", errMsg, errs[i].String()) - } - return errors.New(errMsg) - } - - return nil -} diff --git a/pkg/contexts/ocm/compdesc/versions/v2/signing.go b/pkg/contexts/ocm/compdesc/versions/v2/signing.go deleted file mode 100644 index f545a5808..000000000 --- a/pkg/contexts/ocm/compdesc/versions/v2/signing.go +++ /dev/null @@ -1,64 +0,0 @@ -package v2 - -import ( - "encoding/json" - "fmt" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/normalizations/rules" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/norm/entry" -) - -func providerMapper(v interface{}) interface{} { - var provider map[string]interface{} - err := json.Unmarshal([]byte(v.(string)), &provider) - if err == nil { - return provider - } - return v -} - -// CDExcludes describes the fields relevant for Signing -// ATTENTION: if changed, please adapt the HashEqual Functions -// in the generic part, accordingly. -var CDExcludes = signing.MapExcludes{ - "component": signing.MapExcludes{ - "provider": signing.MapValue{ - Mapping: providerMapper, - Continue: signing.MapExcludes{ - "labels": rules.LabelExcludes, - }, - }, - "labels": rules.LabelExcludes, - "repositoryContexts": nil, - "resources": signing.DefaultedMapFields{ - Next: signing.DynamicArrayExcludes{ - ValueMapper: rules.MapResourcesWithNoneAccess, - Continue: signing.MapExcludes{ - "access": nil, - "srcRefs": nil, - "labels": rules.LabelExcludes, - }, - }, - }.EnforceNull("extraIdentity"), - "sources": nil, - "componentReferences": signing.DefaultedMapFields{ - Next: signing.ArrayExcludes{ - signing.MapExcludes{ - "labels": rules.LabelExcludes, - }, - }, - }.EnforceNull("extraIdentity"), - }, - "signatures": nil, - "nestedDigests": nil, -} - -func (cd *ComponentDescriptor) Normalize(normAlgo string) ([]byte, error) { - if normAlgo != compdesc.JsonNormalisationV1 { - return nil, fmt.Errorf("unsupported cd normalization %q", normAlgo) - } - data, err := signing.Normalize(entry.Type, cd, CDExcludes) - return data, err -} diff --git a/pkg/contexts/ocm/compdesc/versions/v2/validation.go b/pkg/contexts/ocm/compdesc/versions/v2/validation.go deleted file mode 100644 index a8cceb784..000000000 --- a/pkg/contexts/ocm/compdesc/versions/v2/validation.go +++ /dev/null @@ -1,205 +0,0 @@ -package v2 - -import ( - "fmt" - - "github.com/mandelsoft/goutils/errors" - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" -) - -// Validate validates a parsed v2 component descriptor. -func (cd *ComponentDescriptor) Validate() error { - if err := Validate(nil, cd); err != nil { - return errors.Wrapf(err.ToAggregate(), "%s:%s", cd.Name, cd.Version) - } - return nil -} - -func Validate(fldPath *field.Path, component *ComponentDescriptor) field.ErrorList { - if component == nil { - return nil - } - allErrs := field.ErrorList{} - - if len(component.Metadata.Version) == 0 { - metaPath := field.NewPath("meta").Child("schemaVersion") - if fldPath != nil { - metaPath = fldPath.Child("meta").Child("schemaVersion") - } - allErrs = append(allErrs, field.Required(metaPath, "must specify a version")) - } - - compPath := field.NewPath("component") - if fldPath != nil { - compPath = fldPath.Child("component") - } - - if err := validateProvider(compPath.Child("provider"), component.Provider); err != nil { - allErrs = append(allErrs, err) - } - - allErrs = append(allErrs, ValidateObjectMeta(compPath, component)...) - - srcPath := compPath.Child("sources") - allErrs = append(allErrs, ValidateSources(srcPath, component.Sources)...) - - refPath := compPath.Child("componentReferences") - allErrs = append(allErrs, ValidateComponentReferences(refPath, component.ComponentReferences)...) - - resourcePath := compPath.Child("resources") - allErrs = append(allErrs, ValidateResources(resourcePath, component.Resources, component.GetVersion())...) - - return allErrs -} - -// ValidateObjectMeta Validate the metadata of an object. -func ValidateObjectMeta(fldPath *field.Path, om compdesc.ObjectMetaAccessor) field.ErrorList { - allErrs := field.ErrorList{} - if len(om.GetName()) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("name"), "must specify a name")) - } - if len(om.GetVersion()) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("version"), "must specify a version")) - } - if len(om.GetLabels()) != 0 { - allErrs = append(allErrs, v1.ValidateLabels(fldPath.Child("labels"), om.GetLabels())...) - } - return allErrs -} - -// ValidateSources validates a list of sources. -// It makes sure that no duplicate sources are present. -func ValidateSources(fldPath *field.Path, sources Sources) field.ErrorList { - allErrs := field.ErrorList{} - sourceIDs := make(map[string]struct{}) - for i, src := range sources { - srcPath := fldPath.Index(i) - allErrs = append(allErrs, ValidateSource(srcPath, src, false)...) - - id := src.GetIdentity(sources) - dig := string(id.Digest()) - if _, ok := sourceIDs[dig]; ok { - allErrs = append(allErrs, field.Duplicate(srcPath, fmt.Sprintf("duplicate source %s", id))) - continue - } - sourceIDs[dig] = struct{}{} - } - return allErrs -} - -// ValidateSource validates the a component's source object. -func ValidateSource(fldPath *field.Path, src Source, access bool) field.ErrorList { - allErrs := field.ErrorList{} - if len(src.GetName()) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("name"), "must specify a name")) - } - if len(src.GetType()) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must specify a type")) - } - if src.Access == nil && access { - allErrs = append(allErrs, field.Required(fldPath.Child("access"), "must specify a access")) - } - allErrs = append(allErrs, v1.ValidateIdentity(fldPath.Child("extraIdentity"), src.ExtraIdentity)...) - return allErrs -} - -// ValidateResource validates a components resource. -func ValidateResource(fldPath *field.Path, res Resource, access bool) field.ErrorList { - allErrs := field.ErrorList{} - allErrs = append(allErrs, ValidateObjectMeta(fldPath, &res)...) - - if err := v1.ValidateRelation(fldPath.Child("relation"), res.Relation); err != nil { - allErrs = append(allErrs, err) - } - - if !v1.IsIdentity(res.Name) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), res.Name, v1.IdentityKeyValidationErrMsg)) - } - - if len(res.GetType()) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must specify a type")) - } - - if res.Access == nil && access { - allErrs = append(allErrs, field.Required(fldPath.Child("access"), "must specify a access")) - } - allErrs = append(allErrs, v1.ValidateIdentity(fldPath.Child("extraIdentity"), res.ExtraIdentity)...) - return allErrs -} - -func validateProvider(fldPath *field.Path, provider v1.ProviderName) *field.Error { - if len(provider) == 0 { - return field.Required(fldPath, "provider must be set") - } - return nil -} - -// ValidateComponentReference validates a component reference. -func ValidateComponentReference(fldPath *field.Path, cr ComponentReference) field.ErrorList { - allErrs := field.ErrorList{} - if len(cr.ComponentName) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("componentName"), "must specify a component name")) - } - allErrs = append(allErrs, ValidateObjectMeta(fldPath, &cr)...) - return allErrs -} - -// ValidateComponentReferences validates a list of component references. -// It makes sure that no duplicate sources are present. -func ValidateComponentReferences(fldPath *field.Path, refs ComponentReferences) field.ErrorList { - allErrs := field.ErrorList{} - refIDs := make(map[string]struct{}) - for i, ref := range refs { - refPath := fldPath.Index(i) - allErrs = append(allErrs, ValidateComponentReference(refPath, ref)...) - - id := ref.GetIdentity(refs) - dig := string(id.Digest()) - if _, ok := refIDs[dig]; ok { - allErrs = append(allErrs, field.Duplicate(refPath, fmt.Sprintf("duplicate component reference %s", id))) - continue - } - refIDs[dig] = struct{}{} - } - - return allErrs -} - -// ValidateResources validates a list of resources. -// It makes sure that no duplicate sources are present. -func ValidateResources(fldPath *field.Path, resources Resources, componentVersion string) field.ErrorList { - allErrs := field.ErrorList{} - resourceIDs := make(map[string]struct{}) - for i, res := range resources { - localPath := fldPath.Index(i) - allErrs = append(allErrs, ValidateResource(localPath, res, true)...) - - if err := ValidateSourceRefs(localPath.Child("sourceRef"), res.SourceRefs); err != nil { - allErrs = append(allErrs, err...) - } - - id := res.GetIdentity(resources) - dig := string(id.Digest()) - if _, ok := resourceIDs[dig]; ok { - allErrs = append(allErrs, field.Duplicate(localPath, fmt.Sprintf("duplicate resource %s", id))) - continue - } - resourceIDs[dig] = struct{}{} - } - return allErrs -} - -func ValidateSourceRefs(fldPath *field.Path, srcs []SourceRef) field.ErrorList { - allErrs := field.ErrorList{} - for i, src := range srcs { - localPath := fldPath.Index(i) - if err := v1.ValidateLabels(localPath.Child("labels"), src.Labels); err != nil { - allErrs = append(allErrs, err...) - } - } - - return allErrs -} diff --git a/pkg/contexts/ocm/compdesc/versions/v2/validation_test.go b/pkg/contexts/ocm/compdesc/versions/v2/validation_test.go deleted file mode 100644 index e8b0d9d0e..000000000 --- a/pkg/contexts/ocm/compdesc/versions/v2/validation_test.go +++ /dev/null @@ -1,477 +0,0 @@ -package v2_test - -import ( - "encoding/json" - "fmt" - "strings" - "testing" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/onsi/gomega/gstruct" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/versions/v2" - - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - meta "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/testutils" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/versions/v2/jsonscheme" - "github.com/open-component-model/ocm/pkg/runtime" -) - -func TestConfig(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "V2 Test Suite") -} - -var _ = Describe("Validation", func() { - testutils.TestCompName(jsonscheme.ResourcesComponentDescriptorV2SchemaYamlBytes()) - - Context("validator", func() { - var ( - comp *ComponentDescriptor - - ociImage1 *Resource - ociRegistry1 *ociartifact.AccessSpec - ociImage2 *Resource - ociRegistry2 *ociartifact.AccessSpec - ) - - BeforeEach(func() { - ociRegistry1 = ociartifact.New("docker/image1:1.2.3") - - unstrucOCIRegistry1, err := runtime.ToUnstructuredTypedObject(ociRegistry1) - Expect(err).ToNot(HaveOccurred()) - - ociImage1 = &Resource{ - ElementMeta: ElementMeta{ - Name: "image1", - Version: "1.2.3", - }, - Relation: meta.ExternalRelation, - Access: unstrucOCIRegistry1, - } - ociRegistry2 = ociartifact.New("docker/image1:1.2.3") - unstrucOCIRegistry2, err := runtime.ToUnstructuredTypedObject(ociRegistry2) - Expect(err).ToNot(HaveOccurred()) - ociImage2 = &Resource{ - ElementMeta: ElementMeta{ - Name: "image2", - Version: "1.2.3", - }, - Relation: meta.ExternalRelation, - Access: unstrucOCIRegistry2, - } - - comp = &ComponentDescriptor{ - Metadata: meta.Metadata{ - Version: SchemaVersion, - }, - ComponentSpec: ComponentSpec{ - ObjectMeta: ObjectMeta{ - Name: "my-comp", - Version: "1.2.3", - }, - Provider: "external", - RepositoryContexts: nil, - Sources: nil, - ComponentReferences: nil, - Resources: []Resource{*ociImage1, *ociImage2}, - }, - } - }) - - Context("#Metadata", func() { - It("should forbid if the component schemaVersion is missing", func() { - comp := ComponentDescriptor{ - Metadata: meta.Metadata{}, - } - - errList := Validate(nil, &comp) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeRequired), - "Field": Equal("meta.schemaVersion"), - })))) - }) - - It("should pass if the component schemaVersion is defined", func() { - errList := Validate(nil, comp) - Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeRequired), - "Field": Equal("meta.schemaVersion"), - })))) - }) - }) - - Context("#ObjectMeta", func() { - It("should forbid if the component's version is missing", func() { - comp := ComponentDescriptor{} - errList := Validate(nil, &comp) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeRequired), - "Field": Equal("component.name"), - })))) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeRequired), - "Field": Equal("component.version"), - })))) - }) - - It("should forbid if the component's name is missing", func() { - comp := ComponentDescriptor{} - errList := Validate(nil, &comp) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeRequired), - "Field": Equal("component.name"), - })))) - }) - }) - - Context("#Sources", func() { - It("should forbid if a duplicated component's source is defined", func() { - comp.Sources = []Source{ - { - SourceMeta: SourceMeta{ - ElementMeta: ElementMeta{ - Name: "a", - }, - }, - Access: runtime.NewEmptyUnstructured("custom"), - }, - { - SourceMeta: SourceMeta{ - ElementMeta: ElementMeta{ - Name: "a", - }, - }, - Access: runtime.NewEmptyUnstructured("custom"), - }, - } - errList := Validate(nil, comp) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeDuplicate), - "Field": Equal("component.sources[1]"), - })))) - }) - }) - - Context("#ComponentReferences", func() { - It("should pass if a reference is set", func() { - comp.ComponentReferences = []ComponentReference{ - { - ElementMeta: ElementMeta{ - Name: "test", - Version: "1.2.3", - }, - ComponentName: "test", - }, - } - errList := Validate(nil, comp) - Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeRequired), - "Field": Equal("component.componentReferences[0].name"), - })))) - Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeRequired), - "Field": Equal("component.componentReferences[0].version"), - })))) - }) - - It("should forbid if a reference's name is missing", func() { - comp.ComponentReferences = []ComponentReference{ - { - ElementMeta: ElementMeta{ - Version: "1.2.3", - }, - ComponentName: "test", - }, - } - errList := Validate(nil, comp) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeRequired), - "Field": Equal("component.componentReferences[0].name"), - })))) - }) - - It("should forbid if a reference's component name is missing", func() { - comp.ComponentReferences = []ComponentReference{ - { - ElementMeta: ElementMeta{ - Name: "test", - Version: "1.2.3", - }, - }, - } - errList := Validate(nil, comp) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeRequired), - "Field": Equal("component.componentReferences[0].componentName"), - })))) - }) - - It("should forbid if a reference's version is missing", func() { - comp.ComponentReferences = []ComponentReference{ - { - ElementMeta: ElementMeta{ - Name: "test", - }, - ComponentName: "test", - }, - } - errList := Validate(nil, comp) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeRequired), - "Field": Equal("component.componentReferences[0].version"), - })))) - }) - - It("should forbid if a duplicated component reference is defined", func() { - comp.ComponentReferences = []ComponentReference{ - { - ElementMeta: ElementMeta{ - Name: "test", - }, - }, - { - ElementMeta: ElementMeta{ - Name: "test", - }, - }, - } - errList := Validate(nil, comp) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeDuplicate), - "Field": Equal("component.componentReferences[1]"), - })))) - }) - }) - - Context("#Resources", func() { - It("should handle srcRefs", func() { - comp.Name = "acme.org/test" - comp.RepositoryContexts = []*runtime.UnstructuredTypedObject{} - comp.ComponentReferences = ComponentReferences{} - comp.Sources = Sources{} - comp.Resources = []Resource{ - { - ElementMeta: ElementMeta{ - Name: "test", - Version: "v1", - }, - Type: "test", - Relation: meta.LocalRelation, - Access: runtime.NewEmptyUnstructured("access"), - SourceRefs: []SourceRef{ - { - IdentitySelector: map[string]string{ - "name": "test", - }, - Labels: nil, - }, - }, - }, - } - v := &DescriptorVersion{} - data := Must(json.Marshal(comp)) - fmt.Printf("%s\n", string(data)) - r := Must(v.Decode(data, &compdesc.DecodeOptions{Codec: compdesc.DefaultYAMLCodec})) - Expect(r).To(DeepEqual(comp)) - - old := strings.Replace(string(data), "srcRefs", "srcRef", -1) - r = Must(v.Decode([]byte(old), &compdesc.DecodeOptions{Codec: compdesc.DefaultYAMLCodec})) - Expect(v.ConvertTo(r)).To(DeepEqual(Must(v.ConvertTo(comp)))) - }) - - It("should forbid if a resource name contains invalid characters", func() { - comp.Resources = []Resource{ - { - ElementMeta: ElementMeta{ - Name: "test$", - }, - }, - { - ElementMeta: ElementMeta{ - Name: "testšŸ™…", - }, - }, - } - errList := Validate(nil, comp) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeInvalid), - "Field": Equal("component.resources[0].name"), - })))) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeInvalid), - "Field": Equal("component.resources[1].name"), - })))) - }) - - It("should forbid if a duplicated local resource is defined", func() { - comp.Resources = []Resource{ - { - ElementMeta: ElementMeta{ - Name: "test", - }, - }, - { - ElementMeta: ElementMeta{ - Name: "test", - }, - }, - } - errList := Validate(nil, comp) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeDuplicate), - "Field": Equal("component.resources[1]"), - })))) - }) - - It("should forbid if a duplicated resource with additional identity labels is defined", func() { - comp.Resources = []Resource{ - { - ElementMeta: ElementMeta{ - Name: "test", - ExtraIdentity: meta.Identity{ - "my-id": "some-id", - }, - }, - }, - { - ElementMeta: ElementMeta{ - Name: "test", - ExtraIdentity: meta.Identity{ - "my-id": "some-id", - }, - }, - }, - } - errList := Validate(nil, comp) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeDuplicate), - "Field": Equal("component.resources[1]"), - })))) - }) - - It("should pass if a duplicated resource has the same name but with different additional identity labels", func() { - comp.Resources = []Resource{ - { - ElementMeta: ElementMeta{ - Name: "test", - ExtraIdentity: meta.Identity{ - "my-id": "some-id", - }, - }, - }, - { - ElementMeta: ElementMeta{ - Name: "test", - }, - }, - } - errList := Validate(nil, comp) - Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeDuplicate), - "Field": Equal("component.resources[1]"), - })))) - Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeDuplicate), - "Field": Equal("component.resources[0]"), - })))) - }) - }) - - Context("#labels", func() { - It("should forbid if labels are defined multiple times in the same context", func() { - comp.ComponentReferences = []ComponentReference{ - { - ElementMeta: ElementMeta{ - Name: "test", - Version: "1.2.3", - Labels: []meta.Label{ - { - Name: "l1", - Value: []byte{}, - }, - { - Name: "l1", - Value: []byte{}, - }, - }, - }, - ComponentName: "test", - }, - } - - errList := Validate(nil, comp) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeDuplicate), - "Field": Equal("component.componentReferences[0].labels[1]"), - })))) - }) - - It("should pass if labels are defined multiple times in the same context with differnet names", func() { - comp.ComponentReferences = []ComponentReference{ - { - ElementMeta: ElementMeta{ - Name: "test", - Version: "1.2.3", - Labels: []meta.Label{ - { - Name: "l1", - Value: []byte{}, - }, - { - Name: "l2", - Value: []byte{}, - }, - }, - }, - ComponentName: "test", - }, - } - - errList := Validate(nil, comp) - Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeDuplicate), - "Field": Equal("component.componentReferences[0].labels[1]"), - })))) - }) - }) - - Context("#Identity", func() { - It("should pass valid identity labels", func() { - identity := meta.Identity{ - "my-l1": "test", - "my-l2": "test", - } - errList := meta.ValidateIdentity(field.NewPath("identity"), identity) - Expect(errList).To(HaveLen(0)) - }) - - It("should forbid if a identity label define the name", func() { - identity := meta.Identity{ - "name": "test", - } - errList := meta.ValidateIdentity(field.NewPath("identity"), identity) - Expect(errList).To(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeForbidden), - "Field": Equal("identity[name]"), - })))) - }) - - It("should forbid if a identity label defines a key with invalid characters", func() { - identity := meta.Identity{ - "my-l1!": "test", - } - errList := meta.ValidateIdentity(field.NewPath("identity"), identity) - Expect(errList).ToNot(ContainElement(PointTo(MatchFields(IgnoreExtras, Fields{ - "Type": Equal(field.ErrorTypeForbidden), - "Field": Equal("identity[my-l1!]"), - })))) - }) - }) - }) -}) diff --git a/pkg/contexts/ocm/compdesc/versions/v2/version.go b/pkg/contexts/ocm/compdesc/versions/v2/version.go deleted file mode 100644 index 8ce8d94c7..000000000 --- a/pkg/contexts/ocm/compdesc/versions/v2/version.go +++ /dev/null @@ -1,332 +0,0 @@ -package v2 - -import ( - "encoding/json" - - "github.com/mandelsoft/goutils/errors" - - "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/contexts/ocm/compdesc/versions/v2/jsonscheme" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const SchemaVersion = "v2" - -func init() { - compdesc.RegisterScheme(&DescriptorVersion{}) -} - -type DescriptorVersion struct{} - -var _ compdesc.Scheme = (*DescriptorVersion)(nil) - -func (v *DescriptorVersion) GetVersion() string { - return SchemaVersion -} - -func (v *DescriptorVersion) Decode(data []byte, opts *compdesc.DecodeOptions) (compdesc.ComponentDescriptorVersion, error) { - var cd ComponentDescriptor - if !opts.DisableValidation { - if err := jsonscheme.Validate(data); err != nil { - return nil, err - } - } - var err error - if opts.StrictMode { - err = opts.Codec.DecodeStrict(data, &cd) - } else { - err = opts.Codec.Decode(data, &cd) - } - if err != nil { - return nil, err - } - - if err := cd.Default(); err != nil { - return nil, err - } - - if !opts.DisableValidation { - err = cd.Validate() - if err != nil { - return nil, err - } - } - return &cd, err -} - -//////////////////////////////////////////////////////////////////////////////// -// convert to internal version -//////////////////////////////////////////////////////////////////////////////// - -func (v *DescriptorVersion) ConvertTo(obj compdesc.ComponentDescriptorVersion) (out *compdesc.ComponentDescriptor, err error) { - if obj == nil { - return nil, nil - } - in, ok := obj.(*ComponentDescriptor) - if !ok { - return nil, errors.Newf("%T is no version v2 descriptor", obj) - } - - defer compdesc.CatchConversionError(&err) - var provider metav1.Provider - err = json.Unmarshal([]byte(in.Provider), &provider) - if err != nil { - provider.Name = in.Provider - provider.Labels = nil - } - - out = &compdesc.ComponentDescriptor{ - Metadata: compdesc.Metadata{ConfiguredVersion: in.Metadata.Version}, - ComponentSpec: compdesc.ComponentSpec{ - ObjectMeta: metav1.ObjectMeta{ - Name: in.Name, - Version: in.Version, - Labels: in.Labels.Copy(), - Provider: provider, - CreationTime: in.CreationTime, - }, - RepositoryContexts: in.RepositoryContexts.Copy(), - Sources: convertSourcesTo(in.Sources), - Resources: convertResourcesTo(in.Resources), - References: convertComponentReferencesTo(in.ComponentReferences), - }, - Signatures: in.Signatures.Copy(), - NestedDigests: in.NestedDigests.Copy(), - } - return out, nil -} - -func convertComponentReferenceTo(in ComponentReference) compdesc.ComponentReference { - return compdesc.ComponentReference{ - ElementMeta: convertElementMetaTo(in.ElementMeta), - ComponentName: in.ComponentName, - Digest: in.Digest.Copy(), - } -} - -func convertComponentReferencesTo(in []ComponentReference) compdesc.References { - if in == nil { - return nil - } - out := make(compdesc.References, len(in)) - for i := range in { - out[i] = convertComponentReferenceTo(in[i]) - } - return out -} - -func convertSourceTo(in Source) compdesc.Source { - return compdesc.Source{ - SourceMeta: compdesc.SourceMeta{ - ElementMeta: convertElementMetaTo(in.ElementMeta), - Type: in.Type, - }, - Access: compdesc.GenericAccessSpec(in.Access.DeepCopy()), - } -} - -func convertSourcesTo(in Sources) compdesc.Sources { - if in == nil { - return nil - } - out := make(compdesc.Sources, len(in)) - for i := range in { - out[i] = convertSourceTo(in[i]) - } - return out -} - -func convertElementMetaTo(in ElementMeta) compdesc.ElementMeta { - return compdesc.ElementMeta{ - Name: in.Name, - Version: in.Version, - ExtraIdentity: in.ExtraIdentity.Copy(), - Labels: in.Labels.Copy(), - } -} - -func convertResourceTo(in Resource) compdesc.Resource { - srcRefs := ConvertSourcerefsTo(in.SourceRefs) - if srcRefs == nil { - srcRefs = ConvertSourcerefsTo(in.SourceRef) - } - return compdesc.Resource{ - ResourceMeta: compdesc.ResourceMeta{ - ElementMeta: convertElementMetaTo(in.ElementMeta), - Type: in.Type, - Relation: in.Relation, - SourceRefs: srcRefs, - Digest: in.Digest.Copy(), - }, - Access: compdesc.GenericAccessSpec(in.Access), - } -} - -func convertResourcesTo(in Resources) compdesc.Resources { - if in == nil { - return nil - } - out := make(compdesc.Resources, len(in)) - for i := range in { - out[i] = convertResourceTo(in[i]) - } - return out -} - -func convertSourceRefTo(in SourceRef) compdesc.SourceRef { - return compdesc.SourceRef{ - IdentitySelector: in.IdentitySelector.Copy(), - Labels: in.Labels.Copy(), - } -} - -func ConvertSourcerefsTo(in []SourceRef) []compdesc.SourceRef { - if in == nil { - return nil - } - out := make([]compdesc.SourceRef, len(in)) - for i := range in { - out[i] = convertSourceRefTo(in[i]) - } - return out -} - -//////////////////////////////////////////////////////////////////////////////// -// convert from internal version -//////////////////////////////////////////////////////////////////////////////// - -func (v *DescriptorVersion) ConvertFrom(in *compdesc.ComponentDescriptor) (compdesc.ComponentDescriptorVersion, error) { - if in == nil { - return nil, nil - } - provider := in.Provider.Name - if len(in.Provider.Labels) != 0 { - data, err := json.Marshal(in.Provider) - if err != nil { - return nil, errors.Wrapf(err, "cannot marshal provider") - } - provider = metav1.ProviderName(data) - } - out := &ComponentDescriptor{ - Metadata: metav1.Metadata{ - Version: SchemaVersion, - }, - ComponentSpec: ComponentSpec{ - ObjectMeta: ObjectMeta{ - Name: in.Name, - Version: in.Version, - Labels: in.Labels.Copy(), - CreationTime: in.CreationTime, - }, - RepositoryContexts: in.RepositoryContexts.Copy(), - Provider: provider, - Sources: convertSourcesFrom(in.Sources), - Resources: convertResourcesFrom(in.Resources), - ComponentReferences: convertComponentReferencesFrom(in.References), - }, - Signatures: in.Signatures.Copy(), - NestedDigests: in.NestedDigests.Copy(), - } - if err := out.Default(); err != nil { - return nil, err - } - return out, nil -} - -func convertComponentReferenceFrom(in compdesc.ComponentReference) ComponentReference { - return ComponentReference{ - ElementMeta: convertElementMetaFrom(in.ElementMeta), - ComponentName: in.ComponentName, - Digest: in.Digest.Copy(), - } -} - -func convertComponentReferencesFrom(in []compdesc.ComponentReference) []ComponentReference { - if in == nil { - return nil - } - out := make([]ComponentReference, len(in)) - for i := range in { - out[i] = convertComponentReferenceFrom(in[i]) - } - return out -} - -func convertSourceFrom(in compdesc.Source) Source { - acc, err := runtime.ToUnstructuredTypedObject(in.Access) - if err != nil { - compdesc.ThrowConversionError(err) - } - return Source{ - SourceMeta: SourceMeta{ - ElementMeta: convertElementMetaFrom(in.ElementMeta), - Type: in.Type, - }, - Access: acc, - } -} - -func convertSourcesFrom(in compdesc.Sources) Sources { - if in == nil { - return nil - } - out := make(Sources, len(in)) - for i := range in { - out[i] = convertSourceFrom(in[i]) - } - return out -} - -func convertElementMetaFrom(in compdesc.ElementMeta) ElementMeta { - return ElementMeta{ - Name: in.Name, - Version: in.Version, - ExtraIdentity: in.ExtraIdentity.Copy(), - Labels: in.Labels.Copy(), - } -} - -func convertResourceFrom(in compdesc.Resource) Resource { - acc, err := runtime.ToUnstructuredTypedObject(in.Access) - if err != nil { - compdesc.ThrowConversionError(err) - } - return Resource{ - ElementMeta: convertElementMetaFrom(in.ElementMeta), - Type: in.Type, - Relation: in.Relation, - SourceRefs: convertSourceRefsFrom(in.SourceRefs), - Access: acc, - Digest: in.Digest.Copy(), - } -} - -func convertResourcesFrom(in compdesc.Resources) Resources { - if in == nil { - return nil - } - out := make(Resources, len(in)) - for i := range in { - out[i] = convertResourceFrom(in[i]) - } - return out -} - -func convertSourceRefFrom(in compdesc.SourceRef) SourceRef { - return SourceRef{ - IdentitySelector: in.IdentitySelector.Copy(), - Labels: in.Labels.Copy(), - } -} - -func convertSourceRefsFrom(in []compdesc.SourceRef) []SourceRef { - if in == nil { - return nil - } - out := make([]SourceRef, len(in)) - for i := range in { - out[i] = convertSourceRefFrom(in[i]) - } - return out -} diff --git a/pkg/contexts/ocm/compdesc/versions/v2/zz_generated.deepcopy.go b/pkg/contexts/ocm/compdesc/versions/v2/zz_generated.deepcopy.go deleted file mode 100644 index da32ccf2e..000000000 --- a/pkg/contexts/ocm/compdesc/versions/v2/zz_generated.deepcopy.go +++ /dev/null @@ -1,266 +0,0 @@ -//go:build !ignore_autogenerated - -// Code generated by controller-gen. DO NOT EDIT. - -package v2 - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ComponentDescriptor) DeepCopyInto(out *ComponentDescriptor) { - *out = *in - out.Metadata = in.Metadata - in.ComponentSpec.DeepCopyInto(&out.ComponentSpec) - if in.Signatures != nil { - in, out := &in.Signatures, &out.Signatures - *out = make(v1.Signatures, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.NestedDigests != nil { - in, out := &in.NestedDigests, &out.NestedDigests - *out = make(v1.NestedDigests, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentDescriptor. -func (in *ComponentDescriptor) DeepCopy() *ComponentDescriptor { - if in == nil { - return nil - } - out := new(ComponentDescriptor) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ComponentReference) DeepCopyInto(out *ComponentReference) { - *out = *in - in.ElementMeta.DeepCopyInto(&out.ElementMeta) - if in.Digest != nil { - in, out := &in.Digest, &out.Digest - *out = new(v1.DigestSpec) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentReference. -func (in *ComponentReference) DeepCopy() *ComponentReference { - if in == nil { - return nil - } - out := new(ComponentReference) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ComponentSpec) DeepCopyInto(out *ComponentSpec) { - *out = *in - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.RepositoryContexts != nil { - in, out := &in.RepositoryContexts, &out.RepositoryContexts - *out = make(runtime.UnstructuredTypedObjectList, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Sources != nil { - in, out := &in.Sources, &out.Sources - *out = make(Sources, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.ComponentReferences != nil { - in, out := &in.ComponentReferences, &out.ComponentReferences - *out = make(ComponentReferences, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Resources != nil { - in, out := &in.Resources, &out.Resources - *out = make(Resources, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentSpec. -func (in *ComponentSpec) DeepCopy() *ComponentSpec { - if in == nil { - return nil - } - out := new(ComponentSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ElementMeta) DeepCopyInto(out *ElementMeta) { - *out = *in - if in.ExtraIdentity != nil { - in, out := &in.ExtraIdentity, &out.ExtraIdentity - *out = make(v1.Identity, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(v1.Labels, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ElementMeta. -func (in *ElementMeta) DeepCopy() *ElementMeta { - if in == nil { - return nil - } - out := new(ElementMeta) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ObjectMeta) DeepCopyInto(out *ObjectMeta) { - *out = *in - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(v1.Labels, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.CreationTime != nil { - in, out := &in.CreationTime, &out.CreationTime - *out = new(v1.Timestamp) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectMeta. -func (in *ObjectMeta) DeepCopy() *ObjectMeta { - if in == nil { - return nil - } - out := new(ObjectMeta) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Resource) DeepCopyInto(out *Resource) { - *out = *in - in.ElementMeta.DeepCopyInto(&out.ElementMeta) - if in.SourceRefs != nil { - in, out := &in.SourceRefs, &out.SourceRefs - *out = make([]SourceRef, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.SourceRef != nil { - in, out := &in.SourceRef, &out.SourceRef - *out = make([]SourceRef, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Access != nil { - in, out := &in.Access, &out.Access - *out = (*in).DeepCopy() - } - if in.Digest != nil { - in, out := &in.Digest, &out.Digest - *out = new(v1.DigestSpec) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Resource. -func (in *Resource) DeepCopy() *Resource { - if in == nil { - return nil - } - out := new(Resource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Source) DeepCopyInto(out *Source) { - *out = *in - in.SourceMeta.DeepCopyInto(&out.SourceMeta) - if in.Access != nil { - in, out := &in.Access, &out.Access - *out = (*in).DeepCopy() - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Source. -func (in *Source) DeepCopy() *Source { - if in == nil { - return nil - } - out := new(Source) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SourceMeta) DeepCopyInto(out *SourceMeta) { - *out = *in - in.ElementMeta.DeepCopyInto(&out.ElementMeta) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceMeta. -func (in *SourceMeta) DeepCopy() *SourceMeta { - if in == nil { - return nil - } - out := new(SourceMeta) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SourceRef) DeepCopyInto(out *SourceRef) { - *out = *in - if in.IdentitySelector != nil { - in, out := &in.IdentitySelector, &out.IdentitySelector - *out = make(v1.StringMap, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(v1.Labels, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceRef. -func (in *SourceRef) DeepCopy() *SourceRef { - if in == nil { - return nil - } - out := new(SourceRef) - in.DeepCopyInto(out) - return out -} diff --git a/pkg/contexts/ocm/config/config_test.go b/pkg/contexts/ocm/config/config_test.go deleted file mode 100644 index 3e0098b3f..000000000 --- a/pkg/contexts/ocm/config/config_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package config_test - -import ( - "encoding/json" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/config" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ocireg" -) - -func normalize(i interface{}) ([]byte, error) { - data, err := json.Marshal(i) - if err != nil { - return nil, err - } - var generic map[string]interface{} - err = json.Unmarshal(data, &generic) - if err != nil { - return nil, err - } - return json.Marshal(generic) -} - -var _ = Describe("oci config", func() { - spec := ocireg.NewRepositorySpec("gcr.io", nil) - data, err := normalize(spec) - Expect(err).To(Succeed()) - - specdata := "{\"aliases\":{\"alias\":" + string(data) + "},\"type\":\"" + config.ConfigType + "\"}" - - Context("serialize", func() { - It("serializes config", func() { - cfg := config.New() - err := cfg.SetAlias("alias", spec) - Expect(err).To(Succeed()) - - data, err := normalize(cfg) - - Expect(err).To(Succeed()) - Expect(data).To(Equal([]byte(specdata))) - - cfg2 := config.New() - err = json.Unmarshal(data, cfg2) - Expect(err).To(Succeed()) - Expect(cfg2).To(Equal(cfg)) - }) - }) - - Context("apply", func() { - It("applies directly", func() { - ctx := cpi.New() - - cfg := config.New() - err := cfg.SetAlias("alias", spec) - Expect(err).To(Succeed()) - - Expect(cfg.ApplyTo(ctx.ConfigContext(), ctx)).To(Succeed()) - - found := ctx.GetAlias("alias") - Expect(found).To(Equal(cfg.Aliases["alias"])) - }) - - It("applies via config context", func() { - ctx := cpi.New() - - cfg := config.New() - err := cfg.SetAlias("alias", spec) - Expect(err).To(Succeed()) - - Expect(ctx.ConfigContext().ApplyConfig(cfg, "programmatic")).To(Succeed()) - - found := ctx.GetAlias("alias") - Expect(found).To(Equal(cfg.Aliases["alias"])) - }) - }) -}) diff --git a/pkg/contexts/ocm/config/type.go b/pkg/contexts/ocm/config/type.go deleted file mode 100644 index 4e69d689d..000000000 --- a/pkg/contexts/ocm/config/type.go +++ /dev/null @@ -1,137 +0,0 @@ -package config - -import ( - "github.com/open-component-model/ocm/pkg/contexts/config" - cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - ConfigType = "ocm" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX - ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" -) - -func init() { - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1)) -} - -// Config describes a memory based config interface. -type Config struct { - runtime.ObjectVersionedType `json:",inline"` - Aliases map[string]*cpi.GenericRepositorySpec `json:"aliases,omitempty"` - Resolver []ResolverRule `json:"resolvers,omitempty"` -} - -type ResolverRule struct { - Prefix string `json:"prefix,omitempty"` - Prio *int `json:"priority,omitempty"` - Spec *cpi.GenericRepositorySpec `json:"repository"` -} - -// New creates a new memory ConfigSpec. -func New() *Config { - return &Config{ - ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), - } -} - -func (a *Config) GetType() string { - return ConfigType -} - -func (a *Config) SetAlias(name string, spec cpi.RepositorySpec) error { - g, err := cpi.ToGenericRepositorySpec(spec) - if err != nil { - return err - } - if a.Aliases == nil { - a.Aliases = map[string]*cpi.GenericRepositorySpec{} - } - a.Aliases[name] = g - return nil -} - -func (a *Config) AddResolverRule(prefix string, spec cpi.RepositorySpec, prio ...int) error { - gen, err := cpi.ToGenericRepositorySpec(spec) - if err != nil { - return err - } - - r := ResolverRule{ - Prefix: prefix, - Spec: gen, - } - if len(prio) > 0 { - p := prio[0] - r.Prio = &p - } - - a.Resolver = append(a.Resolver, r) - return nil -} - -func (a *Config) ApplyTo(ctx config.Context, target interface{}) error { - t, ok := target.(cpi.Context) - if !ok { - return config.ErrNoContext(ConfigType) - } - for n, s := range a.Aliases { - t.SetAlias(n, s) - } - - if len(a.Resolver) > 0 { - for _, rule := range a.Resolver { - if rule.Prio != nil { - t.AddResolverRule(rule.Prefix, rule.Spec, *rule.Prio) - } else { - t.AddResolverRule(rule.Prefix, rule.Spec) - } - } - } - return nil -} - -const usage = ` -The config type ` + ConfigType + ` can be used to set some -configurations for an OCM context; - -
-    type: ` + ConfigType + `
-    aliases:
-       myrepo: 
-          type: <any repository type>
-          <specification attributes>
-          ...
-    resolvers:
-      - repository:
-          type: <any repository type>
-          <specification attributes>
-          ...
-        prefix: ghcr.io/open-component-model/ocm
-        priority: 10
-
- -With aliases repository alias names can be mapped to a repository specification. -The alias name can be used in a string notation for an OCM repository. - -Resolvers define a list of OCM repository specifications to be used to resolve -dedicated component versions. These settings are used to compose a standard -component version resolver provided for an OCM context. Optionally, a component -name prefix can be given. It limits the usage of the repository to resolve only -components with the given name prefix (always complete name segments). -An optional priority can be used to influence the lookup order. Larger value -means higher priority (default 10). - -All matching entries are tried to lookup a component version in the following -order: -- highest priority first -- longest matching sequence of component name segments first. - -If resolvers are defined, it is possible to use component version names on the -command line without a repository. The names are resolved with the specified -resolution rule. -They are also used as default lookup repositories to lookup component references -for recursive operations on component versions (--lookup option). -` diff --git a/pkg/contexts/ocm/consts/deprecated.go b/pkg/contexts/ocm/consts/deprecated.go deleted file mode 100644 index dbb35a422..000000000 --- a/pkg/contexts/ocm/consts/deprecated.go +++ /dev/null @@ -1,18 +0,0 @@ -package consts - -import ( - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/extraid" -) - -const ( - // Deprecated: use extraid.SystemIdentityName. - SystemIdentityName = metav1.SystemIdentityName - // Deprecated: use extraid.SystemIdentityVersion . - SystemIdentityVersion = metav1.SystemIdentityVersion - - // Deprecated: use extraid.ExecutableOperatingSystem . - ExecutableOperatingSystem = extraid.ExecutableOperatingSystem - // Deprecated: use extraid.ExecutableArchitecture . - ExecutableArchitecture = extraid.ExecutableArchitecture -) diff --git a/pkg/contexts/ocm/context/interface.go b/pkg/contexts/ocm/context/interface.go deleted file mode 100644 index 1448b6325..000000000 --- a/pkg/contexts/ocm/context/interface.go +++ /dev/null @@ -1,52 +0,0 @@ -package context - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" -) - -type ( - Context = internal.Context - ContextProvider = internal.ContextProvider - LocalContextProvider = internal.LocalContextProvider - ComponentVersionResolver = internal.ComponentVersionResolver - Repository = internal.Repository - RepositorySpecHandlers = internal.RepositorySpecHandlers - RepositorySpecHandler = internal.RepositorySpecHandler - UniformRepositorySpec = internal.UniformRepositorySpec - ComponentLister = internal.ComponentLister - ComponentAccess = internal.ComponentAccess - ComponentVersionAccess = internal.ComponentVersionAccess - AccessSpec = internal.AccessSpec - GenericAccessSpec = internal.GenericAccessSpec - HintProvider = internal.HintProvider - AccessMethod = internal.AccessMethodImpl - AccessType = internal.AccessType - DataAccess = internal.DataAccess - BlobAccess = internal.BlobAccess - SourceAccess = internal.SourceAccess - SourceMeta = internal.SourceMeta - ResourceAccess = internal.ResourceAccess - ResourceMeta = internal.ResourceMeta - RepositorySpec = internal.RepositorySpec - GenericRepositorySpec = internal.GenericRepositorySpec - IntermediateRepositorySpecAspect = internal.IntermediateRepositorySpecAspect - RepositoryType = internal.RepositoryType - RepositoryTypeScheme = internal.RepositoryTypeScheme - RepositoryDelegationRegistry = internal.RepositoryDelegationRegistry - AccessTypeScheme = internal.AccessTypeScheme - ComponentReference = internal.ComponentReference -) - -type ( - DigesterType = internal.DigesterType - BlobDigester = internal.BlobDigester - BlobDigesterRegistry = internal.BlobDigesterRegistry - DigestDescriptor = internal.DigestDescriptor - HasherProvider = internal.HasherProvider - Hasher = internal.Hasher -) - -type ( - BlobHandlerRegistry = internal.BlobHandlerRegistry - BlobHandler = internal.BlobHandler -) diff --git a/pkg/contexts/ocm/cpi/accspeccpi/accesstypes.go b/pkg/contexts/ocm/cpi/accspeccpi/accesstypes.go deleted file mode 100644 index 21454dffc..000000000 --- a/pkg/contexts/ocm/cpi/accspeccpi/accesstypes.go +++ /dev/null @@ -1,44 +0,0 @@ -package accspeccpi - -import ( - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets/flagsetscheme" - "github.com/open-component-model/ocm/pkg/runtime" -) - -type AccessTypeVersionScheme = runtime.TypeVersionScheme[AccessSpec, AccessType] - -func NewAccessTypeVersionScheme(kind string) AccessTypeVersionScheme { - return runtime.NewTypeVersionScheme[AccessSpec, AccessType](kind, newStrictAccessTypeScheme()) -} - -func RegisterAccessType(atype AccessType) { - defaultAccessTypeScheme.Register(atype) -} - -func RegisterAccessTypeVersions(s AccessTypeVersionScheme) { - defaultAccessTypeScheme.AddKnownTypes(s) -} - -//////////////////////////////////////////////////////////////////////////////// - -type AccessSpecFormatVersionRegistry = runtime.FormatVersionRegistry[AccessSpec] - -func NewAccessSpecFormatVersionRegistry() AccessSpecFormatVersionRegistry { - return runtime.NewFormatVersionRegistry[AccessSpec]() -} - -func MustNewAccessSpecMultiFormatVersion(kind string, formats AccessSpecFormatVersionRegistry) runtime.FormatVersion[AccessSpec] { - return runtime.MustNewMultiFormatVersion[AccessSpec](kind, formats) -} - -func NewAccessSpecType[I AccessSpec](name string, opts ...AccessSpecTypeOption) AccessType { - return flagsetscheme.NewTypedObjectTypeObject[AccessSpec](runtime.NewVersionedTypedObjectType[AccessSpec, I](name), opts...) -} - -func NewAccessSpecTypeByConverter[I AccessSpec, V runtime.VersionedTypedObject](name string, converter runtime.Converter[I, V], opts ...AccessSpecTypeOption) AccessType { - return flagsetscheme.NewTypedObjectTypeObject[AccessSpec](runtime.NewVersionedTypedObjectTypeByConverter[AccessSpec, I, V](name, converter), opts...) -} - -func NewAccessSpecTypeByFormatVersion(name string, fmt runtime.FormatVersion[AccessSpec], opts ...AccessSpecTypeOption) AccessType { - return flagsetscheme.NewTypedObjectTypeObject[AccessSpec](runtime.NewVersionedTypedObjectTypeByFormatVersion[AccessSpec](name, fmt), opts...) -} diff --git a/pkg/contexts/ocm/cpi/accspeccpi/interface.go b/pkg/contexts/ocm/cpi/accspeccpi/interface.go deleted file mode 100644 index 3e541635d..000000000 --- a/pkg/contexts/ocm/cpi/accspeccpi/interface.go +++ /dev/null @@ -1,33 +0,0 @@ -package accspeccpi - -import ( - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" -) - -type ( - Context = internal.Context - ContextProvider = internal.ContextProvider - - AccessType = internal.AccessType - - AccessMethodImpl = internal.AccessMethodImpl - AccessMethod = internal.AccessMethod - AccessSpec = internal.AccessSpec - AccessSpecRef = internal.AccessSpecRef - - HintProvider = internal.HintProvider - GlobalAccessProvider = internal.GlobalAccessProvider - CosumerIdentityProvider = credentials.ConsumerIdentityProvider - - ComponentVersionAccess = internal.ComponentVersionAccess -) - -var ( - newStrictAccessTypeScheme = internal.NewStrictAccessTypeScheme - defaultAccessTypeScheme = internal.DefaultAccessTypeScheme -) - -func NewAccessSpecRef(spec AccessSpec) *AccessSpecRef { - return internal.NewAccessSpecRef(spec) -} diff --git a/pkg/contexts/ocm/cpi/accspeccpi/method.go b/pkg/contexts/ocm/cpi/accspeccpi/method.go deleted file mode 100644 index 2063b4146..000000000 --- a/pkg/contexts/ocm/cpi/accspeccpi/method.go +++ /dev/null @@ -1,129 +0,0 @@ -package accspeccpi - -import ( - "io" - "sync" - - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/utils" -) - -//////////////////////////////////////////////////////////////////////////////// - -type DefaultAccessMethodImpl struct { - lock sync.Mutex - blob blobaccess.BlobAccess - - factory BlobAccessFactory - comp ComponentVersionAccess - spec AccessSpec - mime string - digest digest.Digest - local bool -} - -var ( - _ AccessMethodImpl = (*DefaultAccessMethodImpl)(nil) - _ blobaccess.DigestSource = (*DefaultAccessMethodImpl)(nil) -) - -type BlobAccessFactory func() (blobaccess.BlobAccess, error) - -func NewDefaultMethod(c ComponentVersionAccess, a AccessSpec, digest digest.Digest, mime string, fac BlobAccessFactory, local ...bool) AccessMethod { - m, _ := AccessMethodForImplementation(NewDefaultMethodImpl(c, a, digest, mime, fac, local...), nil) - return m -} - -func NewDefaultMethodImpl(c ComponentVersionAccess, a AccessSpec, digest digest.Digest, mime string, fac BlobAccessFactory, local ...bool) AccessMethodImpl { - return &DefaultAccessMethodImpl{ - spec: a, - comp: c, - mime: mime, - digest: digest, - factory: fac, - local: utils.Optional(local...), - } -} - -func NewDefaultMethodForBlobAccess(c ComponentVersionAccess, a AccessSpec, digest digest.Digest, blob blobaccess.BlobAccess, local ...bool) (AccessMethod, error) { - return AccessMethodForImplementation(NewDefaultMethodImplForBlobAccess(c, a, digest, blob, local...)) -} - -func NewDefaultMethodImplForBlobAccess(c ComponentVersionAccess, a AccessSpec, digest digest.Digest, blob blobaccess.BlobAccess, local ...bool) (AccessMethodImpl, error) { - blob, err := blob.Dup() - if err != nil { - return nil, err - } - return &DefaultAccessMethodImpl{ - spec: a, - blob: blob, - comp: c, - mime: blob.MimeType(), - digest: digest, - factory: nil, - local: utils.Optional(local...), - }, nil -} - -func (m *DefaultAccessMethodImpl) getAccess() (blobaccess.BlobAccess, error) { - m.lock.Lock() - defer m.lock.Unlock() - if m.blob == nil { - acc, err := m.factory() - if err != nil { - return nil, err - } - m.blob = acc - } - return m.blob, nil -} - -func (m *DefaultAccessMethodImpl) Digest() digest.Digest { - return m.digest -} - -func (m *DefaultAccessMethodImpl) IsLocal() bool { - return m.local -} - -func (m *DefaultAccessMethodImpl) GetKind() string { - return m.spec.GetKind() -} - -func (m *DefaultAccessMethodImpl) AccessSpec() AccessSpec { - return m.spec -} - -func (m *DefaultAccessMethodImpl) Get() ([]byte, error) { - a, err := m.getAccess() - if err != nil { - return nil, err - } - return a.Get() -} - -func (m *DefaultAccessMethodImpl) Reader() (io.ReadCloser, error) { - a, err := m.getAccess() - if err != nil { - return nil, err - } - return a.Reader() -} - -func (m *DefaultAccessMethodImpl) Close() error { - var err error - m.lock.Lock() - defer m.lock.Unlock() - - if m.blob != nil { - err = m.blob.Close() - m.blob = nil - } - return err -} - -func (m *DefaultAccessMethodImpl) MimeType() string { - return m.mime -} diff --git a/pkg/contexts/ocm/cpi/builder.go b/pkg/contexts/ocm/cpi/builder.go deleted file mode 100644 index ea045d498..000000000 --- a/pkg/contexts/ocm/cpi/builder.go +++ /dev/null @@ -1,50 +0,0 @@ -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 deleted file mode 100644 index efec373b5..000000000 --- a/pkg/contexts/ocm/cpi/interface.go +++ /dev/null @@ -1,232 +0,0 @@ -package cpi - -// This is the Context Provider Interface for credential providers - -import ( - _ "unsafe" - - "github.com/mandelsoft/goutils/sliceutils" - "github.com/mandelsoft/logging" - - "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/contexts/ocm/internal" - "github.com/open-component-model/ocm/pkg/registrations" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const CONTEXT_TYPE = internal.CONTEXT_TYPE - -const CommonTransportFormat = internal.CommonTransportFormat - -var TAG_BLOBHANDLER = logging.DefineTag("blobhandler", "execution of blob handler used to upload resource blobs to an ocm repository.") - -func BlobHandlerLogger(ctx Context, messageContext ...logging.MessageContext) logging.Logger { - if len(messageContext) > 0 { - messageContext = sliceutils.CopyAppend[logging.MessageContext](messageContext, TAG_BLOBHANDLER) - return ctx.Logger(messageContext...) - } else { - return ctx.Logger(TAG_BLOBHANDLER) - } -} - -type ( - Context = internal.Context - ContextProvider = internal.ContextProvider - LocalContextProvider = internal.LocalContextProvider - ComponentVersionResolver = internal.ComponentVersionResolver - Repository = internal.Repository - RepositoryTypeProvider = internal.RepositoryTypeProvider - RepositoryTypeScheme = internal.RepositoryTypeScheme - RepositoryDelegationRegistry = internal.RepositoryDelegationRegistry - RepositoryPriorityDecoder = internal.PriorityDecoder[Context, RepositorySpec] - RepositorySpecHandlers = internal.RepositorySpecHandlers - RepositorySpecHandler = internal.RepositorySpecHandler - UniformRepositorySpec = internal.UniformRepositorySpec - ComponentLister = internal.ComponentLister - ComponentAccess = internal.ComponentAccess - ComponentVersionAccess = internal.ComponentVersionAccess - AccessSpec = internal.AccessSpec - AccessSpecDecoder = internal.AccessSpecDecoder - GenericAccessSpec = internal.GenericAccessSpec - AccessMethod = internal.AccessMethod - AccessProvider = internal.AccessProvider - AccessTypeProvider = internal.AccessTypeProvider - AccessTypeScheme = internal.AccessTypeScheme - DataAccess = internal.DataAccess - BlobAccess = internal.BlobAccess - SourceAccess = internal.SourceAccess - SourceMeta = internal.SourceMeta - ResourceAccess = internal.ResourceAccess - ResourceMeta = internal.ResourceMeta - RepositorySpec = internal.RepositorySpec - RepositorySpecDecoder = internal.RepositorySpecDecoder - IntermediateRepositorySpecAspect = internal.IntermediateRepositorySpecAspect - GenericRepositorySpec = internal.GenericRepositorySpec - RepositoryType = internal.RepositoryType - ComponentReference = internal.ComponentReference -) - -type ArtifactAccess[M any] internal.ArtifactAccess[M] - -type ( - BlobHandler = internal.BlobHandler - BlobHandlerProvider = internal.BlobHandlerProvider - BlobHandlerOption = internal.BlobHandlerOption - BlobHandlerOptions = internal.BlobHandlerOptions - BlobHandlerKey = internal.BlobHandlerKey - BlobHandlerRegistry = internal.BlobHandlerRegistry - StorageContext = internal.StorageContext - ImplementationRepositoryType = internal.ImplementationRepositoryType - - BlobHandlerConfig = internal.BlobHandlerConfig - BlobHandlerRegistrationHandler = internal.BlobHandlerRegistrationHandler -) - -type ( - DigesterType = internal.DigesterType - BlobDigester = internal.BlobDigester - BlobDigesterRegistry = internal.BlobDigesterRegistry - DigestDescriptor = internal.DigestDescriptor - HasherProvider = internal.HasherProvider - Hasher = internal.Hasher -) - -type NamePath = registrations.NamePath - -func NewNamePath(p string) NamePath { - return registrations.NewNamePath(p) -} - -func FromProvider(p ContextProvider) Context { - return internal.FromProvider(p) -} - -func NewBlobHandlerOptions(olist ...BlobHandlerOption) *BlobHandlerOptions { - return internal.NewBlobHandlerOptions(olist...) -} - -func DefaultBlobHandlerProvider(ctx Context) BlobHandlerProvider { - return internal.DefaultBlobHandlerProvider(ctx) -} - -func NewResourceMeta(name string, typ string, relation metav1.ResourceRelation) *ResourceMeta { - return compdesc.NewResourceMeta(name, typ, relation) -} - -func NewDigestDescriptor(digest string, typ DigesterType) *DigestDescriptor { - return internal.NewDigestDescriptor(digest, typ.HashAlgorithm, typ.NormalizationAlgorithm) -} - -func DefaultBlobDigesterRegistry() BlobDigesterRegistry { - return internal.DefaultBlobDigesterRegistry -} - -func DefaultDelegationRegistry() RepositoryDelegationRegistry { - return internal.DefaultRepositoryDelegationRegistry -} - -func DefaultContext() internal.Context { - return internal.DefaultContext -} - -func WithPrio(p int) BlobHandlerOption { - return internal.WithPrio(p) -} - -func ForRepo(ctxtype, repostype string) BlobHandlerOption { - return internal.ForRepo(ctxtype, repostype) -} - -func ForMimeType(mimetype string) BlobHandlerOption { - return internal.ForMimeType(mimetype) -} - -func ForArtifactType(arttype string) BlobHandlerOption { - return internal.ForArtifactType(arttype) -} - -func RegisterRepositorySpecHandler(handler RepositorySpecHandler, types ...string) { - internal.RegisterRepositorySpecHandler(handler, types...) -} - -func RegisterBlobHandler(handler BlobHandler, opts ...BlobHandlerOption) { - internal.RegisterBlobHandler(handler, opts...) -} - -func RegisterBlobHandlerRegistrationHandler(path string, handler BlobHandlerRegistrationHandler) { - internal.RegisterBlobHandlerRegistrationHandler(path, handler) -} - -func MustRegisterDigester(digester BlobDigester, arttypes ...string) { - internal.MustRegisterDigester(digester, arttypes...) -} - -func SetDefaultDigester(d BlobDigester) { - internal.SetDefaultDigester(d) -} - -func ToGenericAccessSpec(spec AccessSpec) (*GenericAccessSpec, error) { - return internal.ToGenericAccessSpec(spec) -} - -func ToGenericRepositorySpec(spec RepositorySpec) (*GenericRepositorySpec, error) { - return internal.ToGenericRepositorySpec(spec) -} - -type AccessSpecRef = internal.AccessSpecRef - -func NewAccessSpecRef(spec AccessSpec) *AccessSpecRef { - return internal.NewAccessSpecRef(spec) -} - -func NewRawAccessSpecRef(data []byte, unmarshaler runtime.Unmarshaler) (*AccessSpecRef, error) { - return internal.NewRawAccessSpecRef(data, unmarshaler) -} - -const ( - KIND_REPOSITORY = internal.KIND_REPOSITORY - KIND_COMPONENTVERSION = internal.KIND_COMPONENTVERSION - KIND_RESOURCE = internal.KIND_RESOURCE - KIND_SOURCE = internal.KIND_SOURCE - KIND_REFERENCE = internal.KIND_REFERENCE -) - -func ErrComponentVersionNotFound(name, version string) error { - return internal.ErrComponentVersionNotFound(name, version) -} - -func ErrComponentVersionNotFoundWrap(err error, name, version string) error { - return internal.ErrComponentVersionNotFoundWrap(err, name, version) -} - -// PrefixProvider is supported by RepositorySpecs to -// provide info about a potential path prefix to -// use for globalized local artifacts. -type PrefixProvider interface { - PathPrefix() string -} - -func RepositoryPrefix(spec RepositorySpec) string { - if s, ok := spec.(PrefixProvider); ok { - return s.PathPrefix() - } - return "" -} - -// HintProvider is able to provide a name hint for globalization of local -// artifacts. -type HintProvider internal.HintProvider - -// GlobalAccessProvider is able to provide a non-local access specification. -type GlobalAccessProvider internal.GlobalAccessProvider - -// provide context interface for other files to avoid diffs in imports. -var ( - newStrictRepositoryTypeScheme = internal.NewStrictRepositoryTypeScheme - defaultRepositoryTypeScheme = internal.DefaultRepositoryTypeScheme -) - -func WrapContextProvider(ctx LocalContextProvider) ContextProvider { - return internal.WrapContextProvider(ctx) -} diff --git a/pkg/contexts/ocm/cpi/modopts.go b/pkg/contexts/ocm/cpi/modopts.go deleted file mode 100644 index 135f106d6..000000000 --- a/pkg/contexts/ocm/cpi/modopts.go +++ /dev/null @@ -1,114 +0,0 @@ -package cpi - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/hashattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" - "github.com/open-component-model/ocm/pkg/signing/hasher/sha256" -) - -type ( - TargetElement = internal.TargetElement - TargetOption = internal.TargetOption - TargetOptions = internal.TargetOptions - - ModificationOption = internal.ModificationOption - ModificationOptions = internal.ModificationOptions - - BlobModificationOption = internal.BlobModificationOption - BlobModificationOptions = internal.BlobModificationOptions - - BlobUploadOption = internal.BlobUploadOption - BlobUploadOptions = internal.BlobUploadOptions - - AddVersionOption = internal.AddVersionOption - AddVersionOptions = internal.AddVersionOptions -) - -//////////////////////////////////////////////////////////////////////////////// - -func NewAddVersionOptions(list ...AddVersionOption) *AddVersionOptions { - return internal.NewAddVersionOptions(list...) -} - -// Overwrite enabled the overwrite mode for adding a component version. -func Overwrite(flag ...bool) AddVersionOption { - return internal.Overwrite(flag...) -} - -//////////////////////////////////////////////////////////////////////////////// - -func NewBlobModificationOptions(list ...BlobModificationOption) *BlobModificationOptions { - return internal.NewBlobModificationOptions(list...) -} - -//////////////////////////////////////////////////////////////////////////////// - -func NewBlobUploadOptions(list ...BlobUploadOption) *BlobUploadOptions { - return internal.NewBlobUploadOptions(list...) -} - -func UseBlobHandlers(h BlobHandlerProvider) internal.BlobOptionImpl { - return internal.UseBlobHandlers(h) -} - -//////////////////////////////////////////////////////////////////////////////// - -func NewModificationOptions(list ...ModificationOption) *ModificationOptions { - return internal.NewModificationOptions(list...) -} - -func TargetIndex(idx int) internal.TargetIndex { - return internal.TargetIndex(-1) -} - -const AppendElement = internal.TargetIndex(-1) - -var UpdateElement = internal.UpdateElement - -func TargetIdentity(id v1.Identity) internal.TargetIdentity { - return internal.TargetIdentity(id) -} - -func ModifyResource(flag ...bool) internal.ModOptionImpl { - return internal.ModifyResource(flag...) -} - -func AcceptExistentDigests(flag ...bool) internal.ModOptionImpl { - return internal.AcceptExistentDigests(flag...) -} - -func WithDefaultHashAlgorithm(algo ...string) internal.ModOptionImpl { - return internal.WithDefaultHashAlgorithm(algo...) -} - -func WithHasherProvider(prov HasherProvider) internal.ModOptionImpl { - return internal.WithHasherProvider(prov) -} - -func SkipVerify(flag ...bool) internal.ModOptionImpl { - return internal.SkipVerify(flag...) -} - -// SkipDigest disables digest creation if enabled. -// -// Deprecated: for legacy code, only. -func SkipDigest(flag ...bool) internal.ModOptionImpl { - return internal.SkipDigest(flag...) -} - -/////////////////////////////////////////////////////// - -func CompleteModificationOptions(ctx ContextProvider, m *ModificationOptions) { - attr := hashattr.Get(ctx.OCMContext()) - if m.DefaultHashAlgorithm == "" { - m.DefaultHashAlgorithm = attr.DefaultHasher - } - if m.DefaultHashAlgorithm == "" { - m.DefaultHashAlgorithm = sha256.Algorithm - } - if m.HasherProvider == nil { - m.HasherProvider = signingattr.Get(ctx.OCMContext()) - } -} diff --git a/pkg/contexts/ocm/cpi/repocpi/README.md b/pkg/contexts/ocm/cpi/repocpi/README.md deleted file mode 100644 index c97d5c00e..000000000 --- a/pkg/contexts/ocm/cpi/repocpi/README.md +++ /dev/null @@ -1,77 +0,0 @@ -# Context Programming Interface for Repositories - -Package repocpi contains the implementation support - for repository backends. It offers three methods - to create component version, component and repository - objects based on three simple implementation interfaces. - - The basic provisioning model is layered: - - ![Implamentation Layers](ocmimpllayers.png) - - - on layer 1 there is the *user facing API* defined - in package [github.com/open-component-model/ocm/pkg/contexts/ocm]. - - - on layer 2 (this package) there is a backend agnostic - implementation of standard functionality based on layer 3. - This is divided into two parts - - a) the *view* objects provided by the `Dup()` calls of the layer 1 API. - All dups are internally based on a single base object. - These objects are called *bridge*. They act as base object - for the views and as abstraction for the implementation objects - providing *generic* implementations potentially based on - the implementation functionality. - (see bridge design pattern https://refactoring.guru/design-patterns/bridge) - - b) the *bridge* object as base for all dup views is used to implement some - common functionality like the view management. The bridge object - is closed, when the last view disappears. - This bridge object then calls the final - storage backend implementation interface. - - - the storage backend implementations based on the implementation - interfaces provided by layer 2. - - The implementation interfaces and the functions to create API objects are: - - - interface [ComponentVersionAccessImpl] is used to create an ocm.ComponentVersionAccess object - using the function [NewComponentVersionAccess]. - - interface [ComponentAccessImpl] is used to create an ocm.ComponentAccess object - using the function [NewComponentAccess]. - - interface [RepositoryImpl] is used to create an ocm.ComponentAccess object - using the function [NewRepository]. - - Component version implementations provide basic access to component versions - and their descriptors. They keep a reference to component implementations, which are - again based on repository implementations. The task of repository implementations is - to provide component objects. Their implementations are responsible to provide - component version objects. - -## Simplified Respository Implementation Interface - Besides this basic implementation interfaces with separated objects for a - repository, component and component version, there is support for a simplified - implementation interface (`StorageBackendImpl`). This is a single interface - bundling all required functionality to implement the objects for the three - concerned elements. With `NewStorageBackend` it is possible to instantiate - a new kind of repository based on this single interface. The required - objects for components and component versions are generically provided - based on the methods provided by this interface. - -## Comparison of Implementation Models - -The simplified implementation model does not provide access to the -implementation objects for components and component versions. -Therefore, it is not possible to keep state for those elements. - -Storage Backend Implementations requiring such state, like the OCI -implementation based on the OCI abstraction provided by the OCI -context, therefore use dedicated implementations for repository, -component and component version objects. This model provides -complete control over the lifecycle of those elements. - -If a storage backend implementation is stateless or just keeps -state at the repository level, the simplified implementation model -can be chosen. - - diff --git a/pkg/contexts/ocm/cpi/repocpi/doc.go b/pkg/contexts/ocm/cpi/repocpi/doc.go deleted file mode 100644 index cecff6a73..000000000 --- a/pkg/contexts/ocm/cpi/repocpi/doc.go +++ /dev/null @@ -1,55 +0,0 @@ -// Package repocpi contains the implementation support -// for repository backends. It offers three methods -// to create component version, component and repository -// objects based on three simple implementation interfaces. -// -// The basic provisioning model is layered: -// -// - on layer 1 there is the user facing API defined -// in package [github.com/open-component-model/ocm/pkg/contexts/ocm]. -// -// - on layer 2 (this package) there is a backend agnostic -// implementation of standard functionality based on layer 3. -// This is divided into two parts -// -// a) the view objects provided by the Dup() calls of the layer 1 API. -// All dups are internally based on a single base object. -// These objects are called bridge. They act as base object -// for the views and as abstraction for the implementation objects -// providing generic implementations potentially based on -// the implementation functionality. -// (see bridge design pattern https://refactoring.guru/design-patterns/bridge) -// -// b) the bridge object as base for all dup views is used to implement some -// common functionality like the view management. The bridge object -// is closed, when the last view disappears. -// This bridge object then calls the final -// storage backend implementation interface. -// -// - the storage backend implementations based on the implementation -// interfaces provided by layer 2. -// -// The implementation interfaces and the functions to create API objects are: -// -// - interface [ComponentVersionAccessImpl] is used to create an ocm.ComponentVersionAccess object -// using the function [NewComponentVersionAccess]. -// - interface [ComponentAccessImpl] is used to create an ocm.ComponentAccess object -// using the function [NewComponentAccess]. -// - interface [RepositoryImpl] is used to create an ocm.ComponentAccess object -// using the function [NewRepository]. -// -// Component version implementations provide basic access to component versions -// and their descriptors. They keep a reference to component implementations, which are -// again based on repository implementations. The task of repository implementations is -// to provide component objects. Their implementations are responsible to provide -// component version objects. -// -// Besides this basic implementation interface with separated object for a -// repository, component and component version, there is support for a simplified -// implementation interface (StorageBackendImpl). This is a single interface -// bundling all required functionality to implement the objects for the three -// concerned elements. With NewStorageBackend it is possible to instantiate -// a new kind of repository based on this single interface. The required -// objects for components and component versions are generically provided -// based on the methods provided by this interface. -package repocpi diff --git a/pkg/contexts/ocm/cpi/repocpi/interface.go b/pkg/contexts/ocm/cpi/repocpi/interface.go deleted file mode 100644 index 40ef3195d..000000000 --- a/pkg/contexts/ocm/cpi/repocpi/interface.go +++ /dev/null @@ -1,7 +0,0 @@ -package repocpi - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" -) - -type Repository = internal.Repository diff --git a/pkg/contexts/ocm/cpi/repotypes.go b/pkg/contexts/ocm/cpi/repotypes.go deleted file mode 100644 index 8ac6d6f83..000000000 --- a/pkg/contexts/ocm/cpi/repotypes.go +++ /dev/null @@ -1,59 +0,0 @@ -package cpi - -// this file is similar to contexts oci. - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - "github.com/open-component-model/ocm/pkg/runtime" -) - -type RepositoryTypeVersionScheme = runtime.TypeVersionScheme[RepositorySpec, RepositoryType] - -func NewRepositoryTypeVersionScheme(kind string) RepositoryTypeVersionScheme { - return runtime.NewTypeVersionScheme[RepositorySpec, RepositoryType](kind, newStrictRepositoryTypeScheme()) -} - -func RegisterRepositoryType(rtype RepositoryType) { - defaultRepositoryTypeScheme.Register(rtype) -} - -func RegisterRepositoryTypeVersions(s RepositoryTypeVersionScheme) { - defaultRepositoryTypeScheme.AddKnownTypes(s) -} - -//////////////////////////////////////////////////////////////////////////////// - -type repositoryType struct { - runtime.VersionedTypedObjectType[RepositorySpec] - checker RepositoryAccessMethodChecker -} - -type RepositoryAccessMethodChecker func(Context, compdesc.AccessSpec) bool - -func NewRepositoryType[I RepositorySpec](name string, checker RepositoryAccessMethodChecker) RepositoryType { - return &repositoryType{ - VersionedTypedObjectType: runtime.NewVersionedTypedObjectType[RepositorySpec, I](name), - checker: checker, - } -} - -func NewRepositoryTypeByConverter[I RepositorySpec, V runtime.VersionedTypedObject](name string, converter runtime.Converter[I, V], checker RepositoryAccessMethodChecker) RepositoryType { - return &repositoryType{ - VersionedTypedObjectType: runtime.NewVersionedTypedObjectTypeByConverter[RepositorySpec, I, V](name, converter), - checker: checker, - } -} - -func NewRepositoryTypeByFormatVersion(name string, fmt runtime.FormatVersion[RepositorySpec], checker RepositoryAccessMethodChecker) RepositoryType { - return &repositoryType{ - VersionedTypedObjectType: runtime.NewVersionedTypedObjectTypeByFormatVersion[RepositorySpec](name, fmt), - checker: checker, - } -} - -func (t *repositoryType) LocalSupportForAccessSpec(ctx Context, a compdesc.AccessSpec) bool { - if t.checker != nil { - return t.checker(ctx, a) - } - return false -} diff --git a/pkg/contexts/ocm/cpi/utils.go b/pkg/contexts/ocm/cpi/utils.go deleted file mode 100644 index 4ce7faef1..000000000 --- a/pkg/contexts/ocm/cpi/utils.go +++ /dev/null @@ -1,89 +0,0 @@ -package cpi - -import ( - "io" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" - "github.com/open-component-model/ocm/pkg/iotools" -) - -type AccessMethodSource interface { - AccessMethod() (AccessMethod, error) -} - -// ResourceReader gets a Reader for a given resource/source access. -// It provides a Reader handling the Close contract for the access method -// by connecting the access method's Close method to the Readers Close method . -// Deprecated: use GetResourceReader. -// It must be deprecated because of the support of free-floating ReSourceAccess -// implementations, they not necessarily provide an AccessMethod. -func ResourceReader(s AccessMethodSource) (io.ReadCloser, error) { - meth, err := s.AccessMethod() - if err != nil { - return nil, err - } - return toResourceReaderForMethod(meth) -} - -// ResourceMimeReader gets a Reader for a given resource/source access. -// It provides a Reader handling the Close contract for the access method -// by connecting the access method's Close method to the Readers Close method. -// Additionally, the mime type is returned. -// Deprecated: use GetResourceMimeReader. -// It must be deprecated because of the support of free-floating ReSourceAccess -// implementations, they not necessarily provide an AccessMethod. -func ResourceMimeReader(s AccessMethodSource) (io.ReadCloser, string, error) { - meth, err := s.AccessMethod() - if err != nil { - return nil, "", err - } - r, err := toResourceReaderForMethod(meth) - return r, meth.MimeType(), err -} - -func toResourceReaderForMethod(meth AccessMethod) (io.ReadCloser, error) { - r, err := meth.Reader() - if err != nil { - meth.Close() - return nil, err - } - return iotools.AddReaderCloser(r, meth, "access method"), nil -} - -// GetResourceMimeReader gets a Reader for a given resource/source access. -// It provides a Reader handling the Close contract for the access method. -func GetResourceReader(acc AccessProvider) (io.ReadCloser, error) { - return blobaccess.ReaderFromProvider(acc) -} - -// GetResourceMimeReader gets a Reader for a given resource/source access. -// It provides a Reader handling the Close contract for the access method. -// Additionally, the mime type is returned. -func GetResourceMimeReader(acc AccessProvider) (io.ReadCloser, string, error) { - return blobaccess.MimeReaderFromProvider(acc) -} - -//////////////////////////////////////////////////////////////////////////////// - -func ArtifactNameHint(spec AccessSpec, cv ComponentVersionAccess) string { - if h, ok := spec.(HintProvider); ok { - return h.GetReferenceHint(cv) - } - return "" -} - -func ReferenceHint(spec AccessSpec, cv ComponentVersionAccess) string { - if h, ok := spec.(internal.HintProvider); ok { - return h.GetReferenceHint(cv) - } - return "" -} - -func GlobalAccess(spec AccessSpec, ctx Context) AccessSpec { - g := spec.GlobalAccessSpec(ctx) - if g != nil && g.IsLocal(ctx) { - g = nil - } - return g -} diff --git a/pkg/contexts/ocm/digester/digesters/artifact/digester.go b/pkg/contexts/ocm/digester/digesters/artifact/digester.go deleted file mode 100644 index 0fcc3508a..000000000 --- a/pkg/contexts/ocm/digester/digesters/artifact/digester.go +++ /dev/null @@ -1,174 +0,0 @@ -package artifact - -import ( - "archive/tar" - "fmt" - "io" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/general" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common/compression" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "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/signing" - "github.com/open-component-model/ocm/pkg/signing/hasher/sha256" - "github.com/open-component-model/ocm/pkg/signing/hasher/sha512" -) - -const OciArtifactDigestV1 string = "ociArtifactDigest/v1" - -const LegacyOciArtifactDigestV1 string = "ociArtefactDigest/v1" - -func init() { - cpi.MustRegisterDigester(New(sha256.Algorithm), "") - cpi.MustRegisterDigester(New(sha512.Algorithm), "") - - // legacy digester types - cpi.MustRegisterDigester(New(digest.SHA256.String(), OciArtifactDigestV1), "") - cpi.MustRegisterDigester(New(digest.SHA512.String(), OciArtifactDigestV1), "") - - cpi.MustRegisterDigester(New(digest.SHA256.String(), LegacyOciArtifactDigestV1), "") - cpi.MustRegisterDigester(New(digest.SHA512.String(), LegacyOciArtifactDigestV1), "") -} - -func New(algo string, ts ...string) cpi.BlobDigester { - norm := general.OptionalDefaulted(OciArtifactDigestV1, ts...) - return &Digester{ - cpi.DigesterType{ - HashAlgorithm: algo, - NormalizationAlgorithm: norm, - }, - } -} - -type Digester struct { - typ cpi.DigesterType -} - -var _ cpi.BlobDigester = (*Digester)(nil) - -func (d *Digester) GetType() cpi.DigesterType { - return d.typ -} - -func (d *Digester) DetermineDigest(reftyp string, method cpi.AccessMethod, preferred signing.Hasher) (*cpi.DigestDescriptor, error) { - if method.IsLocal() { - mime := method.MimeType() - if !artdesc.IsOCIMediaType(mime) { - return nil, nil - } - r, err := method.Reader() - if err != nil { - return nil, err - } - defer r.Close() - - var reader io.ReadCloser - reader, _, err = compression.AutoDecompress(r) - if err != nil { - return nil, err - } - defer reader.Close() - tr := tar.NewReader(reader) - - var desc *cpi.DigestDescriptor - oci := false - layout := false - for { - header, err := tr.Next() - if err != nil { - if errors.Is(err, io.EOF) { - if oci { - if layout { - return desc, nil - } else { - err = fmt.Errorf("oci-layout not found") - } - } else { - err = fmt.Errorf("descriptor not found in archive") - } - } - return nil, errors.ErrInvalidWrap(err, "artifact archive") - } - - switch header.Typeflag { - case tar.TypeDir: - case tar.TypeReg: - switch header.Name { - case artifactset.OCILayouFileName: - layout = true - case artifactset.OCIArtifactSetDescriptorFileName: - oci = true - fallthrough - case artifactset.ArtifactSetDescriptorFileName: - data, err := io.ReadAll(tr) - if err != nil { - return nil, fmt.Errorf("unable to read descriptor from archive: %w", err) - } - index, err := artdesc.DecodeIndex(data) - if err != nil { - return nil, err - } - if index == nil { - return nil, fmt.Errorf("no main artifact found") - } - main := artifactset.RetrieveMainArtifactFromIndex(index) - if main == "" { - return nil, fmt.Errorf("no main artifact found") - } - dig := artifactset.RetrieveDigest(index, main) - if dig == "" { - return nil, fmt.Errorf("no main artifact digest found for %s", main) - } - if d.GetType().HashAlgorithm != signing.NormalizeHashAlgorithm(string(dig.Algorithm())) { - return nil, nil - } - desc = cpi.NewDigestDescriptor(dig.Hex(), d.GetType()) - if !oci { - return desc, nil - } - } - } - } - // not reached (endless for) - } - if ociartifact.Is(method.AccessSpec()) { - var ( - dig digest.Digest - err error - ) - - impl := accspeccpi.GetAccessMethodImplementation(method) - // first, ask access specification (inexpensive) - if s, ok := method.AccessSpec().(blobaccess.DigestSource); ok { - dig = s.Digest() - } - if dig == "" { - // second: check for error providing interface - if s, ok := impl.(accspeccpi.DigestSource); ok { - dig, err = s.GetDigest() - } - } - if dig == "" && err == nil { - // third: fallback to standard digest interface - if s, ok := impl.(blobaccess.DigestSource); ok { - dig = s.Digest() - } - } - - if dig != "" { - if d.GetType().HashAlgorithm != signing.NormalizeHashAlgorithm(dig.Algorithm().String()) { - return nil, nil - } - return cpi.NewDigestDescriptor(dig.Hex(), d.GetType()), nil - } - return nil, errors.NewEf(err, "cannot determine digest") - } - return nil, nil -} diff --git a/pkg/contexts/ocm/digester/digesters/blob/digester.go b/pkg/contexts/ocm/digester/digesters/blob/digester.go deleted file mode 100644 index ec9f64469..000000000 --- a/pkg/contexts/ocm/digester/digesters/blob/digester.go +++ /dev/null @@ -1,45 +0,0 @@ -package blob - -import ( - "fmt" - "io" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/signing" -) - -const GenericBlobDigestV1 = "genericBlobDigest/v1" - -func init() { - cpi.MustRegisterDigester(&defaultDigester{}) - cpi.SetDefaultDigester(&defaultDigester{}) -} - -type defaultDigester struct{} - -var _ cpi.BlobDigester = (*defaultDigester)(nil) - -func (d defaultDigester) GetType() cpi.DigesterType { - return cpi.DigesterType{ - HashAlgorithm: "", - NormalizationAlgorithm: GenericBlobDigestV1, - } -} - -func (d defaultDigester) DetermineDigest(typ string, acc cpi.AccessMethod, preferred signing.Hasher) (*cpi.DigestDescriptor, error) { - r, err := acc.Reader() - if err != nil { - return nil, err - } - hash := preferred.Create() - - if _, err := io.Copy(hash, r); err != nil { - return nil, err - } - - return &cpi.DigestDescriptor{ - Value: fmt.Sprintf("%x", hash.Sum(nil)), - HashAlgorithm: preferred.Algorithm(), - NormalisationAlgorithm: GenericBlobDigestV1, - }, nil -} diff --git a/pkg/contexts/ocm/digester/digesters/init.go b/pkg/contexts/ocm/digester/digesters/init.go deleted file mode 100644 index bdd5ed2f4..000000000 --- a/pkg/contexts/ocm/digester/digesters/init.go +++ /dev/null @@ -1,6 +0,0 @@ -package digesters - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/digester/digesters/artifact" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/digester/digesters/blob" -) diff --git a/pkg/contexts/ocm/download/config/type.go b/pkg/contexts/ocm/download/config/type.go deleted file mode 100644 index c13f390f3..000000000 --- a/pkg/contexts/ocm/download/config/type.go +++ /dev/null @@ -1,91 +0,0 @@ -package config - -import ( - "fmt" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/config" - cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - ConfigType = "downloader.ocm" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX - ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" -) - -func init() { - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1, usage)) -} - -// Config describes a memory based config interface. -type Config struct { - runtime.ObjectVersionedType `json:",inline"` - Registrations []Registration `json:"registrations,omitempty"` -} - -type Registration struct { - Name string `json:"name"` - Description string `json:"description,omitempty"` - download.HandlerOptions `json:",inline"` - Config download.HandlerConfig `json:"config,omitempty"` -} - -// New creates a new memory ConfigSpec. -func New() *Config { - return &Config{ - ObjectVersionedType: runtime.NewVersionedObjectType(ConfigType), - } -} - -func (a *Config) GetType() string { - return ConfigType -} - -func (a *Config) AddRegistration(hdlrs ...Registration) error { - for i, h := range hdlrs { - if h.Name == "" { - return fmt.Errorf("handler registration %d requires a name", i) - } - } - a.Registrations = append(a.Registrations, hdlrs...) - return nil -} - -func (a *Config) ApplyTo(ctx cfgcpi.Context, target interface{}) error { - t, ok := target.(cpi.Context) - if !ok { - return config.ErrNoContext(ConfigType) - } - reg := download.For(t) - for _, h := range a.Registrations { - accepted, err := reg.RegisterByName(h.Name, t, h.Config, &h.HandlerOptions) - if err != nil { - return errors.Wrapf(err, "registering download handler %q[%s]", h.Name, h.Description) - } - if !accepted { - download.Logger(t).Info("no matching handler for configuration %q[%s]", h.Name, h.Description) - } - } - return nil -} - -const usage = ` -The config type ` + ConfigType + ` can be used to define a list -of pre-configured download handler registrations (see ocm ocm-downloadhandlers): - -
-    type: ` + ConfigType + `
-    descrition: "my standard download handler configuration"
-    handlers:
-      - name: oci/artifact
-        artifactType: ociImage
-        mimeType:
-        config: ...
-      ...
-
-` diff --git a/pkg/contexts/ocm/download/download.go b/pkg/contexts/ocm/download/download.go deleted file mode 100644 index e536bb646..000000000 --- a/pkg/contexts/ocm/download/download.go +++ /dev/null @@ -1,69 +0,0 @@ -package download - -import ( - "github.com/mandelsoft/goutils/optionutils" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/utils" -) - -type Option = optionutils.Option[*Options] - -type Options struct { - Printer common.Printer - FileSystem vfs.FileSystem -} - -func (o *Options) ApplyTo(opts *Options) { - if o.Printer != nil { - opts.Printer = o.Printer - } - if o.FileSystem != nil { - opts.FileSystem = o.FileSystem - } -} - -//////////////////////////////////////////////////////////////////////////////// - -type filesystem struct { - fs vfs.FileSystem -} - -func (o *filesystem) ApplyTo(opts *Options) { - if o.fs != nil { - opts.FileSystem = o.fs - } -} - -func WithFileSystem(fs vfs.FileSystem) Option { - return &filesystem{fs} -} - -//////////////////////////////////////////////////////////////////////////////// - -type printer struct { - pr common.Printer -} - -func (o *printer) ApplyTo(opts *Options) { - if o.pr != nil { - opts.Printer = o.pr - } -} - -func WithPrinter(pr common.Printer) Option { - return &printer{pr} -} - -//////////////////////////////////////////////////////////////////////////////// - -func DownloadResource(ctx cpi.ContextProvider, r cpi.ResourceAccess, path string, opts ...Option) (string, error) { - eff := optionutils.EvalOptions(opts...) - - fs := utils.FileSystem(eff.FileSystem) - pr := utils.OptionalDefaulted(common.NewPrinter(nil), eff.Printer) - _, tgt, err := For(ctx).Download(pr, r, path, fs) - return tgt, err -} diff --git a/pkg/contexts/ocm/download/handlers/blob/handler.go b/pkg/contexts/ocm/download/handlers/blob/handler.go deleted file mode 100644 index f18080fd9..000000000 --- a/pkg/contexts/ocm/download/handlers/blob/handler.go +++ /dev/null @@ -1,47 +0,0 @@ -package blob - -import ( - "io" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" -) - -type Handler struct{} - -func init() { - download.Register(&Handler{}, download.ForArtifactType(download.ALL)) -} - -func wrapErr(err error, racc cpi.ResourceAccess) error { - if err == nil { - return nil - } - m := racc.Meta() - return errors.Wrapf(err, "resource %s/%s%s", m.GetName(), m.GetVersion(), m.ExtraIdentity.String()) -} - -func (_ Handler) Download(p common.Printer, racc cpi.ResourceAccess, path string, fs vfs.FileSystem) (bool, string, error) { - rd, err := cpi.GetResourceReader(racc) - if err != nil { - return true, "", wrapErr(err, racc) - } - defer rd.Close() - if path == "" { - path = racc.Meta().GetName() - } - file, err := fs.OpenFile(path, vfs.O_TRUNC|vfs.O_CREATE|vfs.O_WRONLY, 0o660) - if err != nil { - return true, "", wrapErr(errors.Wrapf(err, "creating target file %q", path), racc) - } - defer file.Close() - n, err := io.Copy(file, rd) - if err == nil { - p.Printf("%s: %d byte(s) written\n", path, n) - } - return true, path, wrapErr(err, racc) -} diff --git a/pkg/contexts/ocm/download/handlers/blueprint/handler.go b/pkg/contexts/ocm/download/handlers/blueprint/handler.go deleted file mode 100644 index 6dc13f787..000000000 --- a/pkg/contexts/ocm/download/handlers/blueprint/handler.go +++ /dev/null @@ -1,85 +0,0 @@ -package blueprint - -import ( - "github.com/mandelsoft/goutils/finalizer" - "github.com/mandelsoft/goutils/set" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - registry "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/utils" -) - -const ( - TYPE = resourcetypes.BLUEPRINT - LEGACY_TYPE = resourcetypes.BLUEPRINT_LEGACY - CONFIG_MIME_TYPE = "application/vnd.gardener.landscaper.blueprint.config.v1" -) - -type Extractor func(pr common.Printer, handler *Handler, access blobaccess.DataAccess, path string, fs vfs.FileSystem) (bool, error) - -var ( - supportedArtifactTypes []string - mimeTypeExtractorRegistry map[string]Extractor -) - -type Handler struct { - ociConfigMimeTypes set.Set[string] -} - -func init() { - supportedArtifactTypes = []string{TYPE, LEGACY_TYPE} - mimeTypeExtractorRegistry = map[string]Extractor{ - mime.MIME_TAR: ExtractArchive, - mime.MIME_TGZ: ExtractArchive, - mime.MIME_TGZ_ALT: ExtractArchive, - BLUEPRINT_MIMETYPE: ExtractArchive, - BLUEPRINT_MIMETYPE_COMPRESSED: ExtractArchive, - BLUEPRINT_MIMETYPE_LEGACY: ExtractArchive, - BLUEPRINT_MIMETYPE_LEGACY_COMPRESSED: ExtractArchive, - } - for _, t := range append(artdesc.ToArchiveMediaTypes(artdesc.MediaTypeImageManifest), artdesc.ToArchiveMediaTypes(artdesc.MediaTypeDockerSchema2Manifest)...) { - mimeTypeExtractorRegistry[t] = ExtractArtifact - } - - h := New() - - registry.Register(h, registry.ForArtifactType(TYPE)) - registry.Register(h, registry.ForArtifactType(LEGACY_TYPE)) -} - -func New(configmimetypes ...string) *Handler { - if len(configmimetypes) == 0 || utils.Optional(configmimetypes...) == "" { - configmimetypes = []string{CONFIG_MIME_TYPE} - } - return &Handler{ - ociConfigMimeTypes: set.New[string](configmimetypes...), - } -} - -func (h *Handler) Download(pr common.Printer, racc cpi.ResourceAccess, path string, fs vfs.FileSystem) (_ bool, _ string, err error) { - var finalize finalizer.Finalizer - defer finalize.FinalizeWithErrorPropagationf(&err, "downloading blueprint") - - meth, err := racc.AccessMethod() - if err != nil { - return false, "", err - } - finalize.Close(meth) - - ex := mimeTypeExtractorRegistry[meth.MimeType()] - if ex == nil { - return false, "", nil - } - - ok, err := ex(pr, h, meth, path, fs) - if err != nil || !ok { - return ok, "", err - } - return true, path, nil -} diff --git a/pkg/contexts/ocm/download/handlers/blueprint/registration.go b/pkg/contexts/ocm/download/handlers/blueprint/registration.go deleted file mode 100644 index 903f029ea..000000000 --- a/pkg/contexts/ocm/download/handlers/blueprint/registration.go +++ /dev/null @@ -1,95 +0,0 @@ -package blueprint - -import ( - "fmt" - "strings" - - "github.com/mandelsoft/goutils/errors" - "golang.org/x/exp/slices" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "github.com/open-component-model/ocm/pkg/listformat" - "github.com/open-component-model/ocm/pkg/registrations" - "github.com/open-component-model/ocm/pkg/utils" -) - -const PATH = "landscaper/blueprint" - -func init() { - download.RegisterHandlerRegistrationHandler(PATH, &RegistrationHandler{}) -} - -type Config struct { - OCIConfigTypes []string `json:"ociConfigTypes"` -} - -func AttributeDescription() map[string]string { - return map[string]string{ - "ociConfigTypes": "a list of accepted OCI config archive mime types\n" + - "defaulted by " + CONFIG_MIME_TYPE + ".", - } -} - -type RegistrationHandler struct{} - -var _ download.HandlerRegistrationHandler = (*RegistrationHandler)(nil) - -func (r *RegistrationHandler) RegisterByName(handler string, ctx download.Target, config download.HandlerConfig, olist ...download.HandlerOption) (bool, error) { - var err error - - if handler != "" { - return true, fmt.Errorf("invalid blueprint handler %q", handler) - } - - opts := download.NewHandlerOptions(olist...) - if opts.MimeType != "" && !slices.Contains(supportedArtifactTypes, opts.ArtifactType) { - return false, errors.Newf("artifact type %s not supported", opts.ArtifactType) - } - - if opts.MimeType != "" { - if _, ok := mimeTypeExtractorRegistry[opts.MimeType]; !ok { - return false, errors.Newf("mime type %s not supported", opts.MimeType) - } - } - - attr, err := registrations.DecodeDefaultedConfig[Config](config) - if err != nil { - return true, errors.Wrapf(err, "cannot unmarshal download handler configuration") - } - - h := New(attr.OCIConfigTypes...) - if opts.MimeType == "" { - for m := range mimeTypeExtractorRegistry { - opts.MimeType = m - download.For(ctx).Register(h, opts) - } - } else { - download.For(ctx).Register(h, opts) - } - - return true, nil -} - -func (r *RegistrationHandler) GetHandlers(ctx cpi.Context) registrations.HandlerInfos { - return registrations.NewLeafHandlerInfo("uploading an OCI artifact to an OCI registry", ` -The artifact downloader is able to transfer OCI artifact-like resources -into an OCI registry given by the combination of the download target and the -registration config. - -If no config is given, the target must be an OCI reference with a potentially -omitted repository. The repo part is derived from the reference hint provided -by the resource's access specification. - -If the config is given, the target is used as repository name prefixed with an -optional repository prefix given by the configuration. - -The following artifact media types are supported: -`+listformat.FormatList("", utils.StringMapKeys(mimeTypeExtractorRegistry)...)+` -It accepts a config with the following fields: -`+listformat.FormatMapElements("", AttributeDescription())+` - -This handler is by default registered for the following artifact types: -`+strings.Join(supportedArtifactTypes, ","), - ) -} diff --git a/pkg/contexts/ocm/download/handlers/blueprint/registration_test.go b/pkg/contexts/ocm/download/handlers/blueprint/registration_test.go deleted file mode 100644 index 81288398f..000000000 --- a/pkg/contexts/ocm/download/handlers/blueprint/registration_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package blueprint_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/env/builder" - - "github.com/mandelsoft/vfs/pkg/projectionfs" - - "github.com/open-component-model/ocm/pkg/common" - "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/oci/testhelper" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download/handlers/blueprint" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - tenv "github.com/open-component-model/ocm/pkg/env" - "github.com/open-component-model/ocm/pkg/utils/tarutils" -) - -var _ = Describe("blueprint downloader registration", func() { - var env *Builder - - BeforeEach(func() { - env = NewBuilder(tenv.TestData()) - - MustBeSuccessful(tarutils.CreateTarFromFs(Must(projectionfs.New(env, TESTDATA_PATH)), ARCHIVE_PATH, tarutils.Gzip, env)) - - env.OCICommonTransport(OCI, accessio.FormatDirectory, func() { - env.Namespace(OCINAMESPACE, func() { - env.Manifest(OCIVERSION, func() { - env.Config(func() { - env.BlobStringData(MIMETYPE, "{}") - }) - env.Layer(func() { - env.BlobFromFile(MIMETYPE, ARCHIVE_PATH) - }) - }) - }) - }) - - testhelper.FakeOCIRepo(env, OCI, OCIHOST) - env.OCMCommonTransport(CTF, accessio.FormatDirectory, func() { - env.ComponentVersion(COMPONENT, VERSION, func() { - env.Resource(OCI_ARTIFACT_NAME, ARTIFACT_VERSION, ARTIFACT_TYPE, v1.ExternalRelation, func() { - env.Access(ociartifact.New(OCIHOST + ".alias/" + OCINAMESPACE + ":" + OCIVERSION)) - }) - }) - }) - }) - - AfterEach(func() { - env.Cleanup() - }) - - It("register and use blueprint downloader for artifact type \"testartifacttype\"", func() { - // As the handler is not registered for the artifact type "testartifacttype" per default (thus, in the - // init-function of handler.go), this test fails if the registration does not work. - Expect(download.For(env).RegisterByName(blueprint.PATH, env.OCMContext(), &blueprint.Config{[]string{MIMETYPE}}, download.ForArtifactType(ARTIFACT_TYPE))).To(BeTrue()) - - repo := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, CTF, 0, env)) - defer Close(repo) - cv := Must(repo.LookupComponentVersion(COMPONENT, VERSION)) - defer Close(cv) - racc := Must(cv.GetResourceByIndex(0)) - - p, buf := common.NewBufferedPrinter() - ok, path := Must2(download.For(env).Download(p, racc, DOWNLOAD_PATH, env)) - Expect(ok).To(BeTrue()) - Expect(path).To(Equal(DOWNLOAD_PATH)) - Expect(env.FileExists(DOWNLOAD_PATH + "/blueprint.yaml")).To(BeTrue()) - Expect(env.FileExists(DOWNLOAD_PATH + "/test/README.md")).To(BeTrue()) - Expect(buf.String()).To(StringEqualTrimmedWithContext(DOWNLOAD_PATH + ": 2 file(s) with 390 byte(s) written")) - }) -}) diff --git a/pkg/contexts/ocm/download/handlers/dirtree/README.md b/pkg/contexts/ocm/download/handlers/dirtree/README.md deleted file mode 100644 index 7a608034a..000000000 --- a/pkg/contexts/ocm/download/handlers/dirtree/README.md +++ /dev/null @@ -1,82 +0,0 @@ -# Directory Tree Downloader - -The standard configuration now provides a downloader for resources of type `directoryTree`and the legacy type `filesystem`. - -It acts on the mimetypes for an artifact set (`application/vnd.oci.image.manifest.v1+tar+gzip`) and a tar/tgz archive (`application/x-tar`, `application/x-tar+gzip`, `application/x-tgz`). The default configuration extracts the content to a -filesystem folder. If the blob format is an artifact set, for example provided by the access method `ociArtifact`, -the default configuration accepts the image config mimetype (`application/vnd.oci.image.config.v1+json`). -In this case the final filesystem content provided by the image is downloaded by evaluating the layered image file system. - -The default behaviour can just be used with -
-import "github.com/open-component-model/ocm/pkg/contexts/ocm/download"
-download.For(octx).Download(printer,resourceAccess,targetdir,vfs)
-
- -If the resource access describes an appropriate artifact, the new handler is automatically selected. - -As usual, the target is always a virtual filesystem. - -Like all download handlers. the dirtree download handler can also explicitly be used with - -
-import "github.com/open-component-model/ocm/pkg/contexts/ocm/download/handlers/dirtree"
-dirtree.New().Download(...)
-
- -In this mode, the behaviour can be influenced by specifying any list of accepted OCI artifact config mime types. - -With `New(mimetypes ...string).SetArchiveMode(true)` it is possible to enable the archive target mode. The content is downloaded to an archive instead of extracted filesystem content. - -The handler checks the mimetypes, only, but the default registration is done exclusively for the directory content resource types. -A context can be extended for other resource types with - -``` -download.For(octx),Register(dirtree.New...(...), download.ForCombi(type, [mimetype])) -``` - -The handler also provides additional methods, which can be used to execute more specific tasks, for example -methods for an optimized content access providing an internal virtual filesystem or an archive byte stream trying to avoid unnecessary conversions depending on the actual input format, - -The localization package has been adapted, accordingly. The `localize.Instantiate` method now prefers to use the -download handlers to get to the filesystem content to be configured, instead of expecting an archive resource. -Therefore, any (potentially own) resource type with any format can be used, as long as there is an appropriate downloader configured for the used OCM context. An optional additional parameter can be used to restrict the accepted resource types -to an explicitly given set of types. - -## Use cases - -If you use `dirtree.New(...).Download(...)` you explicitly use the `dirtree` downloader and nothing else. -It only checks the mime types, but not the resource types. So, you can enforce to use it on resources, -regardless of their type to download dirtree-like resources (with a matching mime type). - -If you use `download.For(...).Download(...)` it tries to find a registered downloader with registration -criteria matching the actual resource. This can be used without bothering with the kind of actually used -resource (to just download it, whatever it is in a standard manner). If a matching downloader is found, -it is used, otherwise just the blob is downloaded as provided by the access method. Here, for sure the -`dirtree` downloader is used for the standard scenarios (it is registered for). This is especially the -`directoryTree` resource type with the tar-like mimetypes and the oci artifact archive mime type. But it -is not used for other scenarios. - -So, if you want to use an own resource type (directly expressing the dedicated new meaning of a general -filesystem content), for example `gitOpsTemplate`, which is more expressive than just `directoryTree`. You -can -- either register the `dirtree` handler in advance for your OCM context and for this resource type at the -- registry (then it would automatically be chosen for all downloads using this context) -- or you know what you are doing, and explicitly call the `dirtree` downloader on such a resource. - -If, for example `gitOpsTemplate` should be a standard resource type, we should add such a registration as -part of the standard. - -Another possible scenario, where you might want to use the explicit `dirtree` usage is to overwrite the -standard behaviour for a special use case. For example, an OCI image is typically downloaded as OCI -artifact with the distribution spec format. But. if you want to access the effective filesystem, you -could explicitly use the `dirtree` downloader for an OCI image, which handles this for you. It would -make less sense to use it on a helm chart OCI artifact, because here the layers have a different meaning -than building a directory tree. - -## Registration Handler - -It provides a registration handler with the path `ocm/dirtree`. and a config -object with the fields: -- *`asArchive`* *bool*: download as archive (default is directory tree). -- *`ociConfigtypes`* *[]string*: list of accepted OCI manifest config media types. Default is the OCI image config media type. \ No newline at end of file diff --git a/pkg/contexts/ocm/download/handlers/dirtree/handler.go b/pkg/contexts/ocm/download/handlers/dirtree/handler.go deleted file mode 100644 index 616c3fe57..000000000 --- a/pkg/contexts/ocm/download/handlers/dirtree/handler.go +++ /dev/null @@ -1,364 +0,0 @@ -package dirtree - -import ( - "fmt" - "io" - "os" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/finalizer" - "github.com/mandelsoft/goutils/general" - "github.com/mandelsoft/goutils/set" - "github.com/mandelsoft/vfs/pkg/layerfs" - "github.com/mandelsoft/vfs/pkg/osfs" - "github.com/mandelsoft/vfs/pkg/projectionfs" - "github.com/mandelsoft/vfs/pkg/vfs" - "golang.org/x/exp/slices" - - "github.com/open-component-model/ocm/pkg/common" - "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/common/compression" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "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/mime" - "github.com/open-component-model/ocm/pkg/utils/tarutils" -) - -var ( - MimeOCIImageArtifactArchive = artifactset.MediaType(artdesc.MediaTypeImageManifest) - MimeOCIImageArtifact = artdesc.ToContentMediaType(artdesc.MediaTypeImageManifest) -) - -var ( - supportedMimeTypes = []string{MimeOCIImageArtifactArchive, mime.MIME_TGZ, mime.MIME_TGZ_ALT, mime.MIME_TAR} - defaultArtifactTypes = []string{resourcetypes.DIRECTORY_TREE, resourcetypes.FILESYSTEM_LEGACY} -) - -func SupportedMimeTypes() []string { - return slices.Clone(supportedMimeTypes) -} - -type Handler struct { - ociConfigtypes set.Set[string] - archive bool -} - -func New(mimetypes ...string) *Handler { - if len(mimetypes) == 0 || general.Optional(mimetypes...) == "" { - mimetypes = []string{artdesc.MediaTypeImageConfig} - } - return &Handler{ - ociConfigtypes: set.New[string](mimetypes...), - } -} - -var DefaultHandler = New() - -func init() { - for _, t := range defaultArtifactTypes { - for _, m := range supportedMimeTypes { - download.Register(DefaultHandler, download.ForCombi(t, m)) - } - } -} - -func (h *Handler) SetArchiveMode(b bool) *Handler { - h.archive = b - return h -} - -func (h *Handler) Download(p common.Printer, racc cpi.ResourceAccess, path string, fs vfs.FileSystem) (bool, string, error) { - lfs, r, err := h.GetForResource(racc) - if err != nil || (lfs == nil && r == nil) { - return err != nil, "", err - } - if path == "" { - path = racc.Meta().GetName() - } - return h.download(p, fs, path, lfs, r) -} - -func (h *Handler) DownloadFromArtifactSet(pr common.Printer, set *artifactset.ArtifactSet, path string, fs vfs.FileSystem) (bool, string, error) { - lfs, r, err := h.GetForArtifactSet(set) - if err != nil || (lfs == nil && r != nil) { - return err != nil, "", err - } - if path == "" { - path = set.GetMain().String() - } - return h.download(common.NewPrinter(nil), fs, path, lfs, r) -} - -func (h *Handler) download(pr common.Printer, fs vfs.FileSystem, path string, lfs vfs.FileSystem, r io.ReadCloser) (ok bool, dest string, err error) { - var finalize finalizer.Finalizer - defer finalize.FinalizeWithErrorPropagation(&err) - - if r != nil { - finalize.Close(r) - } - if lfs != nil { - finalize.With(func() error { return vfs.Cleanup(lfs) }) - } - if h.archive { - w, err := fs.OpenFile(path, vfs.O_TRUNC|vfs.O_CREATE|vfs.O_WRONLY, 0o600) - if err != nil { - return true, "", errors.Wrapf(err, "cannot write target archive %s", path) - } - finalize.Close(w) - if r != nil { - n, err := io.Copy(w, r) - if err != nil { - return true, "", errors.Wrapf(err, "cannot copy to archive %s", path) - } - pr.Printf("%s: %d byte(s) written\n", path, n) - return true, path, nil - } else { - cw := iotools.NewCountingWriter(w) - err := tarutils.PackFsIntoTar(lfs, "", cw, tarutils.TarFileSystemOptions{}) - if err == nil { - pr.Printf("%s: %d byte(s) written\n", path, cw.Size()) - } - return true, path, err - } - } else { - err := fs.MkdirAll(path, 0o700) - if err != nil { - return true, "", errors.Wrapf(err, "cannot create target directory") - } - - var fcnt, size int64 - if r != nil { - var p vfs.FileSystem - p, err = projectionfs.New(fs, path) - if err != nil { - return true, "", err - } - fcnt, size, err = tarutils.ExtractTarToFsWithInfo(p, r) - } else { - fcnt, size, err = CopyDir(lfs, "/", fs, path) - } - if err == nil { - pr.Printf("%s: %d file(s) with %d byte(s) written\n", path, fcnt, size) - } - return true, path, err - } -} - -// GetForResource provides a virtual filesystem for an OCi image manifest -// provided by the given resource matching the configured config types. -// It returns nil without error, if the OCI artifact does not match the requirement. -func (h *Handler) GetForResource(racc cpi.ResourceAccess) (fs vfs.FileSystem, reader io.ReadCloser, err error) { - var finalize finalizer.Finalizer - defer finalize.FinalizeWithErrorPropagation(&err) - - meth, err := racc.AccessMethod() - if err != nil { - return nil, nil, err - } - finalize.Close(meth) - - media := mime.BaseType(meth.MimeType()) - - switch media { - case mime.MIME_TGZ, mime.MIME_TAR: - case MimeOCIImageArtifact: - default: - if !h.ociConfigtypes.Contains(media) && !h.ociConfigtypes.Contains(meth.MimeType()) { - return nil, nil, nil - } - } - - r, err := meth.Reader() - if err != nil { - return nil, nil, err - } - if media != MimeOCIImageArtifact { - r, _, err = compression.AutoDecompress(r) - if err != nil { - return nil, nil, errors.Wrapf(err, "cannot determine compression for filesystem blob") - } - return nil, finalize.BindToReader(r), nil - } - finalize.Close(r) - set, err := artifactset.Open(accessobj.ACC_READONLY, "", 0, accessio.Reader(r)) - if err != nil { - return nil, nil, err - } - finalize.Close(set) - return h.getForArtifactSet(&finalize, set) -} - -// GetForArtifactSet provides a virtual filesystem for an OCi image manifest -// provided by the given artifact set matching the configured config types. -// It returns nil without error, if the OCI artifact does not match the requirement. -func (h *Handler) GetForArtifactSet(set *artifactset.ArtifactSet) (fs vfs.FileSystem, reader io.ReadCloser, err error) { - var finalize finalizer.Finalizer - defer finalize.FinalizeWithErrorPropagation(&err) - - return h.getForArtifactSet(&finalize, set) -} - -func (h *Handler) getForArtifactSet(finalize *finalizer.Finalizer, set *artifactset.ArtifactSet) (fs vfs.FileSystem, reader io.ReadCloser, err error) { - m, err := set.GetArtifact(set.GetMain().String()) - if err != nil { - return nil, nil, err - } - finalize.Close(m) - - return h.getForArtifact(finalize, m) -} - -// GetForArtifact provides a virtual filesystem for an OCi image manifest. -// It returns nil without error, if the OCI artifact does not match the requirement. -func (h *Handler) GetForArtifact(art oci.ArtifactAccess) (fs vfs.FileSystem, reader io.ReadCloser, err error) { - var finalize finalizer.Finalizer - defer finalize.FinalizeWithErrorPropagation(&err) - - return h.getForArtifact(&finalize, art) -} - -func (h *Handler) getForArtifact(finalize *finalizer.Finalizer, m oci.ArtifactAccess) (fs vfs.FileSystem, reader io.ReadCloser, err error) { - if !m.IsManifest() { - return nil, nil, fmt.Errorf("oci artifact is no image manifest") - } - macc := m.ManifestAccess() - if !h.ociConfigtypes.Contains(macc.GetDescriptor().Config.MediaType) { - return nil, nil, nil - } - - var cfs vfs.FileSystem - finalize.With(func() error { - return vfs.Cleanup(cfs) - }) - - // setup layered filesystem from manifest layers - for i, l := range macc.GetDescriptor().Layers { - nested := finalize.Nested() - - blob, err := macc.GetBlob(l.Digest) - if err != nil { - return nil, nil, errors.Wrapf(err, "cannot get blob for layer %d", i) - } - nested.Close(blob) - r, err := blob.Reader() - if err != nil { - return nil, nil, errors.Wrapf(err, "cannot get reader for layer blob %d", i) - } - nested.Close(r) - r, _, err = compression.AutoDecompress(r) - if err != nil { - return nil, nil, errors.Wrapf(err, "cannot determine compression for layer blob %d", i) - } - - if len(macc.GetDescriptor().Layers) == 1 { - // return archive reader to enable optimized handling bay caller - return nil, finalize.BindToReader(r), nil - } - - nested.Close(r) - - fslayer, err := osfs.NewTempFileSystem() - if err != nil { - return nil, nil, errors.Wrapf(err, "cannot create filesystem for layer %d", i) - } - nested.With(func() error { - return vfs.Cleanup(fslayer) - }) - err = tarutils.ExtractTarToFs(fslayer, r) - if err != nil { - return nil, nil, errors.Wrapf(err, "cannot unpack layer blob %d", i) - } - - if cfs == nil { - cfs = fslayer - } else { - cfs = layerfs.New(fslayer, cfs) - } - fslayer = nil // don't cleanup used layer - if err := nested.Finalize(); err != nil { - return nil, nil, err - } - } - fs = cfs - cfs = nil // don't cleanup used filesystem - return fs, nil, nil -} - -// TODO: to be moved to vfs - -// CopyDir recursively copies a directory tree, attempting to preserve permissions. -// Source directory must exist, destination directory may exist. -// Symlinks are ignored and skipped. -func CopyDir(srcfs vfs.FileSystem, src string, dstfs vfs.FileSystem, dst string) (int64, int64, error) { - var fcnt, bcnt int64 - var n, b int64 - - src = vfs.Trim(srcfs, src) - dst = vfs.Trim(dstfs, dst) - - si, err := srcfs.Stat(src) - if err != nil { - return 0, 0, err - } - if !si.IsDir() { - return 0, 0, vfs.NewPathError("CopyDir", src, vfs.ErrNotDir) - } - - di, err := dstfs.Stat(dst) - if err != nil && !os.IsNotExist(err) { - return 0, 0, err - } - if err == nil && !di.IsDir() { - return 0, 0, vfs.NewPathError("CopyDir", dst, vfs.ErrNotDir) - } - - err = dstfs.MkdirAll(dst, si.Mode()) - if err != nil { - return 0, 0, err - } - - entries, err := vfs.ReadDir(srcfs, src) - if err != nil { - return 0, 0, err - } - - for _, entry := range entries { - srcPath := vfs.Join(srcfs, src, entry.Name()) - dstPath := vfs.Join(dstfs, dst, entry.Name()) - - if entry.IsDir() { - n, b, err = CopyDir(srcfs, srcPath, dstfs, dstPath) - fcnt += n - bcnt += b - } else { - // Skip symlinks. - if entry.Mode()&os.ModeSymlink != 0 { - var old string - old, err = srcfs.Readlink(srcPath) - if err == nil { - err = dstfs.Symlink(old, dstPath) - } - if err == nil { - fcnt++ - err = os.Chmod(dst, entry.Mode()) - } - } else { - err = vfs.CopyFile(srcfs, srcPath, dstfs, dstPath) - if err == nil { - bcnt += entry.Size() - fcnt++ - } - } - } - if err != nil { - return fcnt, bcnt, err - } - } - return fcnt, bcnt, nil -} diff --git a/pkg/contexts/ocm/download/handlers/dirtree/registration.go b/pkg/contexts/ocm/download/handlers/dirtree/registration.go deleted file mode 100644 index 9a0f84c20..000000000 --- a/pkg/contexts/ocm/download/handlers/dirtree/registration.go +++ /dev/null @@ -1,81 +0,0 @@ -package dirtree - -import ( - "fmt" - - "github.com/mandelsoft/goutils/errors" - ociv1 "github.com/opencontainers/image-spec/specs-go/v1" - "golang.org/x/exp/slices" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "github.com/open-component-model/ocm/pkg/listformat" - "github.com/open-component-model/ocm/pkg/registrations" -) - -func init() { - download.RegisterHandlerRegistrationHandler("ocm/dirtree", &RegistrationHandler{}) -} - -type Config struct { - AsArchive bool `json:"asArchive"` - OCIConfigTypes []string `json:"ociConfigTypes"` -} - -func AttributeDescription() map[string]string { - return map[string]string{ - "asArchive": "flag to request an archive download", - "ociConfigTypes": "a list of accepted OCI config archive mime types\n" + - "defaulted by " + ociv1.MediaTypeImageConfig + ".", - } -} - -type RegistrationHandler struct{} - -var _ download.HandlerRegistrationHandler = (*RegistrationHandler)(nil) - -func (r *RegistrationHandler) RegisterByName(handler string, ctx download.Target, config download.HandlerConfig, olist ...download.HandlerOption) (bool, error) { - var err error - - if handler != "" { - return true, fmt.Errorf("invalid dirtree handler %q", handler) - } - - attr, err := registrations.DecodeDefaultedConfig[Config](config) - if err != nil { - return true, errors.Wrapf(err, "cannot unmarshal download handler configuration") - } - - opts := download.NewHandlerOptions(olist...) - if opts.MimeType != "" && !slices.Contains(supportedMimeTypes, opts.MimeType) { - return true, errors.Wrapf(err, "mime type %s not supported", opts.MimeType) - } - if opts.ArtifactType != "" && slices.Contains(defaultArtifactTypes, opts.ArtifactType) && !attr.AsArchive { - return true, nil - } - - h := New(attr.OCIConfigTypes...).SetArchiveMode(attr.AsArchive) - if opts.MimeType == "" { - for _, m := range supportedMimeTypes { - opts.MimeType = m - download.For(ctx).Register(h, opts) - } - } else { - download.For(ctx).Register(h, opts) - } - - return true, nil -} - -func (r *RegistrationHandler) GetHandlers(ctx cpi.Context) registrations.HandlerInfos { - return registrations.NewLeafHandlerInfo("downloading directory tree-like resources", ` -The dirtree downloader is able to download directory-tree like -resources as directory structure (default) or archive. -The following artifact media types are supported: -`+listformat.FormatList("", SupportedMimeTypes()...)+` -By default, it is registered for the following resource types: -`+listformat.FormatList("", defaultArtifactTypes...)+` -It accepts a config with the following fields: -`+listformat.FormatMapElements("", AttributeDescription()), - ) -} diff --git a/pkg/contexts/ocm/download/handlers/dirtree/registration_test.go b/pkg/contexts/ocm/download/handlers/dirtree/registration_test.go deleted file mode 100644 index 78f31b243..000000000 --- a/pkg/contexts/ocm/download/handlers/dirtree/registration_test.go +++ /dev/null @@ -1,137 +0,0 @@ -package dirtree_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/mandelsoft/vfs/pkg/projectionfs" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/common" - "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/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download/handlers/dirtree" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - env2 "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/utils/tarutils" -) - -const TEST_ARTIFACT = "testArtifact" - -var _ = Describe("artifact management", func() { - var env *builder.Builder - - BeforeEach(func() { - env = builder.NewBuilder(env2.TestData()) - }) - - AfterEach(func() { - env.Cleanup() - }) - - Context("archive", func() { - BeforeEach(func() { - MustBeSuccessful(tarutils.CreateTarFromFs(Must(projectionfs.New(env, "testdata/layers/all")), "archive", tarutils.Gzip, env)) - - env.OCMCommonTransport("ctf", accessio.FormatDirectory, func() { - env.ComponentVersion(COMPONENT, VERSION, func() { - env.Resource(RESOURCE, VERSION, TEST_ARTIFACT, metav1.LocalRelation, func() { - env.BlobFromFile(artifactset.MediaType(mime.MIME_TGZ_ALT), "archive") - }) - }) - }) - }) - - It("downloads to dir", func() { - Expect(download.For(env).RegisterByName("ocm/dirtree", env.OCMContext(), &dirtree.Config{AsArchive: false}, download.ForArtifactType(TEST_ARTIFACT))).To(BeTrue()) - - repo := Must(ctf.Open(ocm.DefaultContext(), accessobj.ACC_READONLY, "ctf", 0, env)) - defer Close(repo) - cv := Must(repo.LookupComponentVersion(COMPONENT, VERSION)) - defer Close(cv) - res := Must(cv.GetResource(metav1.NewIdentity(RESOURCE))) - - p, buf := common.NewBufferedPrinter() - accepted, path := Must2(download.For(env).Download(p, res, "result", env)) - Expect(accepted).To(BeTrue()) - Expect(path).To(Equal("result")) - Expect(buf.String()).To(StringEqualTrimmedWithContext(` -result: 2 file(s) with 25 byte(s) written -`)) - - data := Must(vfs.ReadFile(env, "result/testfile")) - Expect(string(data)).To(StringEqualWithContext("testdata\n")) - data = Must(vfs.ReadFile(env, "result/dir/nestedfile")) - Expect(string(data)).To(StringEqualWithContext("other test data\n")) - }) - - It("downloads archive to archive", func() { - Expect(download.For(env).RegisterByName("ocm/dirtree", env.OCMContext(), &dirtree.Config{AsArchive: true}, download.ForArtifactType(TEST_ARTIFACT))).To(BeTrue()) - - repo := Must(ctf.Open(ocm.DefaultContext(), accessobj.ACC_READONLY, "ctf", 0, env)) - defer Close(repo) - cv := Must(repo.LookupComponentVersion(COMPONENT, VERSION)) - defer Close(cv) - res := Must(cv.GetResource(metav1.NewIdentity(RESOURCE))) - - p, buf := common.NewBufferedPrinter() - accepted, path := Must2(download.For(env).Download(p, res, "target", env)) - Expect(accepted).To(BeTrue()) - Expect(path).To(Equal("target")) - Expect(buf.String()).To(StringEqualTrimmedWithContext(` -target: 3584 byte(s) written -`)) - - MustBeSuccessful(env.MkdirAll("result", 0o700)) - resultfs := Must(projectionfs.New(env, "result")) - MustBeSuccessful(tarutils.ExtractArchiveToFs(resultfs, "target", env)) - - data := Must(vfs.ReadFile(env, "result/testfile")) - Expect(string(data)).To(StringEqualWithContext("testdata\n")) - data = Must(vfs.ReadFile(env, "result/dir/nestedfile")) - Expect(string(data)).To(StringEqualWithContext("other test data\n")) - }) - - It("downloads archive to archive using config", func() { - spec := ` -type: downloader.ocm.config.ocm.software -registrations: -- name: ocm/dirtree - artifactType: ` + TEST_ARTIFACT + ` - config: - asArchive: true -` - env.ConfigContext().ApplyData([]byte(spec), nil, "manual") - - repo := Must(ctf.Open(ocm.DefaultContext(), accessobj.ACC_READONLY, "ctf", 0, env)) - defer Close(repo) - cv := Must(repo.LookupComponentVersion(COMPONENT, VERSION)) - defer Close(cv) - res := Must(cv.GetResource(metav1.NewIdentity(RESOURCE))) - - p, buf := common.NewBufferedPrinter() - accepted, path := Must2(download.For(env).Download(p, res, "target", env)) - Expect(accepted).To(BeTrue()) - Expect(path).To(Equal("target")) - Expect(buf.String()).To(StringEqualTrimmedWithContext(` -target: 3584 byte(s) written -`)) - - MustBeSuccessful(env.MkdirAll("result", 0o700)) - resultfs := Must(projectionfs.New(env, "result")) - MustBeSuccessful(tarutils.ExtractArchiveToFs(resultfs, "target", env)) - - data := Must(vfs.ReadFile(env, "result/testfile")) - Expect(string(data)).To(StringEqualWithContext("testdata\n")) - data = Must(vfs.ReadFile(env, "result/dir/nestedfile")) - Expect(string(data)).To(StringEqualWithContext("other test data\n")) - }) - }) -}) diff --git a/pkg/contexts/ocm/download/handlers/executable/handler.go b/pkg/contexts/ocm/download/handlers/executable/handler.go deleted file mode 100644 index 3384bbd73..000000000 --- a/pkg/contexts/ocm/download/handlers/executable/handler.go +++ /dev/null @@ -1,82 +0,0 @@ -package executable - -import ( - "io" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/common/compression" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/mime" -) - -type Handler struct{} - -func init() { - h := &Handler{} - download.Register(h, download.ForCombi(resourcetypes.OCM_PLUGIN, mime.MIME_OCTET)) - download.Register(h, download.ForCombi(resourcetypes.OCM_PLUGIN, mime.MIME_GZIP)) - download.Register(h, download.ForCombi(resourcetypes.EXECUTABLE, mime.MIME_OCTET)) - download.Register(h, download.ForCombi(resourcetypes.EXECUTABLE, mime.MIME_GZIP)) -} - -func wrapErr(err error, racc cpi.ResourceAccess) error { - if err == nil { - return nil - } - m := racc.Meta() - return errors.Wrapf(err, "resource %s/%s%s", m.GetName(), m.GetVersion(), m.ExtraIdentity.String()) -} - -func (_ Handler) Download(p common.Printer, racc cpi.ResourceAccess, path string, fs vfs.FileSystem) (bool, string, error) { - rd, err := cpi.GetResourceReader(racc) - if err != nil { - return true, "", wrapErr(err, racc) - } - defer rd.Close() - - r, _, err := compression.AutoDecompress(rd) - if err != nil { - return true, "", err - } - if path == "" { - path = racc.Meta().GetName() - } - - t := "" - if ok, err := vfs.Exists(fs, path); err == nil && ok { - t = path - path += ".new" - } - file, err := fs.OpenFile(path, vfs.O_TRUNC|vfs.O_CREATE|vfs.O_WRONLY, 0o660) - if err != nil { - return true, "", wrapErr(errors.Wrapf(err, "creating target file %q", path), racc) - } - n, err := io.Copy(file, r) - file.Close() - if err == nil { - if t != "" { - err = fs.Remove(t) - if err == nil { - err = vfs.CopyFile(fs, path, fs, t) - } - if err == nil { - err = fs.Remove(path) - } - if err == nil { - path = t - } else { - p.Printf("cannot replace existing target file %s -> downloaded to %s\n", t, path) - } - } - p.Printf("%s: %d byte(s) written\n", path, n) - fs.Chmod(path, 0o755) - } else { - fs.Remove(path) - } - return true, path, wrapErr(err, racc) -} diff --git a/pkg/contexts/ocm/download/handlers/helm/download.go b/pkg/contexts/ocm/download/handlers/helm/download.go deleted file mode 100644 index c8fd341e0..000000000 --- a/pkg/contexts/ocm/download/handlers/helm/download.go +++ /dev/null @@ -1,67 +0,0 @@ -package helm - -import ( - "strings" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/finalizer" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/common" - "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/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" -) - -func Download(p common.Printer, ctx oci.Context, ref string, path string, fs vfs.FileSystem, creds ...credentials.CredentialsSource) error { - _, _, _, err := Download2(p, ctx, ref, path, fs, false, creds...) - return err -} - -func Download2(p common.Printer, ctx oci.Context, ref string, path string, fs vfs.FileSystem, asartifact bool, creds ...credentials.CredentialsSource) (chart, prov string, aset string, err error) { - var finalize finalizer.Finalizer - defer finalize.FinalizeWithErrorPropagationf(&err, "downloading helm chart %q", ref) - - r, err := oci.ParseRef(ref) - if err != nil { - return - } - - spec, err := ctx.MapUniformRepositorySpec(&r.UniformRepositorySpec) - if err != nil { - return - } - - repo, err := ctx.RepositoryForSpec(spec, creds...) - if err != nil { - return - } - finalize.Close(repo) - - art, err := repo.LookupArtifact(r.Repository, r.Version()) - if err != nil { - return - } - finalize.Close(art) - - if asartifact { - aset = strings.TrimSuffix(path, ".tgz") + ".ctf" - ctf, err := artifactset.Open(accessobj.ACC_CREATE|accessobj.ACC_WRITABLE, aset, 0o600, accessio.FormatTGZ, accessio.PathFileSystem(fs)) - if err != nil { - return "", "", "", errors.Wrapf(err, "cannot create artifact set") - } - err = artifactset.TransferArtifact(art, ctf) - if err == nil { - ctf.Annotate(artifactset.MAINARTIFACT_ANNOTATION, art.Digest().String()) - } - ctf.Close() - if err != nil { - fs.Remove(aset) - return "", "", "", errors.Wrapf(err, "cannot transfer helm OCI artifact") - } - } - chart, prov, err = download(p, art, path, fs) - return chart, prov, aset, err -} diff --git a/pkg/contexts/ocm/download/handlers/helm/handler.go b/pkg/contexts/ocm/download/handlers/helm/handler.go deleted file mode 100644 index b17aba902..000000000 --- a/pkg/contexts/ocm/download/handlers/helm/handler.go +++ /dev/null @@ -1,152 +0,0 @@ -package helm - -import ( - "io" - "strings" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/finalizer" - "github.com/mandelsoft/vfs/pkg/vfs" - helmregistry "helm.sh/helm/v3/pkg/registry" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common" - "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/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - registry "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/mime" -) - -const TYPE = resourcetypes.HELM_CHART - -type Handler struct{} - -func init() { - registry.Register(&Handler{}, registry.ForArtifactType(TYPE)) -} - -func (h Handler) fromArchive(p common.Printer, meth cpi.AccessMethod, path string, fs vfs.FileSystem) (_ bool, _ string, err error) { - basetype := mime.BaseType(helmregistry.ChartLayerMediaType) - if mime.BaseType(meth.MimeType()) != basetype { - return false, "", nil - } - - chart := path - if !strings.HasSuffix(chart, ".tgz") { - chart += ".tgz" - } - err = write(p, meth, chart, fs) - if err != nil { - return true, "", err - } - return true, chart, nil -} - -func (h Handler) fromOCIArtifact(p common.Printer, meth cpi.AccessMethod, path string, fs vfs.FileSystem) (_ bool, _ string, err error) { - var finalize finalizer.Finalizer - defer finalize.FinalizeWithErrorPropagationf(&err, "from OCI artifact") - - rd, err := meth.Reader() - if err != nil { - return true, "", err - } - finalize.Close(rd, "access method reader") - set, err := artifactset.Open(accessobj.ACC_READONLY, "", 0, accessio.Reader(rd)) - if err != nil { - return true, "", err - } - finalize.Close(set, "artifact set") - art, err := set.GetArtifact(set.GetMain().String()) - if err != nil { - return true, "", err - } - finalize.Close(art) - chart, _, err := download(p, art, path, fs) - if err != nil { - return true, "", err - } - return true, chart, nil -} - -func (h Handler) Download(p common.Printer, racc cpi.ResourceAccess, path string, fs vfs.FileSystem) (_ bool, _ string, err error) { - var finalize finalizer.Finalizer - defer finalize.FinalizeWithErrorPropagationf(&err, "downloading helm chart") - - if path == "" { - path = racc.Meta().GetName() - } - - meth, err := racc.AccessMethod() - if err != nil { - return false, "", err - } - finalize.Close(meth) - if mime.BaseType(meth.MimeType()) != mime.BaseType(artdesc.MediaTypeImageManifest) { - return h.fromArchive(p, meth, path, fs) - } - return h.fromOCIArtifact(p, meth, path, fs) -} - -func download(p common.Printer, art oci.ArtifactAccess, path string, fs vfs.FileSystem) (chart, prov string, err error) { - var finalize finalizer.Finalizer - defer finalize.FinalizeWithErrorPropagation(&err) - - m := art.ManifestAccess() - if m == nil { - return "", "", errors.Newf("artifact is no image manifest") - } - if len(m.GetDescriptor().Layers) < 1 { - return "", "", errors.Newf("no layers found") - } - chart = path - if !strings.HasSuffix(chart, ".tgz") { - chart += ".tgz" - } - blob, err := m.GetBlob(m.GetDescriptor().Layers[0].Digest) - if err != nil { - return "", "", err - } - finalize.Close(blob) - err = write(p, blob, chart, fs) - if err != nil { - return "", "", err - } - if len(m.GetDescriptor().Layers) > 1 { - prov = chart[:len(chart)-3] + "prov" - blob, err := m.GetBlob(m.GetDescriptor().Layers[1].Digest) - if err != nil { - return "", "", err - } - err = write(p, blob, path, fs) - if err != nil { - return "", "", err - } - } - return chart, prov, err -} - -func write(p common.Printer, blob blobaccess.DataReader, path string, fs vfs.FileSystem) (err error) { - var finalize finalizer.Finalizer - defer finalize.FinalizeWithErrorPropagation(&err) - - cr, err := blob.Reader() - if err != nil { - return err - } - finalize.Close(cr) - file, err := fs.OpenFile(path, vfs.O_TRUNC|vfs.O_CREATE|vfs.O_WRONLY, 0o660) - if err != nil { - return err - } - finalize.Close(file) - n, err := io.Copy(file, cr) - if err == nil { - p.Printf("%s: %d byte(s) written\n", path, n) - } - return nil -} diff --git a/pkg/contexts/ocm/download/handlers/init.go b/pkg/contexts/ocm/download/handlers/init.go deleted file mode 100644 index 63eb3f4b7..000000000 --- a/pkg/contexts/ocm/download/handlers/init.go +++ /dev/null @@ -1,10 +0,0 @@ -package handlers - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/download/handlers/blob" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/download/handlers/blueprint" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/download/handlers/dirtree" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/download/handlers/executable" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/download/handlers/helm" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/download/handlers/ocirepo" -) diff --git a/pkg/contexts/ocm/download/handlers/ocirepo/handler.go b/pkg/contexts/ocm/download/handlers/ocirepo/handler.go deleted file mode 100644 index d5f12b764..000000000 --- a/pkg/contexts/ocm/download/handlers/ocirepo/handler.go +++ /dev/null @@ -1,190 +0,0 @@ -package ocirepo - -import ( - "fmt" - "strings" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/finalizer" - "github.com/mandelsoft/goutils/general" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/common/accessobj" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/grammar" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/contexts/oci/transfer" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/ociuploadattr" - "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/download" -) - -//////////////////////////////////////////////////////////////////////////////// - -type handler struct { - spec *ociuploadattr.Attribute -} - -func New(repospec ...*ociuploadattr.Attribute) download.Handler { - return &handler{spec: general.Optional(repospec...)} -} - -func (h *handler) Download(p common.Printer, racc cpi.ResourceAccess, path string, fs vfs.FileSystem) (accepted bool, target string, err error) { - var finalize finalizer.Finalizer - defer finalize.FinalizeWithErrorPropagationf(&err, "upload to OCI registry") - - ctx := racc.GetOCMContext() - m, err := racc.AccessMethod() - if err != nil { - return false, "", err - } - finalize.Close(m, "access method for download") - - mediaType := m.MimeType() - - if !artdesc.IsOCIMediaType(mediaType) || (!strings.HasSuffix(mediaType, "+tar") && !strings.HasSuffix(mediaType, "+tar+gzip")) { - return false, "", nil - } - - log := download.Logger(ctx).WithName("ocireg") - - var repo oci.Repository - - var version string = "latest" - - aspec := m.AccessSpec() - namespace := racc.ReferenceHint() - if l, ok := aspec.(*localblob.AccessSpec); namespace == "" && ok { - namespace = l.ReferenceName - } - - i := strings.LastIndex(namespace, ":") - if i > 0 { - version = namespace[i:] - version = version[1:] // remove colon - namespace = namespace[:i] - } - - ocictx := ctx.OCIContext() - - var artspec oci.ArtSpec - var prefix string - var result oci.RefSpec - - if h.spec == nil { - log.Debug("no config set") - if path == "" { - return false, "", fmt.Errorf("path required as target repo specification") - } - ref, err := oci.ParseRef(path) - if err != nil { - return true, "", err - } - result.UniformRepositorySpec = ref.UniformRepositorySpec - repospec, err := ocictx.MapUniformRepositorySpec(&ref.UniformRepositorySpec) - if err != nil { - return true, "", err - } - repo, err = ocictx.RepositoryForSpec(repospec) - if err != nil { - return true, "", err - } - finalize.Close(repo, "repository for downloading OCI artifact") - artspec = ref.ArtSpec - } else { - log.Debug("evaluating config") - if path != "" { - artspec, err = oci.ParseArt(path) - if err != nil { - return true, "", err - } - } - var us *oci.UniformRepositorySpec - repo, us, prefix, err = h.spec.GetInfo(ctx) - if err != nil { - return true, "", err - } - result.UniformRepositorySpec = *us - } - log.Debug("using artifact spec", "spec", artspec.String()) - if artspec.Digest != nil { - return true, "", fmt.Errorf("digest no possible for target") - } - - if artspec.Repository != "" { - namespace = artspec.Repository - } - if artspec.Reference() != "" { - version = artspec.Reference() - } - - if prefix != "" && namespace != "" { - namespace = prefix + grammar.RepositorySeparator + namespace - } - if version == "" || version == "latest" { - version = racc.Meta().GetVersion() - } - log.Debug("using final target", "namespace", namespace, "version", version) - if namespace == "" { - return true, "", fmt.Errorf("no OCI namespace") - } - - var art oci.ArtifactAccess - - cand := m - if local, ok := aspec.(*localblob.AccessSpec); ok { - if local.GlobalAccess != nil { - s, err := ctx.AccessSpecForSpec(local.GlobalAccess) - if err == nil { - _ = s - // c, err := s.AccessMethod() // TODO: try global access for direct artifact access - // set cand to oci access method - } - } - } - if ocimeth, ok := accspeccpi.GetAccessMethodImplementation(cand).(ociartifact.AccessMethodImpl); ok { - // prepare for optimized point to point implementation - art, _, err = ocimeth.GetArtifact() - if err != nil { - return true, "", errors.Wrapf(err, "cannot access source artifact") - } - finalize.Close(art) - } - - ns, err := repo.LookupNamespace(namespace) - if err != nil { - return true, "", err - } - finalize.Close(ns) - - if art == nil { - log.Debug("using artifact set transfer mode") - set, err := artifactset.OpenFromDataAccess(accessobj.ACC_READONLY, m.MimeType(), m) - if err != nil { - return true, "", errors.Wrapf(err, "opening resource blob as artifact set") - } - finalize.Close(set) - art, err = set.GetArtifact(set.GetMain().String()) - if err != nil { - return true, "", errors.Wrapf(err, "get artifact from blob") - } - finalize.Close(art) - } else { - log.Debug("using direct transfer mode") - } - - p.Printf("uploading resource %s to %s[%s:%s]...\n", racc.Meta().GetName(), repo.GetSpecification().UniformRepositorySpec(), namespace, version) - err = transfer.TransferArtifact(art, ns, oci.AsTags(version)...) - if err != nil { - return true, "", errors.Wrapf(err, "transfer artifact") - } - - result.Repository = namespace - result.Tag = &version - return true, result.String(), nil -} diff --git a/pkg/contexts/ocm/download/handlers/ocirepo/registration.go b/pkg/contexts/ocm/download/handlers/ocirepo/registration.go deleted file mode 100644 index acf396ba7..000000000 --- a/pkg/contexts/ocm/download/handlers/ocirepo/registration.go +++ /dev/null @@ -1,87 +0,0 @@ -package ocirepo - -import ( - "fmt" - - "github.com/mandelsoft/goutils/errors" - "golang.org/x/exp/slices" - - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/ociuploadattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "github.com/open-component-model/ocm/pkg/listformat" - "github.com/open-component-model/ocm/pkg/registrations" -) - -const PATH = "oci/artifact" - -func init() { - download.RegisterHandlerRegistrationHandler(PATH, &RegistrationHandler{}) -} - -var supportedMimeTypes = []string{ - artifactset.MediaType(artdesc.MediaTypeImageManifest), - artifactset.MediaType(artdesc.MediaTypeImageIndex), -} - -type Config = ociuploadattr.Attribute - -func AttributeDescription() map[string]string { - return ociuploadattr.AttributeDescription() -} - -type RegistrationHandler struct{} - -var _ download.HandlerRegistrationHandler = (*RegistrationHandler)(nil) - -func (r *RegistrationHandler) RegisterByName(handler string, ctx download.Target, config download.HandlerConfig, olist ...download.HandlerOption) (bool, error) { - var err error - - if handler != "" { - return true, fmt.Errorf("invalid ocireg handler %q", handler) - } - - attr, err := registrations.DecodeConfig[Config](config, ociuploadattr.AttributeType{}.Decode) - if err != nil { - return true, errors.Wrapf(err, "cannot unmarshal download handler configuration") - } - - opts := download.NewHandlerOptions(olist...) - if opts.MimeType != "" && !slices.Contains(supportedMimeTypes, opts.MimeType) { - return true, errors.Wrapf(err, "mime type %s not supported", opts.MimeType) - } - - h := New(attr) - if opts.MimeType == "" { - for _, m := range supportedMimeTypes { - opts.MimeType = m - download.For(ctx).Register(h, opts) - } - } else { - download.For(ctx).Register(h, opts) - } - - return true, nil -} - -func (r *RegistrationHandler) GetHandlers(ctx cpi.Context) registrations.HandlerInfos { - return registrations.NewLeafHandlerInfo("uploading an OCI artifact to an OCI registry", ` -The artifact downloader is able to transfer OCI artifact-like resources -into an OCI registry given by the combination of the download target and the -registration config. - -If no config is given, the target must be an OCI reference with a potentially -omitted repository. The repo part is derived from the reference hint provided -by the resource's access specification. - -If the config is given, the target is used as repository name prefixed with an -optional repository prefix given by the configuration. - -The following artifact media types are supported: -`+listformat.FormatList("", supportedMimeTypes...)+` -It accepts a config with the following fields: -`+listformat.FormatMapElements("", AttributeDescription()), - ) -} diff --git a/pkg/contexts/ocm/download/handlers/ocirepo/upload_test.go b/pkg/contexts/ocm/download/handlers/ocirepo/upload_test.go deleted file mode 100644 index fcf95934c..000000000 --- a/pkg/contexts/ocm/download/handlers/ocirepo/upload_test.go +++ /dev/null @@ -1,221 +0,0 @@ -package ocirepo_test - -import ( - "strings" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/env/builder" - - ociv1 "github.com/opencontainers/image-spec/specs-go/v1" - - "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/oci/grammar" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - ctfoci "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/ociuploadattr" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download/handlers/ocirepo" - ctfocm "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/mime" -) - -const ( - COMP = "github.com/compa" - VERS = "1.0.0" - CTF = "ctf" -) - -const ( - HINT = "ocm.software/test" - UPLOAD = "ocm.software/upload" -) - -const ( - TARGETHOST = "target" - TARGETPATH = "/tmp/target" -) - -const ( - OCIHOST = "source" - OCIPATH = "/tmp/source" - OCINAMESPACE = "ocm/value" - OCIVERSION = "v2.0" -) - -const ARTIFACTSET = "/tmp/set.tgz" - -var _ = Describe("upload", func() { - var env *Builder - - BeforeEach(func() { - env = NewBuilder() - - // fake OCI registry - spec := Must(ctfoci.NewRepositorySpec(accessobj.ACC_WRITABLE, TARGETPATH, env)) - env.OCIContext().SetAlias(TARGETHOST, spec) - - env.OCICommonTransport(TARGETPATH, accessio.FormatDirectory) - }) - - AfterEach(func() { - env.Cleanup() - }) - - Context("local blob", func() { - BeforeEach(func() { - env.ArtifactSet(ARTIFACTSET, accessio.FormatTGZ, func() { - env.Manifest(OCIVERSION, func() { - env.Config(func() { - env.BlobStringData(mime.MIME_JSON, "{}") - }) - env.Layer(func() { - env.BlobStringData(mime.MIME_TEXT, "manifestlayer") - }) - }) - env.Annotation(artifactset.MAINARTIFACT_ANNOTATION, OCIVERSION) - }) - - env.OCMCommonTransport(CTF, accessio.FormatDirectory, func() { - env.ComponentVersion(COMP, VERS, func() { - env.Provider("mandelsoft") - env.Resource("value", "", resourcetypes.OCI_IMAGE, v1.LocalRelation, func() { - env.BlobFromFile(artifactset.MediaType(ociv1.MediaTypeImageManifest), ARTIFACTSET) - env.Hint(HINT) - }) - }) - }) - }) - - It("uploads local oci artifact blob", func() { - download.For(env).Register(ocirepo.New(), download.ForArtifactType(resourcetypes.OCI_IMAGE)) - - src := Must(ctfocm.Open(env, accessobj.ACC_READONLY, CTF, 0, env)) - defer Close(src, "source ctf") - - cv := Must(src.LookupComponentVersion(COMP, VERS)) - defer Close(cv) - - racc := Must(cv.GetResourceByIndex(0)) - - ok, path := Must2(download.For(env).Download(nil, racc, TARGETHOST+".alias"+grammar.RepositorySeparator+UPLOAD, env)) - Expect(ok).To(BeTrue()) - Expect(path).To(Equal("target.alias/ocm.software/upload:1.0.0")) - - env.OCMContext().Finalize() - - target, err := ctfoci.Open(env.OCIContext(), accessobj.ACC_READONLY, TARGETPATH, 0, env) - Expect(err).To(Succeed()) - defer Close(target) - Expect(target.ExistsArtifact(path[strings.Index(path, grammar.RepositorySeparator)+1:strings.Index(path, ":")], VERS)).To(BeTrue()) - }) - - It("uploads local oci artifact blob using named handler", func() { - download.RegisterHandlerByName(env, ocirepo.PATH, nil, download.ForArtifactType(resourcetypes.OCI_IMAGE)) - - src := Must(ctfocm.Open(env.OCMContext(), accessobj.ACC_READONLY, CTF, 0, accessio.PathFileSystem(env))) - defer Close(src, "source ctf") - - cv := Must(src.LookupComponentVersion(COMP, VERS)) - defer Close(cv) - - racc := Must(cv.GetResourceByIndex(0)) - - ok, path := Must2(download.For(env).Download(nil, racc, TARGETHOST+".alias"+grammar.RepositorySeparator+UPLOAD, env)) - Expect(ok).To(BeTrue()) - Expect(path).To(Equal("target.alias/ocm.software/upload:1.0.0")) - - env.OCMContext().Finalize() - - target, err := ctfoci.Open(env.OCIContext(), accessobj.ACC_READONLY, TARGETPATH, 0, env) - Expect(err).To(Succeed()) - defer Close(target) - Expect(target.ExistsArtifact(path[strings.Index(path, grammar.RepositorySeparator)+1:strings.Index(path, ":")], VERS)).To(BeTrue()) - }) - - It("uploads local oci artifact blob using named handler and config", func() { - cfg := ociuploadattr.Attribute{ - Ref: TARGETHOST + ".alias" + grammar.RepositorySeparator + "upload", - } - download.RegisterHandlerByName(env, ocirepo.PATH, cfg, download.ForArtifactType(resourcetypes.OCI_IMAGE)) - - src := Must(ctfocm.Open(env.OCMContext(), accessobj.ACC_READONLY, CTF, 0, accessio.PathFileSystem(env))) - defer Close(src, "source ctf") - - cv := Must(src.LookupComponentVersion(COMP, VERS)) - defer Close(cv) - - racc := Must(cv.GetResourceByIndex(0)) - - ok, path := Must2(download.For(env).Download(nil, racc, "", env)) - Expect(ok).To(BeTrue()) - // Expect(path).To(Equal("CommonTransportFormat::/tmp/target//upload/ocm.software/test:1.0.0")) - Expect(path).To(Equal("target.alias/upload/ocm.software/test:1.0.0")) - - env.OCMContext().Finalize() - - target, err := ctfoci.Open(env.OCIContext(), accessobj.ACC_READONLY, TARGETPATH, 0, env) - Expect(err).To(Succeed()) - defer Close(target) - // Expect(target.ExistsArtifact(path[strings.Index(path, grammar.RepositorySeparator+grammar.RepositorySeparator)+2:strings.LastIndex(path, ":")], VERS)).To(BeTrue()) - Expect(target.ExistsArtifact(path[strings.Index(path, grammar.RepositorySeparator)+1:strings.Index(path, ":")], VERS)).To(BeTrue()) - }) - }) - - Context("oci ref", func() { - BeforeEach(func() { - env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { - env.Namespace(OCINAMESPACE, func() { - env.Manifest(OCIVERSION, func() { - env.Config(func() { - env.BlobStringData(mime.MIME_JSON, "{}") - }) - env.Layer(func() { - env.BlobStringData(mime.MIME_TEXT, "manifestlayer") - }) - }) - }) - }) - - // fake OCI registry - spec := Must(ctfoci.NewRepositorySpec(accessobj.ACC_WRITABLE, OCIPATH, env)) - env.OCIContext().SetAlias(OCIHOST, spec) - - env.OCMCommonTransport(CTF, accessio.FormatDirectory, func() { - env.ComponentVersion(COMP, VERS, func() { - env.Provider("mandelsoft") - env.Resource("value", "", resourcetypes.OCI_IMAGE, v1.LocalRelation, func() { - env.Access(ociartifact.New(OCIHOST + ".alias" + grammar.RepositorySeparator + OCINAMESPACE + grammar.TagSeparator + OCIVERSION)) - }) - }) - }) - }) - - It("uploads oci artifact ref", func() { - download.For(env).Register(ocirepo.New(), download.ForArtifactType(resourcetypes.OCI_IMAGE)) - - src := Must(ctfocm.Open(env.OCMContext(), accessobj.ACC_READONLY, CTF, 0, accessio.PathFileSystem(env))) - defer Close(src, "source ctf") - - cv := Must(src.LookupComponentVersion(COMP, VERS)) - defer Close(cv, "version") - - racc := Must(cv.GetResourceByIndex(0)) - - ok, path := Must2(download.For(env).Download(nil, racc, TARGETHOST+".alias"+grammar.RepositorySeparator+UPLOAD, env)) - Expect(ok).To(BeTrue()) - Expect(path).To(Equal("target.alias/ocm.software/upload:1.0.0")) - - MustBeSuccessful(env.OCMContext().Finalize()) - - target := Must(ctfoci.Open(env.OCIContext(), accessobj.ACC_READONLY, TARGETPATH, 0, env)) - defer Close(target, "download target") - Expect(target.ExistsArtifact(path[strings.Index(path, grammar.RepositorySeparator)+1:strings.Index(path, ":")], VERS)).To(BeTrue()) - }) - }) -}) diff --git a/pkg/contexts/ocm/download/handlers/plugin/handler.go b/pkg/contexts/ocm/download/handlers/plugin/handler.go deleted file mode 100644 index 220e56ea8..000000000 --- a/pkg/contexts/ocm/download/handlers/plugin/handler.go +++ /dev/null @@ -1,49 +0,0 @@ -package plugin - -import ( - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/finalizer" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" -) - -// pluginHandler delegates download format of artifacts to a plugin based handler. -type pluginHandler struct { - plugin plugin.Plugin - name string - config []byte -} - -func New(p plugin.Plugin, name string, config []byte) (download.Handler, error) { - dd := p.GetDownloaderDescriptor(name) - if dd == nil { - return nil, errors.ErrUnknown(descriptor.KIND_DOWNLOADER, name, p.Name()) - } - - return &pluginHandler{ - plugin: p, - name: name, - config: config, - }, nil -} - -func (b *pluginHandler) Download(_ common.Printer, racc cpi.ResourceAccess, path string, _ vfs.FileSystem) (resp bool, eff string, rerr error) { - m, err := racc.AccessMethod() - if err != nil { - return true, "", err - } - var finalize finalizer.Finalizer - defer finalize.FinalizeWithErrorPropagation(&rerr) - - finalize.Close(m, "method for download") - r := accessio.NewOndemandReader(m) - finalize.Close(r, "reader for downlowd download") - - return b.plugin.Download(b.name, r, racc.Meta().Type, m.MimeType(), path, b.config) -} diff --git a/pkg/contexts/ocm/download/handlers/plugin/registration.go b/pkg/contexts/ocm/download/handlers/plugin/registration.go deleted file mode 100644 index d5394568e..000000000 --- a/pkg/contexts/ocm/download/handlers/plugin/registration.go +++ /dev/null @@ -1,148 +0,0 @@ -package plugin - -import ( - "encoding/json" - "fmt" - - "github.com/ghodss/yaml" - "github.com/mandelsoft/goutils/errors" - "github.com/xeipuuv/gojsonschema" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugincacheattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin" - "github.com/open-component-model/ocm/pkg/registrations" -) - -type Config = json.RawMessage - -func init() { - download.RegisterHandlerRegistrationHandler("plugin", &RegistrationHandler{}) -} - -type RegistrationHandler struct{} - -var _ download.HandlerRegistrationHandler = (*RegistrationHandler)(nil) - -func (r *RegistrationHandler) RegisterByName(handler string, ctx cpi.Context, config download.HandlerConfig, olist ...download.HandlerOption) (bool, error) { - path := cpi.NewNamePath(handler) - - if config == nil { - return true, fmt.Errorf("target specification required") - } - - if len(path) < 1 || len(path) > 2 { - return true, fmt.Errorf("plugin handler name must be of the form [/]") - } - - opts := download.NewHandlerOptions(olist...) - - name := "" - if len(path) > 1 { - name = path[1] - } - - attr, err := registrations.DecodeAnyConfig(config) - if err != nil { - return true, errors.Wrapf(err, "plugin download handler config for %s/%s", path[0], name) - } - - err = RegisterDownloadHandler(ctx, path[0], name, attr, opts) - return true, err -} - -func RegisterDownloadHandler(ctx cpi.Context, pname, name string, config []byte, olist ...download.HandlerOption) error { - opts := download.NewHandlerOptions(olist...) - set := plugincacheattr.Get(ctx) - if set == nil { - return errors.ErrUnknown(plugin.KIND_PLUGIN, pname) - } - - p := set.Get(pname) - if p == nil { - return errors.ErrUnknown(plugin.KIND_PLUGIN, pname) - } - d := p.LookupDownloader(name, opts.ArtifactType, opts.MimeType) - if len(d) == 0 { - if name == "" { - return fmt.Errorf("no downloader found for [art:%q, media:%q]", opts.ArtifactType, opts.MimeType) - } - return fmt.Errorf("downloader %s not valid for [art:%q, media:%q]", name, opts.ArtifactType, opts.MimeType) - } - for _, e := range d { - if len(config) != 0 { - if e.ConfigScheme == "" { - return errors.Newf("no config accepted by download handler") - } - err := ValidateConfig([]byte(e.ConfigScheme), config) - if err != nil { - return err - } - } - h, err := New(p, e.Name, config) - if err != nil { - return err - } - download.For(ctx).Register(h, opts) - } - return nil -} - -func ValidateConfig(schemadata, configdata []byte) error { - if string(schemadata) == "any" { - var i interface{} - return json.Unmarshal(configdata, &i) - } - data, err := yaml.YAMLToJSON(schemadata) - if err != nil { - return errors.Wrapf(err, "invalid JSON scheme for downloader config") - } - - schema, err := gojsonschema.NewSchema(gojsonschema.NewBytesLoader(configdata)) - if err != nil { - return errors.Wrapf(err, "invalid JSON scheme for downloader config") - } - - loader := gojsonschema.NewBytesLoader(data) - res, err := schema.Validate(loader) - if err != nil { - return err - } - - if !res.Valid() { - errs := res.Errors() - errMsg := errs[0].String() - for i := 1; i < len(errs); i++ { - errMsg = fmt.Sprintf("%s;%s", errMsg, errs[i].String()) - } - return errors.New(errMsg) - } - return nil -} - -func (r *RegistrationHandler) GetHandlers(ctx cpi.Context) registrations.HandlerInfos { - infos := registrations.NewNodeHandlerInfo("downloaders provided by plugins", - "sub namespace of the form <plugin name>/<handler>") - - set := plugincacheattr.Get(ctx) - if set == nil { - return infos - } - - for _, name := range set.PluginNames() { - p := set.Get(name) - if !p.IsValid() { - continue - } - for _, d := range set.Get(name).GetDescriptor().Downloaders { - i := registrations.HandlerInfo{ - Name: name + "/" + d.GetName(), - ShortDesc: "", - Description: d.GetDescription(), - } - infos = append(infos, i) - } - } - return infos -} diff --git a/pkg/contexts/ocm/download/logging.go b/pkg/contexts/ocm/download/logging.go deleted file mode 100644 index a0f17ad5e..000000000 --- a/pkg/contexts/ocm/download/logging.go +++ /dev/null @@ -1,13 +0,0 @@ -package download - -import ( - "github.com/mandelsoft/logging" - - ocmlog "github.com/open-component-model/ocm/pkg/logging" -) - -var REALM = ocmlog.DefineSubRealm("Downloaders", "downloader") - -func Logger(ctx logging.ContextProvider, messageContext ...logging.MessageContext) logging.Logger { - return ctx.LoggingContext().Logger(append([]logging.MessageContext{REALM}, messageContext...)) -} diff --git a/pkg/contexts/ocm/download/registration.go b/pkg/contexts/ocm/download/registration.go deleted file mode 100644 index adcb6ea3d..000000000 --- a/pkg/contexts/ocm/download/registration.go +++ /dev/null @@ -1,120 +0,0 @@ -package download - -import ( - "fmt" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/registrations" -) - -type Target = cpi.Context - -//////////////////////////////////////////////////////////////////////////////// - -type HandlerOptions struct { - HandlerKey `json:",inline"` - Priority int `json:"priority,omitempty"` -} - -func NewHandlerOptions(olist ...HandlerOption) *HandlerOptions { - var opts HandlerOptions - for _, o := range olist { - o.ApplyHandlerOptionTo(&opts) - } - return &opts -} - -func (o *HandlerOptions) ApplyHandlerOptionTo(opts *HandlerOptions) { - if o.Priority > 0 { - opts.Priority = o.Priority - } - o.HandlerKey.ApplyHandlerOptionTo(opts) -} - -type HandlerOption interface { - ApplyHandlerOptionTo(*HandlerOptions) -} - -//////////////////////////////////////////////////////////////////////////////// - -// HandlerKey is the registration key for download handlers. -type HandlerKey struct { - ArtifactType string `json:"artifactType,omitempty"` - MimeType string `json:"mimeType,omitempty"` -} - -var _ HandlerOption = HandlerKey{} - -func NewHandlerKey(artifactType, mimetype string) HandlerKey { - return HandlerKey{ - ArtifactType: artifactType, - MimeType: mimetype, - } -} - -func (k HandlerKey) ApplyHandlerOptionTo(opts *HandlerOptions) { - if k.ArtifactType != "" { - opts.ArtifactType = k.ArtifactType - } - if k.MimeType != "" { - opts.MimeType = k.MimeType - } -} - -func ForCombi(artifacttype string, mimetype string) HandlerOption { - return HandlerKey{ArtifactType: artifacttype, MimeType: mimetype} -} - -func ForMimeType(mimetype string) HandlerOption { - return HandlerKey{MimeType: mimetype} -} - -func ForArtifactType(artifacttype string) HandlerOption { - return HandlerKey{ArtifactType: artifacttype} -} - -type prio struct { - prio int -} - -func WithPrio(p int) HandlerOption { - return prio{p} -} - -func (o prio) ApplyHandlerOptionTo(opts *HandlerOptions) { - opts.Priority = o.prio -} - -//////////////////////////////////////////////////////////////////////////////// - -type ( - HandlerConfig = registrations.HandlerConfig - HandlerRegistrationHandler = registrations.HandlerRegistrationHandler[Target, HandlerOption] - HandlerRegistrationRegistry = registrations.HandlerRegistrationRegistry[Target, HandlerOption] - - RegistrationHandlerInfo = registrations.RegistrationHandlerInfo[Target, HandlerOption] -) - -func NewHandlerRegistrationRegistry(base ...HandlerRegistrationRegistry) HandlerRegistrationRegistry { - return registrations.NewHandlerRegistrationRegistry[Target, HandlerOption](base...) -} - -func NewRegistrationHandlerInfo(path string, handler HandlerRegistrationHandler) *RegistrationHandlerInfo { - return registrations.NewRegistrationHandlerInfo[Target, HandlerOption](path, handler) -} - -func RegisterHandlerRegistrationHandler(path string, handler HandlerRegistrationHandler) { - DefaultRegistry.RegisterRegistrationHandler(path, handler) -} - -func RegisterHandlerByName(ctx cpi.ContextProvider, name string, config HandlerConfig, opts ...HandlerOption) error { - hdlrs := For(ctx) - o, err := hdlrs.RegisterByName(name, ctx.OCMContext(), config, opts...) - if err != nil { - return err - } - if !o { - return fmt.Errorf("no matching handler found for %q", name) - } - return nil -} diff --git a/pkg/contexts/ocm/download/registry.go b/pkg/contexts/ocm/download/registry.go deleted file mode 100644 index 62a3ba839..000000000 --- a/pkg/contexts/ocm/download/registry.go +++ /dev/null @@ -1,190 +0,0 @@ -package download - -import ( - "sort" - "sync" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/general" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/common" - "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/utils/registry" - "github.com/open-component-model/ocm/pkg/registrations" - "github.com/open-component-model/ocm/pkg/runtimefinalizer" -) - -const ALL = "*" - -type Handler interface { - Download(p common.Printer, racc cpi.ResourceAccess, path string, fs vfs.FileSystem) (bool, string, error) -} - -const DEFAULT_BLOBHANDLER_PRIO = 100 - -type PrioHandler struct { - Handler - Prio int -} - -// MultiHandler is a Handler consisting of a sequence of handlers. -type MultiHandler []Handler - -var _ sort.Interface = MultiHandler(nil) - -func (m MultiHandler) Download(p common.Printer, racc cpi.ResourceAccess, path string, fs vfs.FileSystem) (bool, string, error) { - errs := errors.ErrListf("download") - for _, h := range m { - ok, p, err := h.Download(p, racc, path, fs) - if ok { - return ok, p, err - } - errs.Add(err) - } - return false, "", errs.Result() -} - -func (m MultiHandler) Len() int { - return len(m) -} - -func (m MultiHandler) Less(i, j int) bool { - pi := DEFAULT_BLOBHANDLER_PRIO - pj := DEFAULT_BLOBHANDLER_PRIO - - if p, ok := m[i].(*PrioHandler); ok { - pi = p.Prio - } - if p, ok := m[j].(*PrioHandler); ok { - pj = p.Prio - } - return pi > pj -} - -func (m MultiHandler) Swap(i, j int) { - m[i], m[j] = m[j], m[i] -} - -type Registry interface { - Copy() Registry - AsHandlerRegistrationRegistry() registrations.HandlerRegistrationRegistry[Target, HandlerOption] - - registrations.HandlerRegistrationRegistryAccess[Target, HandlerOption] - - Register(hdlr Handler, olist ...HandlerOption) - LookupHandler(art, media string) MultiHandler - Handler - DownloadAsBlob(p common.Printer, racc cpi.ResourceAccess, path string, fs vfs.FileSystem) (bool, string, error) -} - -func AsHandlerRegistrationRegistry(r Registry) registrations.HandlerRegistrationRegistry[Target, HandlerOption] { - if r == nil { - return nil - } - return r.AsHandlerRegistrationRegistry() -} - -type _registry struct { - registrations.HandlerRegistrationRegistry[Target, HandlerOption] - - id runtimefinalizer.ObjectIdentity - lock sync.RWMutex - base Registry - handlers *registry.Registry[Handler, registry.RegistrationKey] -} - -func NewRegistry(base ...Registry) Registry { - b := general.Optional(base...) - return &_registry{ - id: runtimefinalizer.NewObjectIdentity("downloader.registry.ocm.software"), - base: b, - HandlerRegistrationRegistry: NewHandlerRegistrationRegistry(AsHandlerRegistrationRegistry(b)), - handlers: registry.NewRegistry[Handler, registry.RegistrationKey](), - } -} - -func (r *_registry) AsHandlerRegistrationRegistry() registrations.HandlerRegistrationRegistry[Target, HandlerOption] { - return r.HandlerRegistrationRegistry -} - -func (r *_registry) Copy() Registry { - n := NewRegistry(r.base).(*_registry) - n.handlers = r.handlers.Copy() - return n -} - -func (r *_registry) LookupHandler(art, media string) MultiHandler { - r.lock.RLock() - defer r.lock.RUnlock() - - return r.getHandlers(art, media) -} - -func (r *_registry) Register(hdlr Handler, olist ...HandlerOption) { - opts := NewHandlerOptions(olist...) - r.lock.Lock() - defer r.lock.Unlock() - if opts.Priority != 0 { - hdlr = &PrioHandler{hdlr, opts.Priority} - } - r.handlers.Register(registry.RegistrationKey{opts.ArtifactType, opts.MimeType}, hdlr) -} - -func (r *_registry) getHandlers(arttype, mediatype string) MultiHandler { - list := r.handlers.LookupHandler(registry.RegistrationKey{arttype, mediatype}) - if r.base != nil { - list = append(list, r.base.LookupHandler(arttype, mediatype)...) - } - return list -} - -func (r *_registry) Download(p common.Printer, racc cpi.ResourceAccess, path string, fs vfs.FileSystem) (bool, string, error) { - p = common.AssurePrinter(p) - art := racc.Meta().GetType() - m, err := racc.AccessMethod() - if err != nil { - return false, "", err - } - defer m.Close() - mime := m.MimeType() - if ok, p, err := r.download(r.LookupHandler(art, mime), p, racc, path, fs); ok { - return ok, p, err - } - return r.download(r.LookupHandler(ALL, ""), p, racc, path, fs) -} - -func (r *_registry) DownloadAsBlob(p common.Printer, racc cpi.ResourceAccess, path string, fs vfs.FileSystem) (bool, string, error) { - return r.download(r.LookupHandler(ALL, ""), p, racc, path, fs) -} - -func (r *_registry) download(list MultiHandler, p common.Printer, racc cpi.ResourceAccess, path string, fs vfs.FileSystem) (bool, string, error) { - sort.Stable(list) - return list.Download(p, racc, path, fs) -} - -var DefaultRegistry = NewRegistry() - -func Register(hdlr Handler, olist ...HandlerOption) { - DefaultRegistry.Register(hdlr, olist...) -} - -//////////////////////////////////////////////////////////////////////////////// - -const ATTR_DOWNLOADER_HANDLERS = "github.com/open-component-model/ocm/pkg/contexts/ocm/download" - -func For(ctx cpi.ContextProvider) Registry { - if ctx == nil { - return DefaultRegistry - } - return ctx.OCMContext().GetAttributes().GetOrCreateAttribute(ATTR_DOWNLOADER_HANDLERS, create).(Registry) -} - -func create(datacontext.Context) interface{} { - return NewRegistry(DefaultRegistry) -} - -func SetFor(ctx datacontext.Context, registry Registry) { - ctx.GetAttributes().SetAttribute(ATTR_DOWNLOADER_HANDLERS, registry) -} diff --git a/pkg/contexts/ocm/download/setup.go b/pkg/contexts/ocm/download/setup.go deleted file mode 100644 index 7d83a00e2..000000000 --- a/pkg/contexts/ocm/download/setup.go +++ /dev/null @@ -1,27 +0,0 @@ -package download - -import ( - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" -) - -func init() { - datacontext.RegisterSetupHandler(datacontext.SetupHandlerFunction(setupContext)) -} - -func setupContext(mode datacontext.BuilderMode, ctx datacontext.Context) { - if octx, ok := ctx.(cpi.Context); ok { - switch mode { - case datacontext.MODE_SHARED: - fallthrough - case datacontext.MODE_DEFAULTED: - // do nothing, fallback to the default attribute lookup - case datacontext.MODE_EXTENDED: - SetFor(octx, NewRegistry(DefaultRegistry)) - case datacontext.MODE_CONFIGURED: - SetFor(octx, DefaultRegistry.Copy()) - case datacontext.MODE_INITIAL: - SetFor(octx, NewRegistry()) - } - } -} diff --git a/pkg/contexts/ocm/elements/artifactaccess/genericaccess/resource.go b/pkg/contexts/ocm/elements/artifactaccess/genericaccess/resource.go deleted file mode 100644 index 1aa4c341c..000000000 --- a/pkg/contexts/ocm/elements/artifactaccess/genericaccess/resource.go +++ /dev/null @@ -1,34 +0,0 @@ -package genericaccess - -import ( - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/generics" - - "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" -) - -func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, access ocm.AccessSpec) (cpi.ArtifactAccess[M], error) { - prov, err := cpi.NewAccessProviderForExternalAccessSpec(ctx, access) - if err != nil { - return nil, errors.Wrapf(err, "invalid external access method %q", access.GetKind()) - } - return cpi.NewArtifactAccessForProvider(generics.Cast[*M](meta), prov), nil -} - -func MustAccess[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, access ocm.AccessSpec) cpi.ArtifactAccess[M] { - a, err := Access(ctx, meta, access) - if err != nil { - panic(err) - } - return a -} - -func ResourceAccess(ctx ocm.Context, meta *ocm.ResourceMeta, access ocm.AccessSpec) (cpi.ResourceAccess, error) { - return Access(ctx, meta, access) -} - -func SourceAccess(ctx ocm.Context, meta *ocm.SourceMeta, access ocm.AccessSpec) (cpi.SourceAccess, error) { - return Access(ctx, meta, access) -} diff --git a/pkg/contexts/ocm/elements/artifactaccess/genericaccess/resource_test.go b/pkg/contexts/ocm/elements/artifactaccess/genericaccess/resource_test.go deleted file mode 100644 index 544570920..000000000 --- a/pkg/contexts/ocm/elements/artifactaccess/genericaccess/resource_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package genericaccess_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/oci/testhelper" - . "github.com/open-component-model/ocm/pkg/env/builder" - - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - me "github.com/open-component-model/ocm/pkg/contexts/ocm/elements/artifactaccess/genericaccess" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" -) - -const ( - OCIPATH = "/tmp/oci" - OCIHOST = "alias" -) - -var _ = Describe("dir tree resource access", func() { - var env *Builder - - BeforeEach(func() { - env = NewBuilder() - - FakeOCIRepo(env, OCIPATH, OCIHOST) - - env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { - OCIManifest1(env) - }) - }) - - AfterEach(func() { - env.Cleanup() - }) - - It("creates resource", func() { - spec := ociartifact.New(oci.StandardOCIRef(OCIHOST+".alias", OCINAMESPACE, OCIVERSION)) - - acc := Must(me.ResourceAccess(env.OCMContext(), compdesc.NewResourceMeta("test", resourcetypes.OCI_IMAGE, compdesc.LocalRelation), spec)) - - Expect(acc.ReferenceHint()).To(Equal(OCINAMESPACE + ":" + OCIVERSION)) - Expect(acc.GlobalAccess()).To(BeNil()) - Expect(acc.Meta().Type).To(Equal(resourcetypes.OCI_IMAGE)) - - blob := Must(acc.BlobAccess()) - defer Defer(blob.Close, "blob") - Expect(blob.MimeType()).To(Equal(artifactset.MediaType(artdesc.MediaTypeImageManifest))) - }) -}) diff --git a/pkg/contexts/ocm/elements/artifactaccess/githubaccess/resource.go b/pkg/contexts/ocm/elements/artifactaccess/githubaccess/resource.go deleted file mode 100644 index 4a1f7b282..000000000 --- a/pkg/contexts/ocm/elements/artifactaccess/githubaccess/resource.go +++ /dev/null @@ -1,33 +0,0 @@ -package githubaccess - -import ( - "github.com/mandelsoft/goutils/optionutils" - - "github.com/open-component-model/ocm/pkg/contexts/ocm" - access "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/github" - "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.DIRECTORY_TREE - -func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, repo string, commit string, opts ...Option) cpi.ArtifactAccess[M] { - eff := optionutils.EvalOptions(opts...) - if meta.GetType() == "" { - meta.SetType(TYPE) - } - - spec := access.New(repo, eff.APIHostName, commit) - // 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, repo string, commit string, opts ...Option) cpi.ResourceAccess { - return Access(ctx, meta, repo, commit, opts...) -} - -func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, repo string, commit string, opts ...Option) cpi.SourceAccess { - return Access(ctx, meta, repo, commit, opts...) -} diff --git a/pkg/contexts/ocm/elements/artifactaccess/helmaccess/resource.go b/pkg/contexts/ocm/elements/artifactaccess/helmaccess/resource.go deleted file mode 100644 index b5aae98b7..000000000 --- a/pkg/contexts/ocm/elements/artifactaccess/helmaccess/resource.go +++ /dev/null @@ -1,30 +0,0 @@ -package helmaccess - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm" - access "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/helm" - "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.HELM_CHART - -func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, chart string, repourl string) cpi.ArtifactAccess[M] { - if meta.GetType() == "" { - meta.SetType(TYPE) - } - - spec := access.New(chart, repourl) - // 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, chart string, repourl string) cpi.ResourceAccess { - return Access(ctx, meta, chart, repourl) -} - -func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, chart string, repourl string) cpi.SourceAccess { - return Access(ctx, meta, chart, repourl) -} diff --git a/pkg/contexts/ocm/elements/artifactaccess/mavenaccess/options.go b/pkg/contexts/ocm/elements/artifactaccess/mavenaccess/options.go deleted file mode 100644 index 8a965b695..000000000 --- a/pkg/contexts/ocm/elements/artifactaccess/mavenaccess/options.go +++ /dev/null @@ -1,20 +0,0 @@ -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 deleted file mode 100644 index 8a1622062..000000000 --- a/pkg/contexts/ocm/elements/artifactaccess/mavenaccess/resource.go +++ /dev/null @@ -1,39 +0,0 @@ -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_PACKAGE - -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/npmaccess/resource.go b/pkg/contexts/ocm/elements/artifactaccess/npmaccess/resource.go deleted file mode 100644 index 594d50cad..000000000 --- a/pkg/contexts/ocm/elements/artifactaccess/npmaccess/resource.go +++ /dev/null @@ -1,30 +0,0 @@ -package npmaccess - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm" - access "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/npm" - "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.NPM_PACKAGE - -func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, registry, pkg, version string) cpi.ArtifactAccess[M] { - if meta.GetType() == "" { - meta.SetType(TYPE) - } - - spec := access.New(registry, pkg, 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, registry, pkg, version string) cpi.ResourceAccess { - return Access(ctx, meta, registry, pkg, version) -} - -func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, registry, pkg, version string) cpi.SourceAccess { - return Access(ctx, meta, registry, pkg, version) -} diff --git a/pkg/contexts/ocm/elements/artifactaccess/ociartifactaccess/resource.go b/pkg/contexts/ocm/elements/artifactaccess/ociartifactaccess/resource.go deleted file mode 100644 index 1e39c3efa..000000000 --- a/pkg/contexts/ocm/elements/artifactaccess/ociartifactaccess/resource.go +++ /dev/null @@ -1,30 +0,0 @@ -package ociartifactaccess - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm" - access "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "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.OCI_IMAGE - -func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, refname string) cpi.ArtifactAccess[M] { - if meta.GetType() == "" { - meta.SetType(TYPE) - } - - spec := access.New(refname) - // 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, path string) cpi.ResourceAccess { - return Access(ctx, meta, path) -} - -func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, path string) cpi.SourceAccess { - return Access(ctx, meta, path) -} diff --git a/pkg/contexts/ocm/elements/artifactaccess/ociblobaccess/resource.go b/pkg/contexts/ocm/elements/artifactaccess/ociblobaccess/resource.go deleted file mode 100644 index b2b9363f8..000000000 --- a/pkg/contexts/ocm/elements/artifactaccess/ociblobaccess/resource.go +++ /dev/null @@ -1,39 +0,0 @@ -package github - -import ( - "github.com/mandelsoft/goutils/optionutils" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/contexts/ocm" - access "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociblob" - "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/mime" -) - -const TYPE = resourcetypes.BLOB - -func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, repository string, digest digest.Digest, size int64, opts ...Option) cpi.ArtifactAccess[M] { - eff := optionutils.EvalOptions(opts...) - if meta.GetType() == "" { - meta.SetType(TYPE) - } - - media := eff.MediaType - if media == "" { - media = mime.MIME_OCTET - } - spec := access.New(repository, digest, media, size) - // 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 string, digest digest.Digest, size int64, opts ...Option) cpi.ResourceAccess { - return Access(ctx, meta, repository, digest, size, opts...) -} - -func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, repository string, digest digest.Digest, size int64, opts ...Option) cpi.SourceAccess { - return Access(ctx, meta, repository, digest, size, opts...) -} diff --git a/pkg/contexts/ocm/elements/artifactaccess/s3access/resource.go b/pkg/contexts/ocm/elements/artifactaccess/s3access/resource.go deleted file mode 100644 index 45f551bc5..000000000 --- a/pkg/contexts/ocm/elements/artifactaccess/s3access/resource.go +++ /dev/null @@ -1,38 +0,0 @@ -package github - -import ( - "github.com/mandelsoft/goutils/optionutils" - - "github.com/open-component-model/ocm/pkg/contexts/ocm" - access "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/s3" - "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/mime" -) - -const TYPE = resourcetypes.BLOB - -func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, bucket, key string, opts ...Option) cpi.ArtifactAccess[M] { - eff := optionutils.EvalOptions(opts...) - if meta.GetType() == "" { - meta.SetType(TYPE) - } - - media := eff.MediaType - if media == "" { - media = mime.MIME_OCTET - } - spec := access.New(eff.Region, bucket, key, eff.Version, media) - // 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, bucket, key string, opts ...Option) cpi.ResourceAccess { - return Access(ctx, meta, bucket, key, opts...) -} - -func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, bucket, key string, opts ...Option) cpi.SourceAccess { - return Access(ctx, meta, bucket, key, opts...) -} diff --git a/pkg/contexts/ocm/elements/artifactaccess/wgetaccess/options.go b/pkg/contexts/ocm/elements/artifactaccess/wgetaccess/options.go deleted file mode 100644 index 25c343671..000000000 --- a/pkg/contexts/ocm/elements/artifactaccess/wgetaccess/options.go +++ /dev/null @@ -1,48 +0,0 @@ -package wgetaccess - -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 ( - Options = wget.Options - 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) -} - -func WithVerb(v string) Option { - return wget.WithVerb(v) -} - -func WithBody(v io.Reader) Option { - return wget.WithBody(v) -} - -func WithNoRedirect(r ...bool) Option { - return wget.WithNoRedirect(r...) -} diff --git a/pkg/contexts/ocm/elements/artifactaccess/wgetaccess/resource.go b/pkg/contexts/ocm/elements/artifactaccess/wgetaccess/resource.go deleted file mode 100644 index 2018c1afe..000000000 --- a/pkg/contexts/ocm/elements/artifactaccess/wgetaccess/resource.go +++ /dev/null @@ -1,30 +0,0 @@ -package wgetaccess - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm" - access "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/wget" - "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.BLOB - -func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, url string, opts ...Option) cpi.ArtifactAccess[M] { - if meta.GetType() == "" { - meta.SetType(TYPE) - } - - spec := access.New(url, 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, url string, opts ...Option) cpi.ResourceAccess { - return Access(ctx, meta, url, opts...) -} - -func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, url string, opts ...Option) cpi.SourceAccess { - return Access(ctx, meta, url, opts...) -} diff --git a/pkg/contexts/ocm/elements/artifactblob/api/options.go b/pkg/contexts/ocm/elements/artifactblob/api/options.go deleted file mode 100644 index 245023d1c..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/api/options.go +++ /dev/null @@ -1,71 +0,0 @@ -package api - -import ( - "github.com/mandelsoft/goutils/optionutils" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" -) - -type ( - Option = optionutils.Option[*Options] - GeneralOptionsProvider = optionutils.NestedOptionsProvider[*Options] -) - -type Options struct { - Global cpi.AccessSpec - Hint string -} - -var ( - _ optionutils.NestedOptionsProvider[*Options] = (*Options)(nil) - _ optionutils.Option[*Options] = (*Options)(nil) -) - -func (w *Options) NestedOptions() *Options { - return w -} - -func (o *Options) ApplyTo(opts *Options) { - if o.Global != nil { - opts.Global = o.Global - } - if o.Hint != "" { - opts.Hint = o.Hint - } -} - -func (o *Options) Apply(opts ...Option) { - optionutils.ApplyOptions(o, opts...) -} - -type hint string - -func (o hint) ApplyTo(opts *Options) { - opts.Hint = string(o) -} - -func WithHint(h string) Option { - return hint(h) -} - -func WrapHint[O any, P optionutils.OptionTargetProvider[*Options, O]](h string) optionutils.Option[P] { - return optionutils.OptionWrapper[*Options, O, P](WithHint(h)) -} - -//////////////////////////////////////////////////////////////////////////////// - -type global struct { - cpi.AccessSpec -} - -func (o global) ApplyTo(opts *Options) { - opts.Global = o.AccessSpec -} - -func WithGlobalAccess(a cpi.AccessSpec) Option { - return global{a} -} - -func WrapGlobalAccess[O any, P optionutils.OptionTargetProvider[*Options, O]](a cpi.AccessSpec) optionutils.Option[P] { - return optionutils.OptionWrapper[*Options, O, P](WithGlobalAccess(a)) -} diff --git a/pkg/contexts/ocm/elements/artifactblob/datablob/options.go b/pkg/contexts/ocm/elements/artifactblob/datablob/options.go deleted file mode 100644 index d22d5d607..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/datablob/options.go +++ /dev/null @@ -1,84 +0,0 @@ -package datablob - -import ( - "github.com/mandelsoft/goutils/optionutils" - - "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 compressionMode string - -const ( - COMPRESSION = compressionMode("compression") - DECOMPRESSION = compressionMode("decompression") - NONE = compressionMode("") -) - -type Options struct { - api.Options - MimeType string - Compression compressionMode -} - -var ( - _ api.GeneralOptionsProvider = (*Options)(nil) - _ Option = (*Options)(nil) -) - -func (o *Options) ApplyTo(opts *Options) { - o.Options.ApplyTo(&opts.Options) - if o.MimeType != "" { - opts.MimeType = o.MimeType - } -} - -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 - -type mimetype struct { - mime string -} - -func (o mimetype) ApplyTo(opts *Options) { - opts.MimeType = o.mime -} - -func WithMimeType(mime string) Option { - return mimetype{mime} -} - -//////////////////////////////////////////////////////////////////////////////// - -type compression struct { - mode compressionMode -} - -func (o compression) ApplyTo(opts *Options) { - opts.Compression = o.mode -} - -func WithCompression() Option { - return compression{COMPRESSION} -} - -func WithDecompression() Option { - return compression{DECOMPRESSION} -} diff --git a/pkg/contexts/ocm/elements/artifactblob/datablob/resource.go b/pkg/contexts/ocm/elements/artifactblob/datablob/resource.go deleted file mode 100644 index 120ab34ad..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/datablob/resource.go +++ /dev/null @@ -1,49 +0,0 @@ -package datablob - -import ( - "github.com/mandelsoft/goutils/generics" - "github.com/mandelsoft/goutils/optionutils" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "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/mime" -) - -func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, blob []byte, opts ...Option) cpi.ArtifactAccess[M] { - eff := optionutils.EvalOptions(opts...) - - media := eff.MimeType - if media == "" { - media = mime.MIME_OCTET - } - - var blobprov blobaccess.BlobAccessProvider - switch eff.Compression { - case NONE: - blobprov = blobaccess.ProviderForData(media, blob) - case COMPRESSION: - blob := blobaccess.ForData(media, blob) - defer blob.Close() - blob, _ = blobaccess.WithCompression(blob) - blobprov = blobaccess.ProviderForBlobAccess(blob) - case DECOMPRESSION: - blob := blobaccess.ForData(media, blob) - defer blob.Close() - blob, _ = blobaccess.WithDecompression(blob) - blobprov = blobaccess.ProviderForBlobAccess(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, media string, meta *ocm.ResourceMeta, blob []byte, opts ...Option) cpi.ResourceAccess { - return Access(ctx, meta, blob, opts...) -} - -func SourceAccess(ctx ocm.Context, media string, meta *ocm.SourceMeta, blob []byte, opts ...Option) cpi.SourceAccess { - return Access(ctx, meta, blob, opts...) -} diff --git a/pkg/contexts/ocm/elements/artifactblob/dirtreeblob/options.go b/pkg/contexts/ocm/elements/artifactblob/dirtreeblob/options.go deleted file mode 100644 index fdffe16d1..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/dirtreeblob/options.go +++ /dev/null @@ -1,77 +0,0 @@ -package dirtreeblob - -import ( - "github.com/mandelsoft/goutils/optionutils" - "github.com/mandelsoft/vfs/pkg/vfs" - - base "github.com/open-component-model/ocm/pkg/blobaccess/dirtree" - "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) -} - -//////////////////////////////////////////////////////////////////////////////// -// DirTree BlobAccess 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 WithFileSystem(fs vfs.FileSystem) Option { - return wrapBase(base.WithFileSystem(fs)) -} - -func WithExcludeFiles(files []string) Option { - return wrapBase(base.WithExcludeFiles(files)) -} - -func WithIncludeFiles(files []string) Option { - return wrapBase(base.WithIncludeFiles(files)) -} - -func WithFollowSymlinks(b ...bool) Option { - return wrapBase(base.WithFollowSymlinks(b...)) -} - -func WithPreserveDir(b ...bool) Option { - return wrapBase(base.WithPreserveDir(b...)) -} - -func WithCompressWithGzip(b ...bool) Option { - return wrapBase(base.WithCompressWithGzip(b...)) -} diff --git a/pkg/contexts/ocm/elements/artifactblob/dirtreeblob/resource.go b/pkg/contexts/ocm/elements/artifactblob/dirtreeblob/resource.go deleted file mode 100644 index a377a7a61..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/dirtreeblob/resource.go +++ /dev/null @@ -1,33 +0,0 @@ -package dirtreeblob - -import ( - "github.com/mandelsoft/goutils/generics" - "github.com/mandelsoft/goutils/optionutils" - - "github.com/open-component-model/ocm/pkg/blobaccess/dirtree" - "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.DIRECTORY_TREE - -func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, path string, opts ...Option) cpi.ArtifactAccess[M] { - eff := optionutils.EvalOptions(opts...) - if meta.GetType() == "" { - meta.SetType(TYPE) - } - blobprov := dirtree.Provider(path, &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 *cpi.ResourceMeta, path string, opts ...Option) cpi.ResourceAccess { - return Access(ctx, meta, path, opts...) -} - -func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, path string, opts ...Option) cpi.SourceAccess { - return Access(ctx, meta, path, opts...) -} diff --git a/pkg/contexts/ocm/elements/artifactblob/dirtreeblob/resource_test.go b/pkg/contexts/ocm/elements/artifactblob/dirtreeblob/resource_test.go deleted file mode 100644 index e286487e6..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/dirtreeblob/resource_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package dirtreeblob_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/common/accessobj" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - me "github.com/open-component-model/ocm/pkg/contexts/ocm/elements/artifactblob/dirtreeblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - testenv "github.com/open-component-model/ocm/pkg/env" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/utils/tarutils" -) - -var _ = Describe("dir tree resource access", func() { - var env *testenv.Environment - - BeforeEach(func() { - env = testenv.NewEnvironment(testenv.TestData()) - }) - - AfterEach(func() { - env.Cleanup() - }) - - It("creates resource", func() { - global := ociartifact.New("ghcr.io/mandelsoft/demo:v1.0.0") - - acc := me.ResourceAccess(env.OCMContext(), compdesc.NewResourceMeta("test", "", compdesc.LocalRelation), "testdata", - me.WithExcludeFiles([]string{"dir/a"}), - me.WithFileSystem(env.FileSystem()), - me.WithHint("demo"), - me.WithGlobalAccess(global), - ) - - Expect(acc.ReferenceHint()).To(Equal("demo")) - Expect(acc.GlobalAccess()).To(Equal(global)) - Expect(acc.Meta().Type).To(Equal(resourcetypes.DIRECTORY_TREE)) - - blob := Must(acc.BlobAccess()) - defer Defer(blob.Close, "blob") - Expect(blob.MimeType()).To(Equal(mime.MIME_TAR)) - - r := Must(blob.Reader()) - defer Defer(r.Close, "reader") - files := Must(tarutils.ListArchiveContentFromReader(r)) - Expect(files).To(ConsistOf([]string{ - "dir", - "dir/b", - "dir/c", - })) - }) - - It("adds resource", func() { - global := ociartifact.New("ghcr.io/mandelsoft/demo:v1.0.0") - - acc := me.ResourceAccess(env.OCMContext(), compdesc.NewResourceMeta("test", "", compdesc.LocalRelation), "testdata", - me.WithExcludeFiles([]string{"dir/a"}), - me.WithFileSystem(env.FileSystem()), - me.WithHint("demo"), - me.WithGlobalAccess(global), - ) - - arch := Must(ctf.Create(env, accessobj.ACC_CREATE, "ctf", 0o700, env, accessobj.FormatDirectory)) - c := Must(arch.LookupComponent("arcme.org/test")) - v := Must(c.NewVersion("v1.0.0")) - - MustBeSuccessful(v.SetResourceByAccess(acc)) - MustBeSuccessful(c.AddVersion(v)) - }) -}) diff --git a/pkg/contexts/ocm/elements/artifactblob/dockerdaemonblob/options.go b/pkg/contexts/ocm/elements/artifactblob/dockerdaemonblob/options.go deleted file mode 100644 index 60c4f35d5..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/dockerdaemonblob/options.go +++ /dev/null @@ -1,69 +0,0 @@ -package dockerdaemonblob - -import ( - "github.com/mandelsoft/goutils/optionutils" - - base "github.com/open-component-model/ocm/pkg/blobaccess/dockerdaemon" - "github.com/open-component-model/ocm/pkg/common" - "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) -} - -//////////////////////////////////////////////////////////////////////////////// -// Docker BlobAccess 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 WithName(n string) Option { - return wrapBase(base.WithName(n)) -} - -func WithVersion(v string) Option { - return wrapBase(base.WithVersion(v)) -} - -func WithVersionOverride(v string, flag ...bool) Option { - return wrapBase(base.WithVersionOverride(v, flag...)) -} - -func WithOrigin(o common.NameVersion) Option { - return wrapBase(base.WithOrigin(o)) -} diff --git a/pkg/contexts/ocm/elements/artifactblob/dockerdaemonblob/resource.go b/pkg/contexts/ocm/elements/artifactblob/dockerdaemonblob/resource.go deleted file mode 100644 index 7ee1ae185..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/dockerdaemonblob/resource.go +++ /dev/null @@ -1,40 +0,0 @@ -package dockerdaemonblob - -import ( - "github.com/mandelsoft/goutils/generics" - "github.com/mandelsoft/goutils/optionutils" - - "github.com/open-component-model/ocm/pkg/blobaccess/dockerdaemon" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "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.OCI_IMAGE - -func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, name string, opts ...Option) cpi.ArtifactAccess[M] { - eff := optionutils.EvalOptions(opts...) - if meta.GetType() == "" { - meta.SetType(TYPE) - } - eff.Blob.Context = ctx.OCIContext() - locator, version, err := dockerdaemon.ImageInfoFor(name, &eff.Blob) - if err == nil { - version = eff.Blob.Version - } - hint := ociartifact.Hint(optionutils.AsValue(eff.Blob.Origin), locator, eff.Hint, version) - blobprov := dockerdaemon.Provider(name, &eff.Blob) - accprov := cpi.NewAccessProviderForBlobAccessProvider(ctx, blobprov, 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 *cpi.ResourceMeta, path string, opts ...Option) cpi.ResourceAccess { - return Access(ctx, meta, path, opts...) -} - -func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, path string, opts ...Option) cpi.SourceAccess { - return Access(ctx, meta, path, opts...) -} diff --git a/pkg/contexts/ocm/elements/artifactblob/dockermultiblob/options.go b/pkg/contexts/ocm/elements/artifactblob/dockermultiblob/options.go deleted file mode 100644 index 451904c13..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/dockermultiblob/options.go +++ /dev/null @@ -1,69 +0,0 @@ -package dockermultiblob - -import ( - "github.com/mandelsoft/goutils/optionutils" - - base "github.com/open-component-model/ocm/pkg/blobaccess/dockermulti" - "github.com/open-component-model/ocm/pkg/common" - "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) -} - -//////////////////////////////////////////////////////////////////////////////// -// Docker BlobAccess 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 WithVariants(names ...string) Option { - return wrapBase(base.WithVariants(names...)) -} - -func WithVersion(v string) Option { - return wrapBase(base.WithVersion(v)) -} - -func WithOrigin(o common.NameVersion) Option { - return wrapBase(base.WithOrigin(o)) -} - -func WithPrinter(p common.Printer) Option { - return wrapBase(base.WithPrinter(p)) -} diff --git a/pkg/contexts/ocm/elements/artifactblob/dockermultiblob/resource.go b/pkg/contexts/ocm/elements/artifactblob/dockermultiblob/resource.go deleted file mode 100644 index 32993f9ea..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/dockermultiblob/resource.go +++ /dev/null @@ -1,35 +0,0 @@ -package dockermultiblob - -import ( - "github.com/mandelsoft/goutils/generics" - "github.com/mandelsoft/goutils/optionutils" - - "github.com/open-component-model/ocm/pkg/blobaccess/dockermulti" - "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.OCI_IMAGE - -func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, opts ...Option) cpi.ArtifactAccess[M] { - eff := optionutils.EvalOptions(opts...) - if meta.GetType() == "" { - meta.SetType(TYPE) - } - eff.Blob.Context = ctx.OCIContext() - - blobprov := dockermulti.Provider(&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 *cpi.ResourceMeta, opts ...Option) cpi.ResourceAccess { - return Access(ctx, meta, opts...) -} - -func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, name string, opts ...Option) cpi.SourceAccess { - return Access(ctx, meta, opts...) -} diff --git a/pkg/contexts/ocm/elements/artifactblob/externalblob/options.go b/pkg/contexts/ocm/elements/artifactblob/externalblob/options.go deleted file mode 100644 index c2a75ab35..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/externalblob/options.go +++ /dev/null @@ -1,22 +0,0 @@ -package externalblob - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/elements/artifactblob/api" -) - -type ( - Option = api.Option - Options = api.Options -) - -//////////////////////////////////////////////////////////////////////////////// -// General Options - -func WithHint(h string) Option { - return api.WrapHint[Options](h) -} - -func WithGlobalAccess(a cpi.AccessSpec) Option { - return api.WrapGlobalAccess[Options](a) -} diff --git a/pkg/contexts/ocm/elements/artifactblob/externalblob/resource.go b/pkg/contexts/ocm/elements/artifactblob/externalblob/resource.go deleted file mode 100644 index 47d858440..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/externalblob/resource.go +++ /dev/null @@ -1,68 +0,0 @@ -package externalblob - -import ( - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/generics" - "github.com/mandelsoft/goutils/optionutils" - - "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" -) - -func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, access ocm.AccessSpec, opts ...Option) (cpi.ArtifactAccess[M], error) { - eff := optionutils.EvalOptions(opts...) - - hint := eff.Hint - if hint == "" { - hint = ocm.ReferenceHint(access, &cpi.DummyComponentVersionAccess{ctx}) - } - global := eff.Global - if global == nil { - global = ocm.GlobalAccess(access, ctx) - } - - prov, err := cpi.NewAccessProviderForExternalAccessSpec(ctx, access) - if err != nil { - return nil, errors.Wrapf(err, "invalid external access method %q", access.GetKind()) - } - return cpi.NewArtifactAccessForProvider(generics.Cast[*M](meta), newAccessProvider(prov, hint, global)), nil -} - -type _accessProvider = cpi.AccessProvider - -type accessProvider struct { - _accessProvider - hint string - global cpi.AccessSpec -} - -func newAccessProvider(prov cpi.AccessProvider, hint string, global cpi.AccessSpec) cpi.AccessProvider { - return &accessProvider{ - _accessProvider: prov, - hint: hint, - global: global, - } -} - -func (p *accessProvider) ReferenceHint() string { - if p.hint != "" { - return p.hint - } - return p._accessProvider.ReferenceHint() -} - -func (p *accessProvider) GlobalAccess() cpi.AccessSpec { - if p.global != nil { - return p.global - } - return p._accessProvider.GlobalAccess() -} - -func ResourceAccess(ctx ocm.Context, meta *cpi.ResourceMeta, access cpi.AccessSpec, opts ...Option) (cpi.ResourceAccess, error) { - return Access(ctx, meta, access, opts...) -} - -func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, access cpi.AccessSpec, opts ...Option) (cpi.SourceAccess, error) { - return Access(ctx, meta, access, opts...) -} diff --git a/pkg/contexts/ocm/elements/artifactblob/fileblob/options.go b/pkg/contexts/ocm/elements/artifactblob/fileblob/options.go deleted file mode 100644 index 99b1faa92..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/fileblob/options.go +++ /dev/null @@ -1,85 +0,0 @@ -package fileblob - -import ( - "github.com/mandelsoft/goutils/optionutils" - "github.com/mandelsoft/vfs/pkg/vfs" - - "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 compressionMode string - -const ( - COMPRESSION = compressionMode("compression") - DECOMPRESSION = compressionMode("decompression") - NONE = compressionMode("") -) - -type Options struct { - api.Options - FileSystem vfs.FileSystem - Compression compressionMode -} - -var ( - _ api.GeneralOptionsProvider = (*Options)(nil) - _ Option = (*Options)(nil) -) - -func (o *Options) ApplyTo(opts *Options) { - o.Options.ApplyTo(&opts.Options) - if o.FileSystem != nil { - opts.FileSystem = o.FileSystem - } -} - -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 - -type filesystem struct { - fs vfs.FileSystem -} - -func (o filesystem) ApplyTo(opts *Options) { - opts.FileSystem = o.fs -} - -func WithFileSystem(fs vfs.FileSystem) Option { - return filesystem{fs} -} - -//////////////////////////////////////////////////////////////////////////////// - -type compression struct { - mode compressionMode -} - -func (o compression) ApplyTo(opts *Options) { - opts.Compression = o.mode -} - -func WithCompression() Option { - return compression{COMPRESSION} -} - -func WithDecompression() Option { - return compression{DECOMPRESSION} -} diff --git a/pkg/contexts/ocm/elements/artifactblob/fileblob/resource.go b/pkg/contexts/ocm/elements/artifactblob/fileblob/resource.go deleted file mode 100644 index 6415b6919..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/fileblob/resource.go +++ /dev/null @@ -1,54 +0,0 @@ -package fileblob - -import ( - "github.com/mandelsoft/goutils/generics" - "github.com/mandelsoft/goutils/optionutils" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/blobaccess/file" - "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/mime" -) - -const TYPE = "blob" - -func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, media string, meta P, path string, opts ...Option) cpi.ArtifactAccess[M] { - eff := optionutils.EvalOptions(opts...) - - if meta.GetType() == "" { - meta.SetType(TYPE) - } - if media == "" { - media = mime.MIME_OCTET - } - - var blobprov blobaccess.BlobAccessProvider - switch eff.Compression { - case NONE: - blobprov = file.Provider(media, path, eff.FileSystem) - case COMPRESSION: - blob := file.BlobAccess(media, path, eff.FileSystem) - defer blob.Close() - blob, _ = blobaccess.WithCompression(blob) - blobprov = blobaccess.ProviderForBlobAccess(blob) - case DECOMPRESSION: - blob := file.BlobAccess(media, path, eff.FileSystem) - defer blob.Close() - blob, _ = blobaccess.WithDecompression(blob) - blobprov = blobaccess.ProviderForBlobAccess(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, media string, meta *ocm.ResourceMeta, path string, opts ...Option) cpi.ResourceAccess { - return Access(ctx, media, meta, path, opts...) -} - -func SourceAccess(ctx ocm.Context, media string, meta *ocm.SourceMeta, path string, opts ...Option) cpi.SourceAccess { - return Access(ctx, media, meta, path, opts...) -} diff --git a/pkg/contexts/ocm/elements/artifactblob/genericblob/options.go b/pkg/contexts/ocm/elements/artifactblob/genericblob/options.go deleted file mode 100644 index 93105221f..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/genericblob/options.go +++ /dev/null @@ -1,19 +0,0 @@ -package genericblob - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/elements/artifactblob/api" -) - -type ( - Options = api.Options - Option = api.Option -) - -func WithHint(h string) Option { - return api.WithHint(h) -} - -func WithGlobalAccess(a cpi.AccessSpec) Option { - return api.WithGlobalAccess(a) -} diff --git a/pkg/contexts/ocm/elements/artifactblob/genericblob/resource.go b/pkg/contexts/ocm/elements/artifactblob/genericblob/resource.go deleted file mode 100644 index 6e511ae64..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/genericblob/resource.go +++ /dev/null @@ -1,25 +0,0 @@ -package genericblob - -import ( - "github.com/mandelsoft/goutils/generics" - "github.com/mandelsoft/goutils/optionutils" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" -) - -func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx cpi.Context, meta P, blob blobaccess.BlobAccessProvider, opts ...Option) cpi.ArtifactAccess[M] { - eff := optionutils.EvalOptions(opts...) - accprov := cpi.NewAccessProviderForBlobAccessProvider(ctx, blob, 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 cpi.Context, media string, meta *cpi.ResourceMeta, blob blobaccess.BlobAccessProvider, opts ...Option) cpi.ResourceAccess { - return Access(ctx, meta, blob, opts...) -} - -func SourceAccess(ctx cpi.Context, media string, meta *cpi.SourceMeta, blob blobaccess.BlobAccessProvider, opts ...Option) cpi.SourceAccess { - return Access(ctx, meta, blob, opts...) -} diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/helmblob_test.go b/pkg/contexts/ocm/elements/artifactblob/helmblob/helmblob_test.go deleted file mode 100644 index f20001baa..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/helmblob/helmblob_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package helmblob_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/open-component-model/ocm/pkg/env/builder" - - "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.SetResourceByAccess(me.ResourceAccess(e.OCMContext(), cpi.NewResourceMeta("helm1", "blob", metav1.LocalRelation), "/testdata/testchart1", me.WithFileSystem(e.FileSystem())))) - MustBeSuccessful(cv.SetResourceByAccess(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/options.go b/pkg/contexts/ocm/elements/artifactblob/helmblob/options.go deleted file mode 100644 index 725ffb40c..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/helmblob/options.go +++ /dev/null @@ -1,87 +0,0 @@ -package helmblob - -import ( - "github.com/mandelsoft/goutils/optionutils" - "github.com/mandelsoft/vfs/pkg/vfs" - - base "github.com/open-component-model/ocm/pkg/blobaccess/helm" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "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) -} - -//////////////////////////////////////////////////////////////////////////////// -// DirTree BlobAccess 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 WithFileSystem(fs vfs.FileSystem) Option { - return wrapBase(base.WithFileSystem(fs)) -} - -func WithContext(ctx oci.ContextProvider) Option { - return wrapBase(base.WithContext(ctx)) -} - -func WithIVersion(v string) Option { - return wrapBase(base.WithVersion(v)) -} - -func WithIVersionOverride(v string, flag ...bool) Option { - return wrapBase(base.WithVersionOverride(v, flag...)) -} - -func WithCACert(v string) Option { - return wrapBase(base.WithCACert(v)) -} - -func WithCACertFile(v string) Option { - return wrapBase(base.WithCACertFile(v)) -} - -func WithHelmRepository(v string) Option { - return wrapBase(base.WithHelmRepository(v)) -} - -func WithPrinter(v common.Printer) Option { - return wrapBase(base.WithPrinter(v)) -} diff --git a/pkg/contexts/ocm/elements/artifactblob/helmblob/resource.go b/pkg/contexts/ocm/elements/artifactblob/helmblob/resource.go deleted file mode 100644 index 88d48b2a5..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/helmblob/resource.go +++ /dev/null @@ -1,34 +0,0 @@ -package helmblob - -import ( - "github.com/mandelsoft/goutils/generics" - "github.com/mandelsoft/goutils/optionutils" - - "github.com/open-component-model/ocm/pkg/blobaccess/helm" - "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.HELM_CHART - -func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, path string, opts ...Option) cpi.ArtifactAccess[M] { - eff := optionutils.EvalOptions(append(opts, WithContext(ctx))...) - if meta.GetType() == "" { - meta.SetType(TYPE) - } - hint := eff.Hint - blobprov := helm.Provider(path, &eff.Blob) - accprov := cpi.NewAccessProviderForBlobAccessProvider(ctx, blobprov, 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 *cpi.ResourceMeta, path string, opts ...Option) cpi.ResourceAccess { - return Access(ctx, meta, path, opts...) -} - -func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, path string, opts ...Option) cpi.SourceAccess { - return Access(ctx, meta, path, opts...) -} diff --git a/pkg/contexts/ocm/elements/artifactblob/mavenblob/access_test.go b/pkg/contexts/ocm/elements/artifactblob/mavenblob/access_test.go deleted file mode 100644 index 88f9fb67a..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/mavenblob/access_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package mavenblob_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "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/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" - "github.com/open-component-model/ocm/pkg/maven/maventest" -) - -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())) - Expect(a.ReferenceHint()).To(Equal("")) - b := Must(a.BlobAccess()) - defer Close(b) - Expect(string(Must(b.Get()))).To(Equal(`{"some": "test content"}`)) - - MustBeSuccessful(cv.SetResourceByAccess(a)) - r := Must(cv.GetResourceByIndex(0)) - m := Must(r.AccessMethod()) - defer Close(m) - Expect(string(Must(m.Get()))).To(Equal(`{"some": "test content"}`)) - }) - - It("blobaccess for package", 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") - - a := me.ResourceAccessForMavenCoords(env.OCMContext(), Must(elements.ResourceMeta("mavenblob", resourcetypes.OCM_JSON, elements.WithLocalRelation())), repo, coords, me.WithCachingFileSystem(env.FileSystem())) - Expect(a.ReferenceHint()).To(Equal(coords.GAV())) - }) - }) -}) diff --git a/pkg/contexts/ocm/elements/artifactblob/mavenblob/options.go b/pkg/contexts/ocm/elements/artifactblob/mavenblob/options.go deleted file mode 100644 index 55e9052f5..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/mavenblob/options.go +++ /dev/null @@ -1,104 +0,0 @@ -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" - "github.com/open-component-model/ocm/pkg/maven" -) - -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 WithHintForCoords(coords *maven.Coordinates) Option { - if coords.IsPackage() { - return WithHint(coords.GAV()) - } - return optionutils.NoOption[*Options]{} -} - -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 deleted file mode 100644 index 63aa8c70f..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/mavenblob/resource.go +++ /dev/null @@ -1,46 +0,0 @@ -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_PACKAGE - -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 eff.Blob.IsPackage() && eff.Hint == "" { - eff.Hint = maven.NewCoordinates(groupId, artifactId, version).GAV() - } - - if meta.GetType() == "" { - meta.SetType(TYPE) - } - - blobprov := maven.Provider(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/ociartifactblob/options.go b/pkg/contexts/ocm/elements/artifactblob/ociartifactblob/options.go deleted file mode 100644 index 2f03a9424..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/ociartifactblob/options.go +++ /dev/null @@ -1,66 +0,0 @@ -package ociartifactblob - -import ( - "github.com/mandelsoft/goutils/optionutils" - - base "github.com/open-component-model/ocm/pkg/blobaccess/ociartifact" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "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) -} - -//////////////////////////////////////////////////////////////////////////////// -// DirTree BlobAccess 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 WithContext(ctx oci.ContextProvider) Option { - return wrapBase(base.WithContext(ctx)) -} - -func WithVersion(v string) Option { - return wrapBase(base.WithVersion(v)) -} - -func WithPrinter(v common.Printer) Option { - return wrapBase(base.WithPrinter(v)) -} diff --git a/pkg/contexts/ocm/elements/artifactblob/ociartifactblob/resource.go b/pkg/contexts/ocm/elements/artifactblob/ociartifactblob/resource.go deleted file mode 100644 index d93d016f7..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/ociartifactblob/resource.go +++ /dev/null @@ -1,43 +0,0 @@ -package ociartifactblob - -import ( - "github.com/mandelsoft/goutils/generics" - "github.com/mandelsoft/goutils/optionutils" - - blob "github.com/open-component-model/ocm/pkg/blobaccess/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "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.OCI_IMAGE - -func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, refname string, opts ...Option) cpi.ArtifactAccess[M] { - eff := optionutils.EvalOptions(append(opts, WithContext(ctx))...) - if meta.GetType() == "" { - meta.SetType(TYPE) - } - - hint := eff.Hint - if hint == "" { - ref, err := oci.ParseRef(refname) - if err == nil { - hint = ref.String() - } - } - - blobprov := blob.Provider(refname, &eff.Blob) - accprov := cpi.NewAccessProviderForBlobAccessProvider(ctx, blobprov, 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 *cpi.ResourceMeta, path string, opts ...Option) cpi.ResourceAccess { - return Access(ctx, meta, path, opts...) -} - -func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, path string, opts ...Option) cpi.SourceAccess { - return Access(ctx, meta, path, opts...) -} diff --git a/pkg/contexts/ocm/elements/artifactblob/textblob/options.go b/pkg/contexts/ocm/elements/artifactblob/textblob/options.go deleted file mode 100644 index ab1d01c96..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/textblob/options.go +++ /dev/null @@ -1,43 +0,0 @@ -package textblob - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/elements/artifactblob/datablob" -) - -type ( - Option = datablob.Option - Options = datablob.Options -) - -const ( - COMPRESSION = datablob.COMPRESSION - DECOMPRESSION = datablob.DECOMPRESSION - NONE = datablob.NONE -) - -//////////////////////////////////////////////////////////////////////////////// -// General Options - -func WithHint(h string) Option { - return datablob.WithHint(h) -} - -func WithGlobalAccess(a cpi.AccessSpec) Option { - return datablob.WithGlobalAccess(a) -} - -//////////////////////////////////////////////////////////////////////////////// -// Local Options - -func WithMimeType(mime string) Option { - return datablob.WithMimeType(mime) -} - -func WithCompression() Option { - return datablob.WithCompression() -} - -func WithDecompression() Option { - return datablob.WithDecompression() -} diff --git a/pkg/contexts/ocm/elements/artifactblob/textblob/resource.go b/pkg/contexts/ocm/elements/artifactblob/textblob/resource.go deleted file mode 100644 index a38e26f07..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/textblob/resource.go +++ /dev/null @@ -1,27 +0,0 @@ -package textblob - -import ( - "github.com/mandelsoft/goutils/optionutils" - - "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/elements/artifactblob/datablob" - "github.com/open-component-model/ocm/pkg/mime" -) - -func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, blob string, opts ...Option) cpi.ArtifactAccess[M] { - eff := optionutils.EvalOptions(opts...) - if eff.MimeType == "" { - eff.MimeType = mime.MIME_TEXT - } - return datablob.Access(ctx, meta, []byte(blob), eff) -} - -func ResourceAccess(ctx ocm.Context, meta *ocm.ResourceMeta, blob string, opts ...Option) cpi.ResourceAccess { - return Access(ctx, meta, blob, opts...) -} - -func SourceAccess(ctx ocm.Context, meta *ocm.SourceMeta, blob string, opts ...Option) cpi.SourceAccess { - return Access(ctx, meta, blob, opts...) -} diff --git a/pkg/contexts/ocm/elements/artifactblob/wgetblob/options.go b/pkg/contexts/ocm/elements/artifactblob/wgetblob/options.go deleted file mode 100644 index ea29ff9b1..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/wgetblob/options.go +++ /dev/null @@ -1,90 +0,0 @@ -package wgetblob - -import ( - "io" - "net/http" - - "github.com/mandelsoft/goutils/optionutils" - "github.com/mandelsoft/logging" - - base "github.com/open-component-model/ocm/pkg/blobaccess/wget" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/wget" - "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 WithMimeType(mime string) Option { - return wrapBase(base.WithMimeType(mime)) -} - -func WithCredentials(creds credentials.Credentials) Option { - return wrapBase(base.WithCredentials(creds)) -} - -func WithHeader(h http.Header) Option { - return wrapBase(base.WithHeader(h)) -} - -func WithVerb(v string) Option { - return wrapBase(base.WithVerb(v)) -} - -func WithBody(v io.Reader) Option { - return wrapBase(base.WithBody(v)) -} - -func WithNoRedirect(r ...bool) Option { - return wrapBase(wget.WithNoRedirect(r...)) -} diff --git a/pkg/contexts/ocm/elements/artifactblob/wgetblob/resource.go b/pkg/contexts/ocm/elements/artifactblob/wgetblob/resource.go deleted file mode 100644 index 535fb8e93..000000000 --- a/pkg/contexts/ocm/elements/artifactblob/wgetblob/resource.go +++ /dev/null @@ -1,35 +0,0 @@ -package wgetblob - -import ( - "github.com/mandelsoft/goutils/generics" - "github.com/mandelsoft/goutils/optionutils" - - "github.com/open-component-model/ocm/pkg/blobaccess/wget" - "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.BLOB - -func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, url string, opts ...Option) cpi.ArtifactAccess[M] { - eff := optionutils.EvalOptions(optionutils.WithDefaults(opts, WithCredentialContext(ctx))...) - - if meta.GetType() == "" { - meta.SetType(TYPE) - } - - blobprov := wget.Provider(url, &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, url string, opts ...Option) cpi.ResourceAccess { - return Access(ctx, meta, url, opts...) -} - -func SourceAccess(ctx ocm.Context, meta *ocm.SourceMeta, url string, opts ...Option) cpi.SourceAccess { - return Access(ctx, meta, url, opts...) -} diff --git a/pkg/contexts/ocm/elements/references.go b/pkg/contexts/ocm/elements/references.go deleted file mode 100644 index e5f40ebce..000000000 --- a/pkg/contexts/ocm/elements/references.go +++ /dev/null @@ -1,22 +0,0 @@ -package elements - -import ( - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" -) - -type ReferenceOption interface { - ApplyToReference(reference *compdesc.ComponentReference) error -} - -func Reference(name, comp, vers string, opts ...ReferenceOption) (*compdesc.ComponentReference, error) { - m := compdesc.NewComponentReference(name, comp, vers, nil) - list := errors.ErrList() - for _, o := range opts { - if o != nil { - list.Add(o.ApplyToReference(m)) - } - } - return m, list.Result() -} diff --git a/pkg/contexts/ocm/elements/resources.go b/pkg/contexts/ocm/elements/resources.go deleted file mode 100644 index a0dda588a..000000000 --- a/pkg/contexts/ocm/elements/resources.go +++ /dev/null @@ -1,78 +0,0 @@ -package elements - -import ( - "github.com/mandelsoft/goutils/errors" - - "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/utils" -) - -type ResourceMetaOption interface { - ApplyToResourceMeta(*compdesc.ResourceMeta) error -} - -func ResourceMeta(name, typ string, opts ...ResourceMetaOption) (*compdesc.ResourceMeta, error) { - m := compdesc.NewResourceMeta(name, typ, metav1.LocalRelation) - list := errors.ErrList() - for _, o := range opts { - if o != nil { - list.Add(o.ApplyToResourceMeta(m)) - } - } - return m, list.Result() -} - -//////////////////////////////////////////////////////////////////////////////// - -type local bool - -func (o local) ApplyToResourceMeta(m *compdesc.ResourceMeta) error { - if o { - m.Relation = metav1.LocalRelation - } else { - m.Relation = metav1.ExternalRelation - } - return nil -} - -// WithLocalRelation sets the resource relation to metav1.LocalRelation. -func WithLocalRelation(flag ...bool) ResourceMetaOption { - return local(utils.OptionalDefaultedBool(true, flag...)) -} - -// WithExternalRelation sets the resource relation to metav1.ExternalRelation. -func WithExternalRelation(flag ...bool) ResourceMetaOption { - return local(!utils.OptionalDefaultedBool(true, flag...)) -} - -//////////////////////////////////////////////////////////////////////////////// - -type srcref struct { - ref metav1.StringMap - labels metav1.Labels - errlist errors.ErrorList -} - -var _ ResourceMetaOption = (*srcref)(nil) - -func (o *srcref) ApplyToResourceMeta(m *compdesc.ResourceMeta) error { - if err := o.errlist.Result(); err != nil { - return err - } - m.SourceRefs = append(m.SourceRefs, compdesc.SourceRef{IdentitySelector: o.ref.Copy(), Labels: o.labels.Copy()}) - return nil -} - -func (o *srcref) WithLabel(name string, value interface{}, opts ...metav1.LabelOption) *srcref { - r := &srcref{ref: o.ref, labels: o.labels.Copy()} - r.errlist.Add(r.labels.Set(name, value, opts...)) - return r -} - -// WithSourceRef adds a source reference to a resource meta object. -// this is a sequence of name/value pairs. -// Optionally, additional labels can be added with srcref.WithLabel. -func WithSourceRef(sel ...string) *srcref { - return &srcref{ref: metav1.StringMap(metav1.NewExtraIdentity(sel...))} -} diff --git a/pkg/contexts/ocm/gc_test.go b/pkg/contexts/ocm/gc_test.go deleted file mode 100644 index a4ad5f81a..000000000 --- a/pkg/contexts/ocm/gc_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package ocm_test - -import ( - "runtime" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - me "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/runtimefinalizer" -) - -var _ = Describe("area test", func() { - It("can be garbage collected", func() { - ctx := me.New() - - r := runtimefinalizer.GetRuntimeFinalizationRecorder(ctx) - Expect(r).NotTo(BeNil()) - - runtime.GC() - time.Sleep(time.Second) - ctx.GetType() - Expect(r.Get()).To(BeNil()) - - ctx = nil - for i := 0; i < 100; i++ { - runtime.GC() - time.Sleep(time.Millisecond) - } - - Expect(r.Get()).To(ContainElement(ContainSubstring(me.CONTEXT_TYPE))) - }) -}) diff --git a/pkg/contexts/ocm/init.go b/pkg/contexts/ocm/init.go deleted file mode 100644 index 2b08aef16..000000000 --- a/pkg/contexts/ocm/init.go +++ /dev/null @@ -1,19 +0,0 @@ -package ocm - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs" - _ "github.com/open-component-model/ocm/pkg/contexts/oci" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/config" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/normalizations" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/versions" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/config" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/digester/digesters" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/download/config" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/download/handlers" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub/providers" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub/types" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/handlers" -) diff --git a/pkg/contexts/ocm/interface.go b/pkg/contexts/ocm/interface.go deleted file mode 100644 index 61afccdd5..000000000 --- a/pkg/contexts/ocm/interface.go +++ /dev/null @@ -1,176 +0,0 @@ -package ocm - -import ( - "context" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "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/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/repocpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" - "github.com/open-component-model/ocm/pkg/runtime" -) - -// ErrTempVersion indicates an ignored update in the backend because the -// current version has not yet been added to the repository. -var ErrTempVersion = repocpi.ErrTempVersion - -const ( - KIND_COMPONENT = internal.KIND_COMPONENT - KIND_COMPONENTVERSION = internal.KIND_COMPONENTVERSION - KIND_COMPONENTREFERENCE = "component reference" - KIND_RESOURCE = internal.KIND_RESOURCE - KIND_SOURCE = internal.KIND_SOURCE - KIND_REFERENCE = internal.KIND_REFERENCE - KIND_REPOSITORYSPEC = internal.KIND_REPOSITORYSPEC -) - -const CONTEXT_TYPE = internal.CONTEXT_TYPE - -const CommonTransportFormat = internal.CommonTransportFormat - -type ( - Context = internal.Context - ContextProvider = internal.ContextProvider - LocalContextProvider = internal.LocalContextProvider - ComponentVersionResolver = internal.ComponentVersionResolver - Repository = internal.Repository - RepositorySpecHandlers = internal.RepositorySpecHandlers - RepositorySpecHandler = internal.RepositorySpecHandler - UniformRepositorySpec = internal.UniformRepositorySpec - ComponentLister = internal.ComponentLister - ComponentAccess = internal.ComponentAccess - ComponentVersionAccess = internal.ComponentVersionAccess - AccessSpec = internal.AccessSpec - GenericAccessSpec = internal.GenericAccessSpec - HintProvider = internal.HintProvider - AccessMethod = internal.AccessMethod - AccessType = internal.AccessType - DataAccess = internal.DataAccess - BlobAccess = internal.BlobAccess - AccessProvider = internal.AccessProvider - SourceAccess = internal.SourceAccess - SourceMeta = internal.SourceMeta - ResourceAccess = internal.ResourceAccess - ResourceMeta = internal.ResourceMeta - RepositorySpec = internal.RepositorySpec - GenericRepositorySpec = internal.GenericRepositorySpec - IntermediateRepositorySpecAspect = internal.IntermediateRepositorySpecAspect - RepositoryType = internal.RepositoryType - RepositoryTypeScheme = internal.RepositoryTypeScheme - RepositoryDelegationRegistry = internal.RepositoryDelegationRegistry - AccessTypeScheme = internal.AccessTypeScheme - ComponentReference = internal.ComponentReference -) - -type ( - DigesterType = internal.DigesterType - BlobDigester = internal.BlobDigester - BlobDigesterRegistry = internal.BlobDigesterRegistry - DigestDescriptor = internal.DigestDescriptor - HasherProvider = internal.HasherProvider - Hasher = internal.Hasher -) - -type ( - BlobHandlerRegistry = internal.BlobHandlerRegistry - BlobHandler = internal.BlobHandler - BlobHandlerProvider = internal.BlobHandlerProvider -) - -func NewDigestDescriptor(digest, hashAlgo, normAlgo string) *DigestDescriptor { - return internal.NewDigestDescriptor(digest, hashAlgo, normAlgo) -} - -// DefaultContext is the default context initialized by init functions. -func DefaultContext() internal.Context { - return internal.DefaultContext -} - -// NoComponentVersion provides a dummy component version -// providing access to the context. -// It can be used to instantiate external access methods -// (not based on any component version). -func NoComponentVersion(ctx ContextProvider) ComponentVersionAccess { - return &cpi.DummyComponentVersionAccess{ctx.OCMContext()} -} - -func DefaultBlobHandlers() BlobHandlerRegistry { - return internal.DefaultBlobHandlerRegistry -} - -func DefaultBlobHandlerProvider(ctx Context) BlobHandlerProvider { - return internal.DefaultBlobHandlerProvider(ctx) -} - -func DefaultRepositoryDelegationRegistry() RepositoryDelegationRegistry { - return internal.DefaultRepositoryDelegationRegistry -} - -func NewRepositoryDelegationRegistry(base ...RepositoryDelegationRegistry) RepositoryDelegationRegistry { - return internal.NewDelegationRegistry[Context, RepositorySpec](base...) -} - -// FromContext returns the Context to use for context.Context. -// This is either an explicit context or the default context. -func FromContext(ctx context.Context) Context { - return internal.FromContext(ctx) -} - -func FromProvider(p ContextProvider) Context { - return internal.FromProvider(p) -} - -func DefinedForContext(ctx context.Context) (Context, bool) { - return internal.DefinedForContext(ctx) -} - -func NewGenericAccessSpec(spec string) (AccessSpec, error) { - return internal.NewGenericAccessSpec([]byte(spec)) -} - -func ToGenericAccessSpec(spec AccessSpec) (*GenericAccessSpec, error) { - return internal.ToGenericAccessSpec(spec) -} - -func ToGenericRepositorySpec(spec RepositorySpec) (*GenericRepositorySpec, error) { - return internal.ToGenericRepositorySpec(spec) -} - -func IsNoneAccess(a compdesc.AccessSpec) bool { - return compdesc.IsNoneAccess(a) -} - -func IsNoneAccessKind(k string) bool { - return compdesc.IsNoneAccessKind(k) -} - -type AccessSpecRef = internal.AccessSpecRef - -func NewAccessSpecRef(spec cpi.AccessSpec) *AccessSpecRef { - return internal.NewAccessSpecRef(spec) -} - -func NewRawAccessSpecRef(data []byte, unmarshaler runtime.Unmarshaler) (*AccessSpecRef, error) { - return internal.NewRawAccessSpecRef(data, unmarshaler) -} - -func NewResourceMeta(name string, typ string, relation metav1.ResourceRelation) *ResourceMeta { - return compdesc.NewResourceMeta(name, typ, relation) -} - -func NewSourceMeta(name string, typ string) *SourceMeta { - return compdesc.NewSourceMeta(name, typ) -} - -func NewComponentReference(name, componentName, version string) *ComponentReference { - return compdesc.NewComponentReference(name, componentName, version, nil) -} - -/////////////////////////////////////////////////////// - -func BlobAccessForAccessMethod(m AccessMethod) (blobaccess.AnnotatedBlobAccess[accspeccpi.AccessMethodView], error) { - return accspeccpi.BlobAccessForAccessMethod(m) -} diff --git a/pkg/contexts/ocm/internal/accesstypes.go b/pkg/contexts/ocm/internal/accesstypes.go deleted file mode 100644 index 1d024e78d..000000000 --- a/pkg/contexts/ocm/internal/accesstypes.go +++ /dev/null @@ -1,244 +0,0 @@ -package internal - -import ( - "encoding/json" - "fmt" - "io" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/general" - "github.com/mandelsoft/goutils/generics" - "github.com/modern-go/reflect2" - - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets/flagsetscheme" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - "github.com/open-component-model/ocm/pkg/errkind" - "github.com/open-component-model/ocm/pkg/refmgmt" - "github.com/open-component-model/ocm/pkg/runtime" -) - -type AccessType flagsetscheme.VersionTypedObjectType[AccessSpec] - -// AccessSpec is the interface access method specifications -// must fulfill. The main task is to map the specification -// to a concrete implementation of the access method for a dedicated -// component version. -type AccessSpec interface { - compdesc.AccessSpec - Describe(Context) string - IsLocal(Context) bool - GlobalAccessSpec(Context) AccessSpec - // AccessMethod provides an access method implementation for - // an access spec. This might be a repository local implementation - // or a global one. It might be implemented directly by the AccessSpec - // for global AccessMethods or forwarded to the ComponentVersion for - // local access methods. It may only be forwarded for AccessSpecs stating - // to be local (IsLocal()==true). - // This forwarding is necessary because the concrete implementation of - // the currently used OCM Repository is not known to the AccessSpec. - AccessMethod(access ComponentVersionAccess) (AccessMethod, error) -} - -type ( - AccessSpecDecoder = runtime.TypedObjectDecoder[AccessSpec] - AccessTypeProvider = runtime.KnownTypesProvider[AccessSpec, AccessType] -) - -// HintProvider is used to provide a reference hint for local access method specs. -// It may optionally be provided by an access spec. -// When adding blobs to a repository the hint is used by blobhandlers for -// expanding a blob to a repository specific representation to determine a -// useful name. -type HintProvider interface { - GetReferenceHint(cv ComponentVersionAccess) string -} - -// GlobalAccessProvider is used to provide a non-local access specification. -// It may optionally be provided by an access spec. -type GlobalAccessProvider interface { - GlobalAccessSpec(ctx Context) AccessSpec -} - -// AccessMethodImpl is the implementation interface -// for access methods provided by access types. It describes -// the access to a dedicated resource -// It can allocate external resources, which should be released -// with the Close() call. -// Resources SHOULD only be allocated, if the content is accessed -// via the DataAccess interface to avoid unnecessary effort -// if the method object is just used to access meta data. -// It is always wrapped by a view model enabling Dup -// operations to pass and keep instances on demand. -type AccessMethodImpl interface { - io.Closer - DataAccess - MimeType - - IsLocal() bool - GetKind() string - AccessSpec() AccessSpec -} - -// AccessMethod is used to support independently closable -// views on an access method implementation, which can -// be passed around and stored. The original method implementation -// object is closed once the last view is closed. -type AccessMethod interface { - refmgmt.Dup[AccessMethod] - AccessMethodImpl - - // AsBlobAccess maps a method object into a - // basic blob access interface. - // It does not provide a separate reference, - // closing the blob access with close the - // access method. - AsBlobAccess() BlobAccess -} - -type AccessTypeScheme flagsetscheme.TypeScheme[AccessSpec, AccessType] - -func NewAccessTypeScheme(base ...AccessTypeScheme) AccessTypeScheme { - return flagsetscheme.NewTypeScheme[AccessSpec, AccessType, AccessTypeScheme]("Access type", "access", "accessType", "blob access specification", "Access Specification Options", &UnknownAccessSpec{}, true, base...) -} - -func NewStrictAccessTypeScheme(base ...AccessTypeScheme) runtime.VersionedTypeRegistry[AccessSpec, AccessType] { - return flagsetscheme.NewTypeScheme[AccessSpec, AccessType, AccessTypeScheme]("Access type", "access", "accessType", "blob access specification", "Access Specification Options", nil, false, base...) -} - -// DefaultAccessTypeScheme contains all globally known access serializer. -var DefaultAccessTypeScheme = NewAccessTypeScheme() - -func RegisterAccessType(atype AccessType) { - DefaultAccessTypeScheme.Register(atype) -} - -func CreateAccessSpec(t runtime.TypedObject) (AccessSpec, error) { - return DefaultAccessTypeScheme.Convert(t) -} - -//////////////////////////////////////////////////////////////////////////////// - -type UnknownAccessSpec struct { - runtime.UnstructuredVersionedTypedObject `json:",inline"` -} - -var ( - _ runtime.TypedObject = &UnknownAccessSpec{} - _ runtime.Unknown = &UnknownAccessSpec{} -) - -func (_ *UnknownAccessSpec) IsUnknown() bool { - return true -} - -func (s *UnknownAccessSpec) AccessMethod(ComponentVersionAccess) (AccessMethod, error) { - return nil, errors.ErrUnknown(errkind.KIND_ACCESSMETHOD, s.GetType()) -} - -func (s *UnknownAccessSpec) Describe(ctx Context) string { - return fmt.Sprintf("unknown access method type %q", s.GetType()) -} - -func (_ *UnknownAccessSpec) IsLocal(Context) bool { - return false -} - -func (_ *UnknownAccessSpec) GlobalAccessSpec(Context) AccessSpec { - return nil -} - -var _ AccessSpec = &UnknownAccessSpec{} - -//////////////////////////////////////////////////////////////////////////////// - -type EvaluatableAccessSpec interface { - AccessSpec - Evaluate(ctx Context) (AccessSpec, error) -} - -type GenericAccessSpec struct { - runtime.UnstructuredVersionedTypedObject `json:",inline"` -} - -var _ AccessSpec = &GenericAccessSpec{} - -func ToGenericAccessSpec(spec AccessSpec) (*GenericAccessSpec, error) { - if reflect2.IsNil(spec) { - return nil, nil - } - if g, ok := spec.(*GenericAccessSpec); ok { - return g, nil - } - data, err := json.Marshal(spec) - if err != nil { - return nil, err - } - return newGenericAccessSpec(data, runtime.DefaultJSONEncoding) -} - -func NewGenericAccessSpec(data []byte, unmarshaler ...runtime.Unmarshaler) (AccessSpec, error) { - return generics.CastPointerR[AccessSpec](newGenericAccessSpec(data, general.Optional(unmarshaler...))) -} - -func newGenericAccessSpec(data []byte, unmarshaler runtime.Unmarshaler) (*GenericAccessSpec, error) { - unstr := &runtime.UnstructuredVersionedTypedObject{} - if unmarshaler == nil { - unmarshaler = runtime.DefaultYAMLEncoding - } - err := unmarshaler.Unmarshal(data, unstr) - if err != nil { - return nil, err - } - return &GenericAccessSpec{*unstr}, nil -} - -func (s *GenericAccessSpec) Describe(ctx Context) string { - eff, err := s.Evaluate(ctx) - if err != nil { - return fmt.Sprintf("invalid access specification: %s", err.Error()) - } - return eff.Describe(ctx) -} - -func (s *GenericAccessSpec) Evaluate(ctx Context) (AccessSpec, error) { - raw, err := s.GetRaw() - if err != nil { - return nil, err - } - return ctx.AccessMethods().Decode(raw, runtime.DefaultJSONEncoding) -} - -func (s *GenericAccessSpec) AccessMethod(acc ComponentVersionAccess) (AccessMethod, error) { - spec, err := s.Evaluate(acc.GetContext()) - if err != nil { - return nil, err - } - if _, ok := spec.(*GenericAccessSpec); ok { - return nil, errors.ErrUnknown(errkind.KIND_ACCESSMETHOD, s.GetType()) - } - return spec.AccessMethod(acc) -} - -func (s *GenericAccessSpec) IsLocal(ctx Context) bool { - spec, err := s.Evaluate(ctx) - if err != nil { - return false - } - if _, ok := spec.(*GenericAccessSpec); ok { - return false - } - return spec.IsLocal(ctx) -} - -func (s *GenericAccessSpec) GlobalAccessSpec(ctx Context) AccessSpec { - spec, err := s.Evaluate(ctx) - if err != nil { - return nil - } - if _, ok := spec.(*GenericAccessSpec); ok { - return nil - } - return spec.GlobalAccessSpec(ctx) -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/pkg/contexts/ocm/internal/blobhandler.go b/pkg/contexts/ocm/internal/blobhandler.go deleted file mode 100644 index e12aed3eb..000000000 --- a/pkg/contexts/ocm/internal/blobhandler.go +++ /dev/null @@ -1,472 +0,0 @@ -package internal - -import ( - "fmt" - "sort" - "strings" - "sync" - - "github.com/open-component-model/ocm/pkg/registrations" - "github.com/open-component-model/ocm/pkg/utils" -) - -type ImplementationRepositoryType struct { - ContextType string `json:"contextType,omitempty"` - RepositoryType string `json:"repositoryType,omitempty"` -} - -func (t ImplementationRepositoryType) String() string { - return fmt.Sprintf("%s[%s]", t.RepositoryType, t.ContextType) -} - -func (t ImplementationRepositoryType) IsInitial() bool { - return t.RepositoryType == "" && t.ContextType == "" -} - -// StorageContext is an object describing the storage context used for the -// mapping of a component repository to a base repository (e.g. oci api) -// It depends on the Context type of the used base repository. -type StorageContext interface { - GetContext() Context - TargetComponentName() string - TargetComponentRepository() Repository - GetImplementationRepositoryType() ImplementationRepositoryType -} - -// BlobHandler is the interface for a dedicated handling of storing blobs -// for the LocalBlob access method in a dedicated kind of repository. -// With the possibility of access by an external distribution spec -// (besides of the blob storage as part of a component version). -// The technical repository to use should be derivable from the chosen -// component directory or passed together with the storage context. -// The task of the handler is to store the local blob on its own -// responsibility and to return an appropriate global access method. -type BlobHandler interface { - // StoreBlob has the chance to decide to store a local blob - // in a repository specific fashion providing external access. - // If this is possible and done an appropriate access spec - // must be returned, if this is not done, nil has to be returned - // without error - StoreBlob(blob BlobAccess, artType, hint string, global AccessSpec, ctx StorageContext) (AccessSpec, error) -} - -// MultiBlobHandler is a BlobHandler consisting of a sequence of handlers. -type MultiBlobHandler []BlobHandler - -var _ sort.Interface = MultiBlobHandler(nil) - -func (m MultiBlobHandler) StoreBlob(blob BlobAccess, artType, hint string, global AccessSpec, ctx StorageContext) (AccessSpec, error) { - for _, h := range m { - a, err := h.StoreBlob(blob, artType, hint, global, ctx) - if err != nil { - return nil, err - } - if a != nil { - return a, nil - } - } - return nil, nil -} - -func (m MultiBlobHandler) Len() int { - return len(m) -} - -func (m MultiBlobHandler) Less(i, j int) bool { - pi := DEFAULT_BLOBHANDLER_PRIO - pj := DEFAULT_BLOBHANDLER_PRIO - - if p, ok := m[i].(*PrioBlobHandler); ok { - pi = p.Prio - } - if p, ok := m[j].(*PrioBlobHandler); ok { - pj = p.Prio - } - return pi > pj -} - -func (m MultiBlobHandler) Swap(i, j int) { - m[i], m[j] = m[j], m[i] -} - -//////////////////////////////////////////////////////////////////////////////// - -type BlobHandlerOptions struct { - BlobHandlerKey `json:",inline"` - Priority int `json:"priority,omitempty"` -} - -func NewBlobHandlerOptions(olist ...BlobHandlerOption) *BlobHandlerOptions { - var opts BlobHandlerOptions - for _, o := range olist { - o.ApplyBlobHandlerOptionTo(&opts) - } - return &opts -} - -func (o *BlobHandlerOptions) ApplyBlobHandlerOptionTo(opts *BlobHandlerOptions) { - if o.Priority > 0 { - opts.Priority = o.Priority - } - o.BlobHandlerKey.ApplyBlobHandlerOptionTo(opts) -} - -type BlobHandlerOption interface { - ApplyBlobHandlerOptionTo(*BlobHandlerOptions) -} - -type prio struct { - prio int -} - -func WithPrio(p int) BlobHandlerOption { - return prio{p} -} - -func (o prio) ApplyBlobHandlerOptionTo(opts *BlobHandlerOptions) { - opts.Priority = o.prio -} - -//////////////////////////////////////////////////////////////////////////////// - -// BlobHandlerKey is the registration key for BlobHandlers. -type BlobHandlerKey struct { - ImplementationRepositoryType `json:",inline"` - ArtifactType string `json:"artifactType,omitempty"` - MimeType string `json:"mimeType,omitempty"` -} - -var _ BlobHandlerOption = BlobHandlerKey{} - -func NewBlobHandlerKey(ctxtype, repotype, artifactType, mimetype string) BlobHandlerKey { - return BlobHandlerKey{ - ImplementationRepositoryType: ImplementationRepositoryType{ - ContextType: ctxtype, - RepositoryType: repotype, - }, - ArtifactType: artifactType, - MimeType: mimetype, - } -} - -func (k BlobHandlerKey) ApplyBlobHandlerOptionTo(opts *BlobHandlerOptions) { - if k.ContextType != "" { - opts.ContextType = k.ContextType - } - if k.RepositoryType != "" { - opts.RepositoryType = k.RepositoryType - } - if k.ArtifactType != "" { - opts.ArtifactType = k.ArtifactType - } - if k.MimeType != "" { - opts.MimeType = k.MimeType - } -} - -func ForRepo(ctxtype, repotype string) BlobHandlerOption { - return BlobHandlerKey{ImplementationRepositoryType: ImplementationRepositoryType{ContextType: ctxtype, RepositoryType: repotype}} -} - -func ForMimeType(mimetype string) BlobHandlerOption { - return BlobHandlerKey{MimeType: mimetype} -} - -func ForArtifactType(artifacttype string) BlobHandlerOption { - return BlobHandlerKey{ArtifactType: artifacttype} -} - -//////////////////////////////////////////////////////////////////////////////// - -type ( - BlobHandlerConfig = registrations.HandlerConfig - BlobHandlerRegistrationHandler = registrations.HandlerRegistrationHandler[Context, BlobHandlerOption] - BlobHandlerRegistrationRegistry = registrations.HandlerRegistrationRegistry[Context, BlobHandlerOption] - - RegistrationHandlerInfo = registrations.RegistrationHandlerInfo[Context, BlobHandlerOption] -) - -func NewBlobHandlerRegistrationRegistry(base ...BlobHandlerRegistrationRegistry) BlobHandlerRegistrationRegistry { - return registrations.NewHandlerRegistrationRegistry[Context, BlobHandlerOption](base...) -} - -func NewRegistrationHandlerInfo(path string, handler BlobHandlerRegistrationHandler) *RegistrationHandlerInfo { - return registrations.NewRegistrationHandlerInfo[Context, BlobHandlerOption](path, handler) -} - -//////////////////////////////////////////////////////////////////////////////// - -// BlobHandlerProvider is used to find a blob handler to use adding a resource -// blob to a component version. -// The task of the BlobHandler is to reject the upload, provide an (external) -// access method for the blob or state not to be responsible. -type BlobHandlerProvider interface { - LookupHandler(sctx StorageContext, artifacttype, mimeType string) BlobHandler -} - -type registryBasedProvider struct { - registry BlobHandlerRegistry -} - -func (p *registryBasedProvider) LookupHandler(sctx StorageContext, artifacttype, mimeType string) BlobHandler { - return p.registry.LookupHandler(sctx.GetImplementationRepositoryType(), artifacttype, mimeType) -} - -func BlobHandlerProviderForRegistry(r BlobHandlerRegistry) BlobHandlerProvider { - return ®istryBasedProvider{r} -} - -func DefaultBlobHandlerProvider(ctx Context) BlobHandlerProvider { - return BlobHandlerProviderForRegistry(ctx.BlobHandlers()) -} - -//////////////////////////////////////////////////////////////////////////////// - -// BlobHandlerRegistry registers blob handlers to use in a dedicated ocm context. -type BlobHandlerRegistry interface { - AsHandlerRegistrationRegistry() registrations.HandlerRegistrationRegistry[Context, BlobHandlerOption] - - // BlobHandlerRegistrationRegistry - registrations.HandlerRegistrationRegistryAccess[Context, BlobHandlerOption] - - IsInitial() bool - - // Copy provides a new independend copy of the registry. - Copy() BlobHandlerRegistry - // RegisterBlobHandler registers a blob handler. It must specify either a sole mime type, - // or a context and repository type, or all three keys. - Register(handler BlobHandler, opts ...BlobHandlerOption) BlobHandlerRegistry - - // GetHandler returns the handler with the given key. - GetHandler(key BlobHandlerKey) BlobHandler - - // LookupHandler returns handler trying all matches in the following order: - // - // - a handler matching all keys - // - handlers matching the repo and mime type (from specific to more general by discarding + components) - // - with artifact type - // - without artifact type - // - handlers matching artifact type - // - handlers matching a sole mimetype handler (from specific to more general by discarding + components) - // - a handler matching the repo - // - LookupHandler(repotype ImplementationRepositoryType, artifacttype, mimeType string) BlobHandler -} - -func AsHandlerRegistrationRegistry(r BlobHandlerRegistry) registrations.HandlerRegistrationRegistry[Context, BlobHandlerOption] { - if r == nil { - return nil - } - return r.AsHandlerRegistrationRegistry() -} - -const DEFAULT_BLOBHANDLER_PRIO = 100 - -type PrioBlobHandler struct { - BlobHandler - Prio int -} - -type handlerCache struct { - cache map[BlobHandlerKey]BlobHandler -} - -func newHandlerCache() *handlerCache { - return &handlerCache{map[BlobHandlerKey]BlobHandler{}} -} - -func (c *handlerCache) len() int { - return len(c.cache) -} - -func (c *handlerCache) get(key BlobHandlerKey) (BlobHandler, bool) { - h, ok := c.cache[key] - return h, ok -} - -func (c *handlerCache) set(key BlobHandlerKey, h BlobHandler) { - c.cache[key] = h -} - -type blobHandlerRegistry struct { - lock sync.RWMutex - base BlobHandlerRegistry - handlers map[BlobHandlerKey]BlobHandler - defhandler MultiBlobHandler - - // (should be) BlobHandlerRegistrationRegistry , but does not work with GoLand up to at least 2022.2.6 - registrations.HandlerRegistrationRegistry[Context, BlobHandlerOption] - - cache *handlerCache -} - -var DefaultBlobHandlerRegistry = NewBlobHandlerRegistry() - -func NewBlobHandlerRegistry(base ...BlobHandlerRegistry) BlobHandlerRegistry { - b := utils.Optional(base...) - r := &blobHandlerRegistry{ - base: b, - handlers: map[BlobHandlerKey]BlobHandler{}, - HandlerRegistrationRegistry: NewBlobHandlerRegistrationRegistry(AsHandlerRegistrationRegistry(b)), - cache: newHandlerCache(), - } - return r -} - -func (r *blobHandlerRegistry) AsHandlerRegistrationRegistry() registrations.HandlerRegistrationRegistry[Context, BlobHandlerOption] { - return r.HandlerRegistrationRegistry -} - -func (r *blobHandlerRegistry) Copy() BlobHandlerRegistry { - r.lock.RLock() - defer r.lock.RUnlock() - n := NewBlobHandlerRegistry(r.base).(*blobHandlerRegistry) - n.defhandler = append(n.defhandler, r.defhandler...) - for k, h := range r.handlers { - n.handlers[k] = h - } - return n -} - -func (r *blobHandlerRegistry) IsInitial() bool { - if r.base != nil && !r.base.IsInitial() { - return false - } - return len(r.handlers) == 0 && len(r.defhandler) == 0 -} - -func (r *blobHandlerRegistry) Register(handler BlobHandler, olist ...BlobHandlerOption) BlobHandlerRegistry { - opts := NewBlobHandlerOptions(olist...) - r.lock.Lock() - defer r.lock.Unlock() - - def := BlobHandlerKey{} - - if opts.Priority != 0 { - handler = &PrioBlobHandler{handler, opts.Priority} - } - if opts.BlobHandlerKey == def { - r.defhandler = append(r.defhandler, handler) - } else { - r.handlers[opts.BlobHandlerKey] = handler - } - if r.cache.len() > 0 { - r.cache = newHandlerCache() - } - return r -} - -func (r *blobHandlerRegistry) forMimeType(ctxtype, repotype, artifacttype, mimetype string) MultiBlobHandler { - var multi MultiBlobHandler - - mime := mimetype - for { - if h := r.getHandler(NewBlobHandlerKey(ctxtype, repotype, artifacttype, mime)); h != nil { - multi = append(multi, h) - } - idx := strings.LastIndex(mime, "+") - if idx < 0 { - break - } - mime = mime[:idx] - } - return multi -} - -func (r *blobHandlerRegistry) GetHandler(key BlobHandlerKey) BlobHandler { - r.lock.RLock() - defer r.lock.RUnlock() - return r.getHandler(key) -} - -func (r *blobHandlerRegistry) getHandler(key BlobHandlerKey) BlobHandler { - def := BlobHandlerKey{} - - if key == def { - if len(r.defhandler) > 0 { - return r.defhandler - } - } - h := r.handlers[key] - if h != nil { - return h - } - if r.base != nil { - return r.base.GetHandler(key) - } - return nil -} - -func (r *blobHandlerRegistry) LookupHandler(repotype ImplementationRepositoryType, artifacttype, mimetype string) BlobHandler { - key := BlobHandlerKey{ - ImplementationRepositoryType: repotype, - ArtifactType: artifacttype, - MimeType: mimetype, - } - h, cache := r.lookupHandler(key) - if cache != nil { - r.lock.Lock() - defer r.lock.Unlock() - // fill cache, if unchanged during pseudo lock upgrade (no support in go sync package for that). - // if cache has been renewed in the meantime, just use the old outdated result, but don't update. - if r.cache == cache { - r.cache.set(key, h) - } - } - return h -} - -func (r *blobHandlerRegistry) lookupHandler(key BlobHandlerKey) (BlobHandler, *handlerCache) { - r.lock.RLock() - defer r.lock.RUnlock() - - if h, ok := r.cache.get(key); ok { - return h, nil - } - var multi MultiBlobHandler - if !key.ImplementationRepositoryType.IsInitial() { - multi = append(multi, r.forMimeType(key.ContextType, key.RepositoryType, key.ArtifactType, key.MimeType)...) - if key.MimeType != "" { - multi = append(multi, r.forMimeType(key.ContextType, key.RepositoryType, key.ArtifactType, "")...) - } - if key.ArtifactType != "" { - multi = append(multi, r.forMimeType(key.ContextType, key.RepositoryType, "", key.MimeType)...) - } - } - multi = append(multi, r.forMimeType("", "", key.ArtifactType, key.MimeType)...) - if key.MimeType != "" { - multi = append(multi, r.forMimeType("", "", key.ArtifactType, "")...) - } - if key.ArtifactType != "" { - multi = append(multi, r.forMimeType("", "", "", key.MimeType)...) - } - if !key.ImplementationRepositoryType.IsInitial() && key.ArtifactType != "" && key.MimeType != "" { - multi = append(multi, r.forMimeType(key.ContextType, key.RepositoryType, "", "")...) - } - - def := r.getHandler(BlobHandlerKey{}) - if def != nil { - if m, ok := def.(MultiBlobHandler); ok { - multi = append(multi, m...) - } else { - multi = append(multi, def) - } - } - if len(multi) == 0 { - return nil, r.cache - } - sort.Stable(multi) - return multi, r.cache -} - -func RegisterBlobHandler(handler BlobHandler, opts ...BlobHandlerOption) { - DefaultBlobHandlerRegistry.Register(handler, opts...) -} - -func MustRegisterBlobHandler(handler BlobHandler, opts ...BlobHandlerOption) { - DefaultBlobHandlerRegistry.Register(handler, opts...) -} - -func RegisterBlobHandlerRegistrationHandler(path string, handler BlobHandlerRegistrationHandler) { - DefaultBlobHandlerRegistry.RegisterRegistrationHandler(path, handler) -} diff --git a/pkg/contexts/ocm/internal/blobhandler_test.go b/pkg/contexts/ocm/internal/blobhandler_test.go deleted file mode 100644 index be007510b..000000000 --- a/pkg/contexts/ocm/internal/blobhandler_test.go +++ /dev/null @@ -1,244 +0,0 @@ -package internal_test - -import ( - "fmt" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/registrations" -) - -const REPO = "repo" - -var ( - IMPL = internal.ImplementationRepositoryType{internal.CONTEXT_TYPE, REPO} - ART = "myType" -) - -type BlobHandler struct { - name string -} - -var _ internal.BlobHandler = (*BlobHandler)(nil) - -func (b BlobHandler) StoreBlob(blob internal.BlobAccess, artType string, hint string, global internal.AccessSpec, ctx internal.StorageContext) (internal.AccessSpec, error) { - return nil, fmt.Errorf(b.name) -} - -type TestRegistrationHandler struct { - name string - registered map[string]interface{} -} - -func NewTestRegistrationHandler(name string) *TestRegistrationHandler { - return &TestRegistrationHandler{ - name: name, - registered: map[string]interface{}{}, - } -} - -func (t *TestRegistrationHandler) RegisterByName(handler string, ctx internal.Context, config internal.BlobHandlerConfig, opts ...internal.BlobHandlerOption) (bool, error) { - path := registrations.NewNamePath(handler) - if len(path) < 1 || path[0] != "match" { - return false, nil - } - t.registered[handler] = nil - return true, nil -} - -func (t *TestRegistrationHandler) GetHandlers(ctx internal.Context) registrations.HandlerInfos { - return nil -} - -var _ = Describe("blob handler registry test", func() { - Context("registration registry", func() { - var reg internal.BlobHandlerRegistrationRegistry - - var ha *TestRegistrationHandler - var hab *TestRegistrationHandler - var habc *TestRegistrationHandler - var habd *TestRegistrationHandler - var habe *TestRegistrationHandler - var hb *TestRegistrationHandler - - BeforeEach(func() { - reg = internal.NewBlobHandlerRegistrationRegistry() - ha = NewTestRegistrationHandler("a") - hab = NewTestRegistrationHandler("a/b") - habc = NewTestRegistrationHandler("a/b/c") - habd = NewTestRegistrationHandler("a/b/d") - habe = NewTestRegistrationHandler("a/b/e") - hb = NewTestRegistrationHandler("b") - }) - - It("registers ordered prefixes", func() { - reg.RegisterRegistrationHandler("a", ha) - reg.RegisterRegistrationHandler("a/b/d", habd) - reg.RegisterRegistrationHandler("a/b/c", habc) - reg.RegisterRegistrationHandler("a/b/e", habe) - reg.RegisterRegistrationHandler("a/b", hab) - reg.RegisterRegistrationHandler("b", hb) - - Expect(reg.GetRegistrationHandlers("a/b/c")).To(Equal([]*internal.RegistrationHandlerInfo{ - internal.NewRegistrationHandlerInfo("a/b/c", habc), - internal.NewRegistrationHandlerInfo("a/b", hab), - internal.NewRegistrationHandlerInfo("a", ha), - })) - }) - - It("registers ordered prefixes", func() { - reg.RegisterRegistrationHandler("a", ha) - reg.RegisterRegistrationHandler("a/b/d", habd) - reg.RegisterRegistrationHandler("a/b/c", habc) - reg.RegisterRegistrationHandler("a/b/e", habe) - reg.RegisterRegistrationHandler("a/b", hab) - reg.RegisterRegistrationHandler("b", hb) - - _, err := reg.RegisterByName("a/b/c/d", nil, nil) - MustFailWithMessage(err, "no registration handler found for a/b/c/d") - Expect(Must(reg.RegisterByName("a/b/c/match/d", nil, nil))).To(BeTrue()) - Expect(Must(reg.RegisterByName("a/b/c/match", nil, nil))).To(BeTrue()) - - Expect(ha.registered).To(Equal(map[string]interface{}{})) - Expect(hb.registered).To(Equal(map[string]interface{}{})) - Expect(hab.registered).To(Equal(map[string]interface{}{})) - Expect(habd.registered).To(Equal(map[string]interface{}{})) - Expect(habe.registered).To(Equal(map[string]interface{}{})) - Expect(habc.registered).To(Equal(map[string]interface{}{"match/d": nil, "match": nil})) - }) - - It("registers ordered prefixes", func() { - reg.RegisterRegistrationHandler("a/b/d", habd) - reg.RegisterRegistrationHandler("a/b/c", habc) - reg.RegisterRegistrationHandler("a/b/e", habe) - reg.RegisterRegistrationHandler("a", ha) - - derived := internal.NewBlobHandlerRegistrationRegistry(reg) - derived.RegisterRegistrationHandler("a/b", hab) - derived.RegisterRegistrationHandler("b", hb) - - list := derived.GetRegistrationHandlers("a/b/e") - Expect(list).To(Equal([]*internal.RegistrationHandlerInfo{ - internal.NewRegistrationHandlerInfo("a/b/e", habe), - internal.NewRegistrationHandlerInfo("a/b", hab), - internal.NewRegistrationHandlerInfo("a", ha), - })) - - _, err := reg.RegisterByName("a/b/e/d", nil, nil) - MustFailWithMessage(err, "no registration handler found for a/b/e/d") - Expect(Must(reg.RegisterByName("a/b/e/match/d", nil, nil))).To(BeTrue()) - Expect(Must(reg.RegisterByName("a/b/e/match", nil, nil))).To(BeTrue()) - - Expect(ha.registered).To(Equal(map[string]interface{}{})) - Expect(hb.registered).To(Equal(map[string]interface{}{})) - Expect(hab.registered).To(Equal(map[string]interface{}{})) - Expect(habd.registered).To(Equal(map[string]interface{}{})) - Expect(habc.registered).To(Equal(map[string]interface{}{})) - Expect(habe.registered).To(Equal(map[string]interface{}{"match/d": nil, "match": nil})) - }) - }) - - //////////////////////////////////////////////////////////////////////////// - - Context("blob handler registry", func() { - var reg internal.BlobHandlerRegistry - var ext internal.BlobHandlerRegistry - - BeforeEach(func() { - reg = internal.NewBlobHandlerRegistry() - ext = internal.NewBlobHandlerRegistry(reg) - }) - - DescribeTable("priortizes complete specs", - func(eff *internal.BlobHandlerRegistry) { - reg.Register(&BlobHandler{"mine"}, internal.ForMimeType(mime.MIME_TEXT)) - reg.Register(&BlobHandler{"repo"}, internal.ForRepo(internal.CONTEXT_TYPE, REPO)) - reg.Register(&BlobHandler{"art"}, internal.ForArtifactType(ART)) - reg.Register(&BlobHandler{"all"}, internal.ForRepo(internal.CONTEXT_TYPE, REPO), internal.ForArtifactType(ART), internal.ForMimeType(mime.MIME_TEXT)) - reg.Register(&BlobHandler{"repomime"}, internal.ForRepo(internal.CONTEXT_TYPE, REPO), internal.ForMimeType(mime.MIME_TEXT)) - - h := (*eff).LookupHandler(IMPL, ART, mime.MIME_TEXT) - Expect(h).NotTo(BeNil()) - _, err := h.StoreBlob(nil, "", "", nil, nil) - Expect(err).To(MatchError(fmt.Errorf("all"))) - }, - Entry("plain", ®), - Entry("extended", &ext), - ) - - DescribeTable("priortizes mime", - func(eff *internal.BlobHandlerRegistry) { - reg.Register(&BlobHandler{"mine"}, internal.ForMimeType(mime.MIME_TEXT)) - reg.Register(&BlobHandler{"repo"}, internal.ForRepo(internal.CONTEXT_TYPE, REPO)) - reg.Register(&BlobHandler{"repomime"}, internal.ForRepo(internal.CONTEXT_TYPE, REPO), internal.ForMimeType(mime.MIME_TEXT)) - - h := (*eff).LookupHandler(IMPL, ART, mime.MIME_TEXT) - Expect(h).NotTo(BeNil()) - _, err := h.StoreBlob(nil, "", "", nil, nil) - Expect(err).To(MatchError(fmt.Errorf("repomime"))) - }, - Entry("plain", ®), - Entry("extended", &ext), - ) - - DescribeTable("priortizes mime", - func(eff *internal.BlobHandlerRegistry) { - reg.Register(&BlobHandler{"mine"}, internal.ForMimeType(mime.MIME_TEXT)) - reg.Register(&BlobHandler{"repo"}, internal.ForRepo(internal.CONTEXT_TYPE, REPO)) - reg.Register(&BlobHandler{"repomine"}, internal.ForRepo(internal.CONTEXT_TYPE, REPO), internal.ForMimeType(mime.MIME_TEXT)) - reg.Register(&BlobHandler{"repoart"}, internal.ForRepo(internal.CONTEXT_TYPE, REPO), internal.ForArtifactType(ART)) - - h := (*eff).LookupHandler(IMPL, ART, mime.MIME_TEXT) - Expect(h).NotTo(BeNil()) - _, err := h.StoreBlob(nil, "", "", nil, nil) - Expect(err).To(MatchError(fmt.Errorf("repoart"))) - }, - Entry("plain", ®), - Entry("extended", &ext), - ) - - DescribeTable("priortizes prio", - func(eff *internal.BlobHandlerRegistry) { - reg.Register(&BlobHandler{"mine"}, internal.ForMimeType(mime.MIME_TEXT)) - reg.Register(&BlobHandler{"repo"}, internal.ForRepo(internal.CONTEXT_TYPE, REPO)) - reg.Register(&BlobHandler{"all"}, internal.ForRepo(internal.CONTEXT_TYPE, REPO), internal.ForMimeType(mime.MIME_TEXT)) - reg.Register(&BlobHandler{"high"}, internal.WithPrio(internal.DEFAULT_BLOBHANDLER_PRIO+1)) - - h := (*eff).LookupHandler(IMPL, ART, mime.MIME_TEXT) - Expect(h).NotTo(BeNil()) - _, err := h.StoreBlob(nil, "", "", nil, nil) - Expect(err).To(MatchError(fmt.Errorf("high"))) - }, - Entry("plain", ®), - Entry("extended", &ext), - ) - - DescribeTable("copies registries", - func(eff *internal.BlobHandlerRegistry) { - mine := &BlobHandler{"mine"} - repo := &BlobHandler{"repo"} - reg.Register(mine, internal.ForMimeType(mime.MIME_TEXT)) - reg.Register(repo, internal.ForRepo(internal.CONTEXT_TYPE, REPO)) - - h := (*eff).LookupHandler(internal.ImplementationRepositoryType{internal.CONTEXT_TYPE, REPO}, ART, mime.MIME_OCTET) - Expect(h).To(Equal(internal.MultiBlobHandler{repo})) - - copy := (*eff).Copy() - new := &BlobHandler{"repo2"} - copy.Register(new, internal.ForRepo(internal.CONTEXT_TYPE, REPO)) - - h = (*eff).LookupHandler(internal.ImplementationRepositoryType{internal.CONTEXT_TYPE, REPO}, ART, mime.MIME_OCTET) - Expect(h).To(Equal(internal.MultiBlobHandler{repo})) - - h = copy.LookupHandler(internal.ImplementationRepositoryType{internal.CONTEXT_TYPE, REPO}, ART, mime.MIME_OCTET) - Expect(h).To(Equal(internal.MultiBlobHandler{new})) - }, - Entry("plain", ®), - Entry("extended", &ext), - ) - }) -}) diff --git a/pkg/contexts/ocm/internal/builder.go b/pkg/contexts/ocm/internal/builder.go deleted file mode 100644 index bfc0cbd52..000000000 --- a/pkg/contexts/ocm/internal/builder.go +++ /dev/null @@ -1,201 +0,0 @@ -package internal - -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/runtime" -) - -type Builder struct { - ctx context.Context - credentials credentials.Context - oci oci.Context - reposcheme RepositoryTypeScheme - repodel RepositoryDelegationRegistry - accessscheme AccessTypeScheme - spechandlers RepositorySpecHandlers - blobhandlers BlobHandlerRegistry - blobdigesters BlobDigesterRegistry -} - -func (b *Builder) getContext() context.Context { - if b.ctx == nil { - return context.Background() - } - return b.ctx -} - -func (b Builder) WithContext(ctx context.Context) Builder { - b.ctx = ctx - return b -} - -func (b Builder) WithCredentials(ctx credentials.Context) Builder { - b.credentials = ctx - return b -} - -func (b Builder) WithOCIRepositories(ctx oci.Context) Builder { - b.oci = ctx - return b -} - -func (b Builder) WithRepositoyTypeScheme(scheme RepositoryTypeScheme) Builder { - b.reposcheme = scheme - return b -} - -func (b Builder) WithRepositoryDelegation(reg RepositoryDelegationRegistry) Builder { - b.repodel = reg - return b -} - -func (b Builder) WithAccessTypeScheme(scheme AccessTypeScheme) Builder { - b.accessscheme = scheme - return b -} - -func (b Builder) WithRepositorySpecHandlers(reg RepositorySpecHandlers) Builder { - b.spechandlers = reg - return b -} - -func (b Builder) WithBlobHandlers(reg BlobHandlerRegistry) Builder { - b.blobhandlers = reg - return b -} - -func (b Builder) WithBlobDigesters(reg BlobDigesterRegistry) Builder { - b.blobdigesters = reg - return b -} - -func (b Builder) Bound() (Context, context.Context) { - c := b.New() - return c, context.WithValue(b.getContext(), key, c) -} - -func (b Builder) New(m ...datacontext.BuilderMode) Context { - mode := datacontext.Mode(m...) - ctx := b.getContext() - - if b.oci == nil { - if b.credentials != nil { - b.oci = oci.WithCredentials(b.credentials).New(mode) - } else { - var ok bool - b.oci, ok = oci.DefinedForContext(ctx) - if !ok && mode != datacontext.MODE_SHARED { - b.oci = oci.New(mode) - } - } - } - if b.credentials == nil { - b.credentials = b.oci.CredentialsContext() - } - if b.reposcheme == nil { - switch mode { - case datacontext.MODE_INITIAL: - b.reposcheme = NewRepositoryTypeScheme(nil) - case datacontext.MODE_CONFIGURED: - b.reposcheme = NewRepositoryTypeScheme(nil) - b.reposcheme.AddKnownTypes(DefaultRepositoryTypeScheme) - case datacontext.MODE_EXTENDED: - b.reposcheme = NewRepositoryTypeScheme(nil, DefaultRepositoryTypeScheme) - case datacontext.MODE_DEFAULTED: - fallthrough - case datacontext.MODE_SHARED: - b.reposcheme = DefaultRepositoryTypeScheme - } - } - if b.accessscheme == nil { - switch mode { - case datacontext.MODE_INITIAL: - b.accessscheme = NewAccessTypeScheme() - case datacontext.MODE_CONFIGURED: - b.accessscheme = NewAccessTypeScheme() - b.accessscheme.AddKnownTypes(DefaultAccessTypeScheme) - case datacontext.MODE_EXTENDED: - b.accessscheme = NewAccessTypeScheme(DefaultAccessTypeScheme) - case datacontext.MODE_DEFAULTED: - fallthrough - case datacontext.MODE_SHARED: - b.accessscheme = DefaultAccessTypeScheme - } - } - if b.spechandlers == nil { - switch mode { - case datacontext.MODE_INITIAL: - b.spechandlers = NewRepositorySpecHandlers() - case datacontext.MODE_CONFIGURED: - b.spechandlers = DefaultRepositorySpecHandlers.Copy() - case datacontext.MODE_EXTENDED: - fallthrough - case datacontext.MODE_DEFAULTED: - fallthrough - case datacontext.MODE_SHARED: - b.spechandlers = DefaultRepositorySpecHandlers - } - } - if b.repodel == nil { - switch mode { - case datacontext.MODE_INITIAL: - b.repodel = nil - case datacontext.MODE_CONFIGURED: - b.repodel = DefaultRepositoryDelegationRegistry.Copy() - case datacontext.MODE_EXTENDED: - b.repodel = NewDelegationRegistry[Context, RepositorySpec](DefaultRepositoryDelegationRegistry) - case datacontext.MODE_DEFAULTED: - fallthrough - case datacontext.MODE_SHARED: - b.repodel = DefaultRepositoryDelegationRegistry - } - } - if b.blobhandlers == nil { - switch mode { - case datacontext.MODE_INITIAL: - b.blobhandlers = NewBlobHandlerRegistry() - case datacontext.MODE_CONFIGURED: - b.blobhandlers = DefaultBlobHandlerRegistry.Copy() - case datacontext.MODE_EXTENDED: - b.blobhandlers = NewBlobHandlerRegistry(DefaultBlobHandlerRegistry) - case datacontext.MODE_DEFAULTED: - fallthrough - case datacontext.MODE_SHARED: - b.blobhandlers = DefaultBlobHandlerRegistry - } - } - if b.blobdigesters == nil { - switch mode { - case datacontext.MODE_INITIAL: - b.blobdigesters = NewBlobDigesterRegistry() - case datacontext.MODE_CONFIGURED: - b.blobdigesters = DefaultBlobDigesterRegistry.Copy() - case datacontext.MODE_EXTENDED: - b.blobdigesters = NewBlobDigesterRegistry(DefaultBlobDigesterRegistry) - case datacontext.MODE_DEFAULTED: - fallthrough - case datacontext.MODE_SHARED: - b.blobdigesters = DefaultBlobDigesterRegistry - } - } - - return datacontext.SetupContext(mode, newContext(b.credentials, b.oci, b.reposcheme, b.accessscheme, b.spechandlers, b.blobhandlers, b.blobdigesters, b.repodel, b.credentials.ConfigContext())) -} - -type delegatingDecoder struct { - ctx Context - delegate RepositoryDelegationRegistry -} - -var _ RepositorySpecDecoder = (*delegatingDecoder)(nil) - -func (d *delegatingDecoder) Decode(data []byte, unmarshaler runtime.Unmarshaler) (RepositorySpec, error) { - if d.delegate != nil { - return d.delegate.Decode(d.ctx, data, unmarshaler) - } - return nil, nil -} diff --git a/pkg/contexts/ocm/internal/builder_test.go b/pkg/contexts/ocm/internal/builder_test.go deleted file mode 100644 index ccbe34fac..000000000 --- a/pkg/contexts/ocm/internal/builder_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package internal_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/config" - "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/cpi" - local "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" - "github.com/open-component-model/ocm/pkg/runtime" -) - -var _ = Describe("builder test", func() { - It("creates local", func() { - ctx := local.Builder{}.New(datacontext.MODE_SHARED) - - Expect(ctx.AttributesContext()).To(BeIdenticalTo(datacontext.DefaultContext)) - Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) - Expect(BaseRepoTypes(ctx.RepositoryTypes())).To(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) - Expect(ctx.AccessMethods()).To(BeIdenticalTo(local.DefaultAccessTypeScheme)) - Expect(ctx.RepositorySpecHandlers()).To(BeIdenticalTo(local.DefaultRepositorySpecHandlers)) - Expect(ctx.BlobHandlers()).To(BeIdenticalTo(local.DefaultBlobHandlerRegistry)) - Expect(ctx.BlobDigesters()).To(BeIdenticalTo(local.DefaultBlobDigesterRegistry)) - - Expect(ctx.ConfigContext().GetId()).To(BeIdenticalTo(config.DefaultContext().GetId())) - Expect(ctx.CredentialsContext().GetId()).To(BeIdenticalTo(credentials.DefaultContext().GetId())) - Expect(ctx.OCIContext().GetId()).To(BeIdenticalTo(oci.DefaultContext().GetId())) - }) - - It("creates defaulted", func() { - ctx := local.Builder{}.New(datacontext.MODE_DEFAULTED) - - Expect(ctx.AttributesContext()).NotTo(BeIdenticalTo(datacontext.DefaultContext)) - Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) - Expect(BaseRepoTypes(ctx.RepositoryTypes())).To(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) - Expect(ctx.AccessMethods()).To(BeIdenticalTo(local.DefaultAccessTypeScheme)) - Expect(ctx.RepositorySpecHandlers()).To(BeIdenticalTo(local.DefaultRepositorySpecHandlers)) - Expect(ctx.BlobHandlers()).To(BeIdenticalTo(local.DefaultBlobHandlerRegistry)) - Expect(ctx.BlobDigesters()).To(BeIdenticalTo(local.DefaultBlobDigesterRegistry)) - - Expect(ctx.ConfigContext()).NotTo(BeIdenticalTo(config.DefaultContext())) - Expect(ctx.ConfigContext().ConfigTypes()).To(BeIdenticalTo(config.DefaultContext().ConfigTypes())) - - Expect(ctx.CredentialsContext()).NotTo(BeIdenticalTo(credentials.DefaultContext())) - Expect(ctx.CredentialsContext().RepositoryTypes()).To(BeIdenticalTo(credentials.DefaultContext().RepositoryTypes())) - - Expect(ctx.OCIContext()).NotTo(BeIdenticalTo(oci.DefaultContext())) - Expect(ctx.OCIContext().RepositoryTypes()).To(BeIdenticalTo(oci.DefaultContext().RepositoryTypes())) - }) - - It("creates configured", func() { - ctx := local.Builder{}.New(datacontext.MODE_CONFIGURED) - - Expect(ctx.AttributesContext()).NotTo(BeIdenticalTo(datacontext.DefaultContext)) - Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) - Expect(ctx.RepositoryTypes()).NotTo(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) - Expect(ctx.RepositoryTypes().KnownTypeNames()).To(Equal(local.DefaultRepositoryTypeScheme.KnownTypeNames())) - Expect(ctx.AccessMethods()).NotTo(BeIdenticalTo(local.DefaultAccessTypeScheme)) - Expect(ctx.RepositorySpecHandlers()).NotTo(BeIdenticalTo(local.DefaultRepositorySpecHandlers)) - Expect(ctx.BlobHandlers()).NotTo(BeIdenticalTo(local.DefaultBlobHandlerRegistry)) - Expect(ctx.BlobDigesters()).NotTo(BeIdenticalTo(local.DefaultBlobDigesterRegistry)) - - Expect(ctx.ConfigContext()).NotTo(BeIdenticalTo(config.DefaultContext())) - Expect(ctx.ConfigContext().ConfigTypes()).NotTo(BeIdenticalTo(config.DefaultContext().ConfigTypes())) - Expect(ctx.ConfigContext().ConfigTypes().KnownTypeNames()).To(Equal(config.DefaultContext().ConfigTypes().KnownTypeNames())) - - Expect(ctx.CredentialsContext()).NotTo(BeIdenticalTo(config.DefaultContext())) - Expect(ctx.CredentialsContext().RepositoryTypes()).NotTo(BeIdenticalTo(credentials.DefaultContext().RepositoryTypes())) - Expect(ctx.CredentialsContext().RepositoryTypes().KnownTypeNames()).To(Equal(credentials.DefaultContext().RepositoryTypes().KnownTypeNames())) - - Expect(ctx.OCIContext()).NotTo(BeIdenticalTo(config.DefaultContext())) - Expect(ctx.OCIContext().RepositoryTypes()).NotTo(BeIdenticalTo(oci.DefaultContext().RepositoryTypes())) - Expect(ctx.OCIContext().RepositoryTypes().KnownTypeNames()).To(Equal(oci.DefaultContext().RepositoryTypes().KnownTypeNames())) - }) - - It("creates iniial", func() { - ctx := local.Builder{}.New(datacontext.MODE_INITIAL) - - Expect(ctx.AttributesContext()).NotTo(BeIdenticalTo(datacontext.DefaultContext)) - Expect(ctx).NotTo(BeIdenticalTo(local.DefaultContext)) - Expect(ctx.RepositoryTypes()).NotTo(BeIdenticalTo(local.DefaultRepositoryTypeScheme)) - Expect(len(ctx.RepositoryTypes().KnownTypeNames())).To(Equal(0)) - Expect(len(ctx.AccessMethods().KnownTypeNames())).To(Equal(0)) - Expect(len(ctx.RepositorySpecHandlers().KnownTypeNames())).To(Equal(0)) - Expect(ctx.BlobHandlers().IsInitial()).To(Equal(true)) - Expect(ctx.BlobDigesters().IsInitial()).To(Equal(true)) - - Expect(ctx.ConfigContext()).NotTo(BeIdenticalTo(config.DefaultContext())) - Expect(len(ctx.ConfigContext().ConfigTypes().KnownTypeNames())).To(Equal(0)) - - Expect(ctx.CredentialsContext()).NotTo(BeIdenticalTo(credentials.DefaultContext())) - Expect(len(ctx.CredentialsContext().RepositoryTypes().KnownTypeNames())).To(Equal(0)) - }) -}) - -func BaseRepoTypes(r cpi.RepositoryTypeScheme) runtime.Scheme[local.RepositorySpec, local.RepositoryType] { - return r.BaseScheme() -} diff --git a/pkg/contexts/ocm/internal/context.go b/pkg/contexts/ocm/internal/context.go deleted file mode 100644 index 60269ba46..000000000 --- a/pkg/contexts/ocm/internal/context.go +++ /dev/null @@ -1,340 +0,0 @@ -package internal - -import ( - "context" - "reflect" - "strings" - - . "github.com/mandelsoft/goutils/finalizer" - - "github.com/modern-go/reflect2" - - "github.com/open-component-model/ocm/pkg/contexts/config" - cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "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/oci/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -const CONTEXT_TYPE = "ocm" + datacontext.OCM_CONTEXT_SUFFIX - -const CommonTransportFormat = ctf.Type - -type ContextProvider interface { - OCMContext() Context -} - -type LocalContextProvider interface { - GetContext() Context -} - -type localContextProvider struct { - LocalContextProvider -} - -func (l *localContextProvider) OCMContext() Context { - return l.GetContext() -} - -func WrapContextProvider(ctx LocalContextProvider) ContextProvider { - return &localContextProvider{ctx} -} - -type Context interface { - datacontext.Context - config.ContextProvider - credentials.ContextProvider - oci.ContextProvider - ContextProvider - - RepositoryTypes() RepositoryTypeScheme - AccessMethods() AccessTypeScheme - - RepositorySpecHandlers() RepositorySpecHandlers - MapUniformRepositorySpec(u *UniformRepositorySpec) (RepositorySpec, error) - - DisableBlobHandlers() - BlobHandlers() BlobHandlerRegistry - BlobDigesters() BlobDigesterRegistry - - RepositoryForSpec(spec RepositorySpec, creds ...credentials.CredentialsSource) (Repository, error) - RepositoryForConfig(data []byte, unmarshaler runtime.Unmarshaler, creds ...credentials.CredentialsSource) (Repository, error) - RepositorySpecForConfig(data []byte, unmarshaler runtime.Unmarshaler) (RepositorySpec, error) - - AccessSpecForSpec(spec compdesc.AccessSpec) (AccessSpec, error) - AccessSpecForConfig(data []byte, unmarshaler runtime.Unmarshaler) (AccessSpec, error) - - Encode(AccessSpec, runtime.Marshaler) ([]byte, error) - - GetAlias(name string) RepositorySpec - SetAlias(name string, spec RepositorySpec) - - GetResolver() ComponentVersionResolver - AddResolverRule(prefix string, spec RepositorySpec, prio ...int) - - // Finalize finalizes elements implicitly opened during resource operations. - // For example, registered blob handler may open objects, which are kept open - // for performance reasons. At the end of a usage finalize should be called - // to finalize those elements. This method can be called any time by a context - // user to cleanup temporarily allocated resources. Therefore, only - // elements should be added to the finalzer, which can be reopened/created - // on-the fly whenever required. - Finalize() error - Finalizer() *Finalizer -} - -// ////////////////////////////////////////////////////////////////////////////// - -var key = reflect.TypeOf(_context{}) - -// DefaultContext is the default context initialized by init functions. -var DefaultContext = Builder{}.New(datacontext.MODE_SHARED) - -// FromContext returns the Context to use for context.Context. -// This is either an explicit context or the default context. -func FromContext(ctx context.Context) Context { - c, _ := datacontext.ForContextByKey(ctx, key, DefaultContext) - return c.(Context) -} - -func FromProvider(p ContextProvider) Context { - if p == nil { - return nil - } - return p.OCMContext() -} - -func DefinedForContext(ctx context.Context) (Context, bool) { - c, ok := datacontext.ForContextByKey(ctx, key, DefaultContext) - if c != nil { - return c.(Context), ok - } - return nil, ok -} - -// ////////////////////////////////////////////////////////////////////////////// - -type _InternalContext = datacontext.InternalContext - -type _context struct { - _InternalContext - updater cfgcpi.Updater - - credctx credentials.Context - ocictx oci.Context - - knownRepositoryTypes RepositoryTypeScheme - knownAccessTypes AccessTypeScheme - - specHandlers RepositorySpecHandlers - blobHandlers BlobHandlerRegistry - blobDigesters BlobDigesterRegistry - aliases map[string]RepositorySpec - resolver *resolver -} - -var ( - _ Context = (*_context)(nil) - _ datacontext.ViewCreator[Context] = (*_context)(nil) -) - -// gcWrapper is used as garbage collectable -// wrapper for a context implementation -// to establish a runtime finalizer. -type gcWrapper struct { - datacontext.GCWrapper - *_context -} - -func newView(c *_context, ref ...bool) Context { - if utils.Optional(ref...) { - return datacontext.FinalizedContext[gcWrapper](c) - } - return c -} - -func (w *gcWrapper) SetContext(c *_context) { - w._context = c -} - -func newContext(credctx credentials.Context, ocictx oci.Context, reposcheme RepositoryTypeScheme, accessscheme AccessTypeScheme, specHandlers RepositorySpecHandlers, blobHandlers BlobHandlerRegistry, blobDigesters BlobDigesterRegistry, repodel RepositoryDelegationRegistry, delegates datacontext.Delegates) Context { - c := &_context{ - credctx: datacontext.PersistentContextRef(credctx), - ocictx: datacontext.PersistentContextRef(ocictx), - specHandlers: specHandlers, - blobHandlers: blobHandlers, - blobDigesters: blobDigesters, - knownAccessTypes: accessscheme, - knownRepositoryTypes: reposcheme, - aliases: map[string]RepositorySpec{}, - } - - if repodel != nil { - c.knownRepositoryTypes = NewRepositoryTypeScheme(&delegatingDecoder{ctx: c, delegate: repodel}, reposcheme) - } - c._InternalContext = datacontext.NewContextBase(c, CONTEXT_TYPE, key, credctx.GetAttributes(), delegates) - c.updater = cfgcpi.NewUpdaterForFactory(credctx.ConfigContext(), c.OCMContext) - c.resolver = &resolver{ - ctx: c, - MatchingResolver: NewMatchingResolver(c), - } - c.Finalizer().With(c.resolver.Finalize) - return newView(c, true) -} - -func (c *_context) CreateView() Context { - return newView(c, true) -} - -func (c *_context) OCMContext() Context { - return newView(c) -} - -func (c *_context) Update() error { - return c.updater.Update() -} - -func (c *_context) AttributesContext() datacontext.AttributesContext { - return c.credctx.AttributesContext() -} - -func (c *_context) ConfigContext() config.Context { - return c.updater.GetContext() -} - -func (c *_context) CredentialsContext() credentials.Context { - return c.credctx -} - -func (c *_context) OCIContext() oci.Context { - return c.ocictx -} - -func (c *_context) RepositoryTypes() RepositoryTypeScheme { - return c.knownRepositoryTypes -} - -func (c *_context) RepositorySpecHandlers() RepositorySpecHandlers { - return c.specHandlers -} - -func (c *_context) MapUniformRepositorySpec(u *UniformRepositorySpec) (RepositorySpec, error) { - return c.specHandlers.MapUniformRepositorySpec(c, u) -} - -func (c *_context) DisableBlobHandlers() { - c.blobHandlers = NewBlobHandlerRegistry(nil) -} - -func (c *_context) BlobHandlers() BlobHandlerRegistry { - c.Update() - return c.blobHandlers -} - -func (c *_context) BlobDigesters() BlobDigesterRegistry { - c.Update() - return c.blobDigesters -} - -func (c *_context) RepositoryForSpec(spec RepositorySpec, creds ...credentials.CredentialsSource) (Repository, error) { - cred, err := credentials.CredentialsChain(creds).Credentials(c.CredentialsContext()) - if err != nil { - return nil, err - } - return spec.Repository(c, cred) -} - -func (c *_context) RepositoryForConfig(data []byte, unmarshaler runtime.Unmarshaler, creds ...credentials.CredentialsSource) (Repository, error) { - spec, err := c.knownRepositoryTypes.Decode(data, unmarshaler) - if err != nil { - return nil, err - } - return c.RepositoryForSpec(spec, creds...) -} - -func (c *_context) RepositorySpecForConfig(data []byte, unmarshaler runtime.Unmarshaler) (RepositorySpec, error) { - return c.knownRepositoryTypes.Decode(data, unmarshaler) -} - -func (c *_context) AccessMethods() AccessTypeScheme { - return c.knownAccessTypes -} - -func (c *_context) AccessSpecForConfig(data []byte, unmarshaler runtime.Unmarshaler) (AccessSpec, error) { - return c.knownAccessTypes.Decode(data, unmarshaler) -} - -// AccessSpecForSpec takes an anonymous access specification and tries to map -// it to an effective implementation. -func (c *_context) AccessSpecForSpec(spec compdesc.AccessSpec) (AccessSpec, error) { - if reflect2.IsNil(spec) { - return nil, nil - } - if n, ok := spec.(AccessSpec); ok { - if g, ok := spec.(EvaluatableAccessSpec); ok { - return g.Evaluate(c) - } - return n, nil - } - un, err := runtime.ToUnstructuredTypedObject(spec) - if err != nil { - return nil, err - } - - raw, err := un.GetRaw() - if err != nil { - return nil, err - } - - return c.knownAccessTypes.Decode(raw, runtime.DefaultJSONEncoding) -} - -func (c *_context) Encode(spec AccessSpec, marshaler runtime.Marshaler) ([]byte, error) { - return c.knownAccessTypes.Encode(spec, marshaler) -} - -func (c *_context) GetAlias(name string) RepositorySpec { - err := c.updater.Update() - if err != nil { - return nil - } - c.updater.RLock() - defer c.updater.RUnlock() - spec := c.aliases[name] - if spec == nil && strings.HasSuffix(name, ".alias") { - spec = c.aliases[name[:len(name)-6]] - } - return spec -} - -func (c *_context) SetAlias(name string, spec RepositorySpec) { - c.updater.Lock() - defer c.updater.Unlock() - c.aliases[name] = spec -} - -func (c *_context) GetResolver() ComponentVersionResolver { - c.Update() - if len(c.resolver.rules) == 0 { - return nil - } - return c.resolver -} - -func (c *_context) AddResolverRule(prefix string, spec RepositorySpec, prio ...int) { - c.resolver.AddRule(prefix, spec, prio...) -} - -type resolver struct { - ctx *_context - *MatchingResolver -} - -func (r *resolver) LookupComponentVersion(name, version string) (ComponentVersionAccess, error) { - r.ctx.Update() - return r.MatchingResolver.LookupComponentVersion(name, version) -} diff --git a/pkg/contexts/ocm/internal/errors.go b/pkg/contexts/ocm/internal/errors.go deleted file mode 100644 index e4d5a135f..000000000 --- a/pkg/contexts/ocm/internal/errors.go +++ /dev/null @@ -1,28 +0,0 @@ -package internal - -import ( - "fmt" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - "github.com/open-component-model/ocm/pkg/errkind" -) - -const ( - KIND_REPOSITORY = "ocm repository" - KIND_COMPONENT = errkind.KIND_COMPONENT - KIND_COMPONENTVERSION = "component version" - KIND_RESOURCE = "component resource" - KIND_SOURCE = "component source" - KIND_REFERENCE = compdesc.KIND_REFERENCE - KIND_REPOSITORYSPEC = "repository specification" -) - -func ErrComponentVersionNotFound(name, version string) error { - return errors.ErrNotFound(KIND_COMPONENTVERSION, fmt.Sprintf("%s:%s", name, version)) -} - -func ErrComponentVersionNotFoundWrap(err error, name, version string) error { - return errors.ErrNotFoundWrap(err, KIND_COMPONENTVERSION, fmt.Sprintf("%s:%s", name, version)) -} diff --git a/pkg/contexts/ocm/internal/hasher.go b/pkg/contexts/ocm/internal/hasher.go deleted file mode 100644 index a170d0c50..000000000 --- a/pkg/contexts/ocm/internal/hasher.go +++ /dev/null @@ -1,11 +0,0 @@ -package internal - -import ( - "github.com/open-component-model/ocm/pkg/signing" -) - -// Hasher creates a new hash.Hash interface. -type Hasher = signing.Hasher - -// HasherProvider provides access to supported hash methods. -type HasherProvider = signing.HasherProvider diff --git a/pkg/contexts/ocm/internal/modopts.go b/pkg/contexts/ocm/internal/modopts.go deleted file mode 100644 index f17a05cf5..000000000 --- a/pkg/contexts/ocm/internal/modopts.go +++ /dev/null @@ -1,505 +0,0 @@ -package internal - -import ( - "fmt" - - "github.com/mandelsoft/goutils/optionutils" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/utils" -) - -type BlobUploadOption interface { - ApplyBlobUploadOption(opts *BlobUploadOptions) -} - -type BlobOptionImpl interface { - BlobUploadOption - BlobModificationOption -} - -type BlobUploadOptions struct { - UseNoDefaultIfNotSet *bool `json:"noDefaultUpload,omitempty"` - BlobHandlerProvider BlobHandlerProvider `json:"-"` -} - -var _ BlobUploadOption = (*BlobUploadOptions)(nil) - -func NewBlobUploadOptions(list ...BlobUploadOption) *BlobUploadOptions { - var m BlobUploadOptions - m.ApplyBlobUploadOptions(list...) - return &m -} - -func (m *BlobUploadOptions) ApplyBlobUploadOptions(list ...BlobUploadOption) { - for _, o := range list { - if o != nil { - o.ApplyBlobUploadOption(m) - } - } -} - -func (o *BlobUploadOptions) ApplyBlobModificationOption(opts *BlobModificationOptions) { - o.ApplyBlobUploadOption(&opts.BlobUploadOptions) -} - -func (o *BlobUploadOptions) ApplyBlobUploadOption(opts *BlobUploadOptions) { - optionutils.ApplyOption(o.UseNoDefaultIfNotSet, &opts.UseNoDefaultIfNotSet) - if o.BlobHandlerProvider != nil { - opts.BlobHandlerProvider = o.BlobHandlerProvider - opts.UseNoDefaultIfNotSet = utils.BoolP(true) - } -} - -//////////////////////////////////////////////////////////////////////////////// - -type nodefaulthandler bool - -func (o nodefaulthandler) ApplyBlobModificationOption(opts *BlobModificationOptions) { - o.ApplyBlobUploadOption(&opts.BlobUploadOptions) -} - -func (o nodefaulthandler) ApplyBlobUploadOption(opts *BlobUploadOptions) { - opts.UseNoDefaultIfNotSet = optionutils.PointerTo(bool(o)) -} - -func UseNoDefaultBlobHandlers(b ...bool) BlobOptionImpl { - return nodefaulthandler(utils.OptionalDefaultedBool(true, b...)) -} - -//////////////////////////////////////////////////////////////////////////////// - -type handler struct { - blobHandlerProvider BlobHandlerProvider -} - -func (o *handler) ApplyBlobModificationOption(opts *BlobModificationOptions) { - o.ApplyBlobUploadOption(&opts.BlobUploadOptions) -} - -func (o *handler) ApplyBlobUploadOption(opts *BlobUploadOptions) { - if o.blobHandlerProvider != nil { - opts.BlobHandlerProvider = o.blobHandlerProvider - } -} - -func UseBlobHandlers(h BlobHandlerProvider) BlobOptionImpl { - return &handler{h} -} - -//////////////////////////////////////////////////////////////////////////////// - -// TargetElement described the index used to set the -// resource or source for the SetXXX calls. -// If -1 is returned an append is enforced. -type TargetElement interface { - GetTargetIndex(resources compdesc.ElementAccessor, meta *compdesc.ElementMeta) (int, error) -} - -type TargetOptionImpl interface { - TargetOption - ModificationOption - BlobModificationOption -} - -type TargetOptions struct { - TargetElement TargetElement -} - -type TargetOption interface { - ApplyTargetOption(options *TargetOptions) -} - -func (m *TargetOptions) ApplyBlobModificationOption(opts *BlobModificationOptions) { - m.ApplyTargetOption(&opts.TargetOptions) -} - -func (m *TargetOptions) ApplyModificationOption(opts *ModificationOptions) { - m.ApplyTargetOption(&opts.TargetOptions) -} - -func (m *TargetOptions) ApplyTargetOption(opts *TargetOptions) { - optionutils.Transfer(&opts.TargetElement, m.TargetElement) -} - -func (m *TargetOptions) ApplyTargetOptions(list ...TargetOption) *TargetOptions { - for _, o := range list { - if o != nil { - o.ApplyTargetOption(m) - } - } - return m -} - -func NewTargetOptions(list ...TargetOption) *TargetOptions { - var m TargetOptions - m.ApplyTargetOptions(list...) - return &m -} - -type ModificationOption interface { - ApplyModificationOption(opts *ModificationOptions) -} - -type ModOptionImpl interface { - ModificationOption - BlobModificationOption -} - -type ModificationOptions struct { - TargetOptions - - // ModifyResource disables the modification of signature releveant - // resource parts. - ModifyResource *bool - - // AcceptExistentDigests don't validate/recalculate the content digest - // of resources. - AcceptExistentDigests *bool - - // DefaultHashAlgorithm is the hash algorithm to use if no specific setting os found - DefaultHashAlgorithm string - - // HasherProvider is the factory for hash algorithms to use. - HasherProvider HasherProvider - - // SkipVerify disabled the verification of given digests - SkipVerify *bool - - // SkipDigest disabled digest creation (for legacy code, only!) - SkipDigest *bool -} - -func (m *ModificationOptions) IsModifyResource() bool { - return utils.AsBool(m.ModifyResource) -} - -func (m *ModificationOptions) IsAcceptExistentDigests() bool { - return utils.AsBool(m.AcceptExistentDigests) -} - -func (m *ModificationOptions) IsSkipDigest() bool { - return utils.AsBool(m.SkipDigest) -} - -func (m *ModificationOptions) IsSkipVerify() bool { - return utils.AsBool(m.SkipVerify) -} - -func (m *ModificationOptions) ApplyModificationOptions(list ...ModificationOption) *ModificationOptions { - for _, o := range list { - if o != nil { - o.ApplyModificationOption(m) - } - } - return m -} - -func (m *ModificationOptions) ApplyBlobModificationOption(opts *BlobModificationOptions) { - m.ApplyModificationOption(&opts.ModificationOptions) -} - -func (m *ModificationOptions) ApplyModificationOption(opts *ModificationOptions) { - m.TargetOptions.ApplyTargetOption(&opts.TargetOptions) - optionutils.Transfer(&opts.ModifyResource, m.ModifyResource) - optionutils.Transfer(&opts.AcceptExistentDigests, m.AcceptExistentDigests) - optionutils.Transfer(&opts.SkipDigest, m.SkipDigest) - optionutils.Transfer(&opts.SkipVerify, m.SkipVerify) - optionutils.Transfer(&opts.HasherProvider, m.HasherProvider) - optionutils.Transfer(&opts.DefaultHashAlgorithm, m.DefaultHashAlgorithm) -} - -func (m *ModificationOptions) GetHasher(algo ...string) Hasher { - return m.HasherProvider.GetHasher(utils.OptionalDefaulted(m.DefaultHashAlgorithm, algo...)) -} - -func NewModificationOptions(list ...ModificationOption) *ModificationOptions { - var m ModificationOptions - m.ApplyModificationOptions(list...) - return &m -} - -//////////////////////////////////////////////////////////////////////////////// - -type TargetIndex int - -func (m TargetIndex) GetTargetIndex(elems compdesc.ElementAccessor, meta *compdesc.ElementMeta) (int, error) { - if int(m) >= elems.Len() { - return -1, nil - } - return int(m), nil -} - -func (m TargetIndex) ApplyBlobModificationOption(opts *BlobModificationOptions) { - m.ApplyModificationOption(&opts.ModificationOptions) -} - -func (m TargetIndex) ApplyModificationOption(opts *ModificationOptions) { - m.ApplyTargetOption(&opts.TargetOptions) -} - -func (m TargetIndex) ApplyTargetOption(opts *TargetOptions) { - opts.TargetElement = m -} - -//////////////////////////////////////////////////////////////////////////////// - -type TargetIdentityOrAppend v1.Identity - -func (m TargetIdentityOrAppend) GetTargetIndex(elems compdesc.ElementAccessor, meta *compdesc.ElementMeta) (int, error) { - idx, _ := TargetIdentity(m).GetTargetIndex(elems, meta) - return idx, nil -} - -func (m TargetIdentityOrAppend) ApplyBlobModificationOption(opts *BlobModificationOptions) { - m.ApplyModificationOption(&opts.ModificationOptions) -} - -func (m TargetIdentityOrAppend) ApplyModificationOption(opts *ModificationOptions) { - m.ApplyTargetOption(&opts.TargetOptions) -} - -func (m TargetIdentityOrAppend) ApplyTargetOption(opts *TargetOptions) { - opts.TargetElement = m -} - -//////////////////////////////////////////////////////////////////////////////// - -type TargetIdentity v1.Identity - -func (m TargetIdentity) GetTargetIndex(elems compdesc.ElementAccessor, meta *compdesc.ElementMeta) (int, error) { - for i := 0; i < elems.Len(); i++ { - r := elems.Get(i) - if r.GetMeta().GetIdentity(elems).Equals(v1.Identity(m)) { - return i, nil - } - } - return -1, fmt.Errorf("element %s not found", v1.Identity(m)) -} - -func (m TargetIdentity) ApplyBlobModificationOption(opts *BlobModificationOptions) { - m.ApplyModificationOption(&opts.ModificationOptions) -} - -func (m TargetIdentity) ApplyModificationOption(opts *ModificationOptions) { - m.ApplyTargetOption(&opts.TargetOptions) -} - -func (m TargetIdentity) ApplyTargetOption(opts *TargetOptions) { - opts.TargetElement = m -} - -//////////////////////////////////////////////////////////////////////////////// - -type replaceElement struct{} - -var UpdateElement = replaceElement{} - -func (m replaceElement) GetTargetIndex(elems compdesc.ElementAccessor, meta *compdesc.ElementMeta) (int, error) { - id := meta.GetIdentity(elems) - for i := 0; i < elems.Len(); i++ { - if elems.Get(i).GetMeta().GetIdentity(elems).Equals(id) { - return i, nil - } - } - return -1, fmt.Errorf("element %s not found", id) -} - -func (m replaceElement) ApplyBlobModificationOption(opts *BlobModificationOptions) { - m.ApplyModificationOption(&opts.ModificationOptions) -} - -func (m replaceElement) ApplyModificationOption(opts *ModificationOptions) { - m.ApplyTargetOption(&opts.TargetOptions) -} - -func (m replaceElement) ApplyTargetOption(opts *TargetOptions) { - opts.TargetElement = m -} - -//////////////////////////////////////////////////////////////////////////////// - -type modifyresource bool - -func (m modifyresource) ApplyBlobModificationOption(opts *BlobModificationOptions) { - m.ApplyModificationOption(&opts.ModificationOptions) -} - -func (m modifyresource) ApplyModificationOption(opts *ModificationOptions) { - opts.ModifyResource = utils.BoolP(m) -} - -func ModifyResource(flag ...bool) ModOptionImpl { - return modifyresource(utils.OptionalDefaultedBool(true, flag...)) -} - -//////////////////////////////////////////////////////////////////////////////// - -type acceptdigests bool - -func (m acceptdigests) ApplyBlobModificationOption(opts *BlobModificationOptions) { - m.ApplyModificationOption(&opts.ModificationOptions) -} - -func (m acceptdigests) ApplyModificationOption(opts *ModificationOptions) { - opts.AcceptExistentDigests = utils.BoolP(m) -} - -func AcceptExistentDigests(flag ...bool) ModOptionImpl { - return acceptdigests(utils.OptionalDefaultedBool(true, flag...)) -} - -//////////////////////////////////////////////////////////////////////////////// - -type hashalgo string - -func (m hashalgo) ApplyBlobModificationOption(opts *BlobModificationOptions) { - m.ApplyModificationOption(&opts.ModificationOptions) -} - -func (m hashalgo) ApplyModificationOption(opts *ModificationOptions) { - opts.DefaultHashAlgorithm = string(m) -} - -func WithDefaultHashAlgorithm(algo ...string) ModOptionImpl { - return hashalgo(utils.Optional(algo...)) -} - -//////////////////////////////////////////////////////////////////////////////// - -type hashprovider struct { - prov HasherProvider -} - -func (m hashprovider) ApplyBlobModificationOption(opts *BlobModificationOptions) { - m.ApplyModificationOption(&opts.ModificationOptions) -} - -func (m *hashprovider) ApplyModificationOption(opts *ModificationOptions) { - opts.HasherProvider = m.prov -} - -func WithHasherProvider(prov HasherProvider) ModOptionImpl { - return &hashprovider{prov} -} - -//////////////////////////////////////////////////////////////////////////////// - -type skipverify bool - -func (m skipverify) ApplyBlobModificationOption(opts *BlobModificationOptions) { - m.ApplyModificationOption(&opts.ModificationOptions) -} - -func (m skipverify) ApplyModificationOption(opts *ModificationOptions) { - opts.SkipVerify = utils.BoolP(m) -} - -func SkipVerify(flag ...bool) ModOptionImpl { - return skipverify(utils.OptionalDefaultedBool(true, flag...)) -} - -//////////////////////////////////////////////////////////////////////////////// - -type skipdigest bool - -func (m skipdigest) ApplyBlobModificationOption(opts *BlobModificationOptions) { - m.ApplyModificationOption(&opts.ModificationOptions) -} - -func (m skipdigest) ApplyModificationOption(opts *ModificationOptions) { - opts.SkipDigest = utils.BoolP(m) -} - -// SkipDigest disables digest creation if enabled. -// -// Deprecated: for legacy code, only. -func SkipDigest(flag ...bool) ModOptionImpl { - return skipdigest(utils.OptionalDefaultedBool(true, flag...)) -} - -//////////////////////////////////////////////////////////////////////////////// - -// BlobModificationOption is used for option list allowing both, -// blob upload and modification options. -type BlobModificationOption interface { - ApplyBlobModificationOption(*BlobModificationOptions) -} - -type BlobModificationOptions struct { - BlobUploadOptions - ModificationOptions -} - -func NewBlobModificationOptions(list ...BlobModificationOption) *BlobModificationOptions { - var m BlobModificationOptions - m.ApplyBlobModificationOptions(list...) - return &m -} - -func (m *BlobModificationOptions) ApplyBlobModificationOptions(list ...BlobModificationOption) { - for _, o := range list { - if o != nil { - o.ApplyBlobModificationOption(m) - } - } -} - -func (o *BlobModificationOptions) ApplyBlobModificationOption(opts *BlobModificationOptions) { - o.BlobUploadOptions.ApplyBlobUploadOption(&opts.BlobUploadOptions) - o.ModificationOptions.ApplyModificationOption(&opts.ModificationOptions) -} - -func (o *BlobModificationOptions) ApplyBlobUploadOption(opts *BlobUploadOptions) { - o.BlobUploadOptions.ApplyBlobUploadOption(opts) -} - -func (o *BlobModificationOptions) ApplyModificationOption(opts *ModificationOptions) { - o.ModificationOptions.ApplyModificationOption(opts) -} - -/////////////////////////////////////////////////////////////////////////////// - -// BlobModificationOption is used for option list allowing both, -// blob upload and modification options. -type AddVersionOption interface { - ApplyAddVersionOption(*AddVersionOptions) -} - -type AddVersionOptions struct { - Overwrite *bool - BlobUploadOptions -} - -func NewAddVersionOptions(list ...AddVersionOption) *AddVersionOptions { - var m AddVersionOptions - m.ApplyAddVersionOptions(list...) - return &m -} - -func (m *AddVersionOptions) ApplyAddVersionOptions(list ...AddVersionOption) { - for _, o := range list { - if o != nil { - o.ApplyAddVersionOption(m) - } - } -} - -func (o *AddVersionOptions) ApplyAddVersionOption(opts *AddVersionOptions) { - optionutils.ApplyOption(o.Overwrite, &opts.Overwrite) - o.BlobUploadOptions.ApplyBlobUploadOption(&opts.BlobUploadOptions) -} - -//////////////////////////////////////////////////////////////////////////////// - -type overwrite bool - -func (m overwrite) ApplyAddVersionOption(opts *AddVersionOptions) { - opts.Overwrite = utils.BoolP(m) -} - -// Overwrite enabled the overwrite mode for adding a component version. -func Overwrite(flag ...bool) AddVersionOption { - return overwrite(utils.OptionalDefaultedBool(true, flag...)) -} diff --git a/pkg/contexts/ocm/internal/repository.go b/pkg/contexts/ocm/internal/repository.go deleted file mode 100644 index de00691e8..000000000 --- a/pkg/contexts/ocm/internal/repository.go +++ /dev/null @@ -1,234 +0,0 @@ -package internal - -import ( - "io" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "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/contexts/ocm/selectors/refsel" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/rscsel" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/srcsel" - "github.com/open-component-model/ocm/pkg/refmgmt/resource" -) - -type ReadOnlyFeature interface { - IsReadOnly() bool - // SetReadOnly is used to set the element into readonly mode. - // Once enabled it cannot be reverted. An underlying object, for - // example a CTF might be in readonly mode, forced by filesystem - // permissions. Such elements cannot be set into write mode again. - // Therefore, generally only one direction is possible. - SetReadOnly() -} - -type ComponentVersionResolver interface { - LookupComponentVersion(name string, version string) (ComponentVersionAccess, error) -} - -type RepositoryImpl interface { - GetContext() Context - - GetSpecification() RepositorySpec - ComponentLister() ComponentLister - - ExistsComponentVersion(name string, version string) (bool, error) - LookupComponentVersion(name string, version string) (ComponentVersionAccess, error) - LookupComponent(name string) (ComponentAccess, error) - - io.Closer - ReadOnlyFeature -} - -type Repository interface { - resource.ResourceView[Repository] - RepositoryImpl - - NewComponentVersion(comp, version string, overrides ...bool) (ComponentVersionAccess, error) - AddComponentVersion(cv ComponentVersionAccess, overrides ...bool) error -} - -// ConsumerIdentityProvider is an interface for object requiring -// credentials, which want to expose the ConsumerId they are -// usingto request implicit credentials. -type ConsumerIdentityProvider = credentials.ConsumerIdentityProvider - -type ( - DataAccess = blobaccess.DataAccess - BlobAccess = blobaccess.BlobAccess - MimeType = blobaccess.MimeType -) - -type ComponentAccess interface { - resource.ResourceView[ComponentAccess] - - GetContext() Context - GetName() string - - ListVersions() ([]string, error) - LookupVersion(version string) (ComponentVersionAccess, error) - HasVersion(vers string) (bool, error) - NewVersion(version string, overrides ...bool) (ComponentVersionAccess, error) - AddVersion(cv ComponentVersionAccess, overrides ...bool) error - AddVersionOpt(cv ComponentVersionAccess, opts ...AddVersionOption) error - - io.Closer -} - -// AccessProvider assembled methods provided -// and used for access methods. -// It is provided for resources in a component version -// with the base support implementation in package cpi. -// But it can also be provided by resource provisioners -// used to feed the ComponentVersionAccess.SetResourceByAccess -// or the ComponentVersionAccessSetSourceByAccess -// method. -type AccessProvider interface { - GetOCMContext() Context - ReferenceHint() string - - Access() (AccessSpec, error) - AccessMethod() (AccessMethod, error) - - GlobalAccess() AccessSpec - - blobaccess.BlobAccessProvider -} - -type ArtifactAccess[M any] interface { - Meta() *M - GetComponentVersion() (ComponentVersionAccess, error) - AccessProvider -} - -type ( - ResourceMeta = compdesc.ResourceMeta - ResourceAccess = ArtifactAccess[ResourceMeta] -) - -type ( - SourceMeta = compdesc.SourceMeta - SourceAccess = ArtifactAccess[SourceMeta] -) - -type ComponentReference = compdesc.ComponentReference - -type ComponentVersionAccess interface { - resource.ResourceView[ComponentVersionAccess] - common.VersionedElement - io.Closer - ReadOnlyFeature - - GetContext() Context - Repository() Repository - GetDescriptor() *compdesc.ComponentDescriptor - - DiscardChanges() - IsPersistent() bool - - GetProvider() *compdesc.Provider - SetProvider(provider *compdesc.Provider) error - - GetResource(meta metav1.Identity) (ResourceAccess, error) - GetResourceIndex(meta metav1.Identity) int - GetResourceByIndex(i int) (ResourceAccess, error) - GetResources() []ResourceAccess - SelectResources(sel ...rscsel.Selector) ([]ResourceAccess, error) - - // - // Deprecated: use GetResources with selector arguments. - //nolint: staticcheck // deprecated - GetResourcesByName(name string, selectors ...compdesc.IdentitySelector) ([]ResourceAccess, error) - - // - // Deprecated: use GetResources with selector arguments. - //nolint: staticcheck // deprecated - GetResourcesByIdentitySelectors(selectors ...compdesc.IdentitySelector) ([]ResourceAccess, error) - - // - // Deprecated: use GetResources with selector arguments. - //nolint: staticcheck // deprecated - GetResourcesByResourceSelectors(selectors ...compdesc.ResourceSelector) ([]ResourceAccess, error) - SetResource(*ResourceMeta, compdesc.AccessSpec, ...ModificationOption) error - SetResourceByAccess(art ResourceAccess, modopts ...BlobModificationOption) error - - GetSource(meta metav1.Identity) (SourceAccess, error) - GetSourceIndex(meta metav1.Identity) int - GetSourceByIndex(i int) (SourceAccess, error) - GetSources() []SourceAccess - SelectSources(sel ...srcsel.Selector) ([]SourceAccess, error) - - // Deprecated: use GetResources with appropriate selectors. - //nolint: staticcheck // deprecated - GetSourcesByName(name string, selectors ...compdesc.IdentitySelector) ([]SourceAccess, error) - // SetSource updates or sets anew source. The options only use the - // target options. All other options are ignored. - SetSource(*SourceMeta, compdesc.AccessSpec, ...TargetOption) error - // SetSourceByAccess updates or sets anew source. The options only use the - // target options. All other options are ignored. - SetSourceByAccess(art SourceAccess, opts ...TargetOption) error - - GetReference(meta metav1.Identity) (ComponentReference, error) - GetReferenceIndex(meta metav1.Identity) int - GetReferenceByIndex(i int) (ComponentReference, error) - GetReferences() []ComponentReference - SelectReferences(sel ...refsel.Selector) ([]ComponentReference, error) - - // Deprecated: use GetReferences with appropriate selectors. - //nolint: staticcheck // deprecated - GetReferencesByName(name string, selectors ...compdesc.IdentitySelector) (compdesc.References, error) - - // Deprecated: use GetReferences with appropriate selectors. - //nolint: staticcheck // deprecated - GetReferencesByIdentitySelectors(selectors ...compdesc.IdentitySelector) (compdesc.References, error) - - // Deprecated: use GetReferences with appropriate selectors. - //nolint: staticcheck // deprecated - GetReferencesByReferenceSelectors(selectors ...compdesc.ReferenceSelector) (compdesc.References, error) - SetReference(ref *ComponentReference, opts ...TargetOption) error - - // AddBlob adds a local blob and returns an appropriate local access spec. - AddBlob(blob BlobAccess, artType, refName string, global AccessSpec, opts ...BlobUploadOption) (AccessSpec, error) - - // AdjustResourceAccess is used to modify the access spec. The old and new one MUST refer to the same content. - AdjustResourceAccess(meta *ResourceMeta, acc compdesc.AccessSpec, opts ...ModificationOption) error - SetResourceBlob(meta *ResourceMeta, blob BlobAccess, refname string, global AccessSpec, opts ...BlobModificationOption) error - AdjustSourceAccess(meta *SourceMeta, acc compdesc.AccessSpec) error - // SetSourceBlob updates or sets anew source. The options only use the - // target options. All other options are ignored. - SetSourceBlob(meta *SourceMeta, blob BlobAccess, refname string, global AccessSpec, opts ...TargetOption) error - - // AccessMethod provides an access method implementation for - // an access spec. This might be a repository local implementation - // or a global one. It might be called by the AccessSpec method - // to map itself to a local implementation or called directly. - // If called it should forward the call to the AccessSpec - // if and only if this specs NOT states to be local IsLocal()==false - // If the spec states to be local, the repository is responsible for - // providing a local implementation or return nil if this is - // not supported by the actual repository type. - AccessMethod(AccessSpec) (AccessMethod, error) - - // Update adds the version with all changes to the component instance it has been created for. - Update() error - - // Execute executes a function on a valid and locked component version reference. - // If it returns an error this error is forwarded. - Execute(func() error) error -} - -// ComponentLister provides the optional repository list functionality of -// a repository. -type ComponentLister interface { - // NumComponents returns the number of components found for a prefix - // If the given prefix does not end with a /, a repository with the - // prefix name is included - NumComponents(prefix string) (int, error) - - // GetNamespaces returns the name of namespaces found for a prefix - // If the given prefix does not end with a /, a repository with the - // prefix name is included - GetComponents(prefix string, closure bool) ([]string, error) -} diff --git a/pkg/contexts/ocm/internal/repotypes.go b/pkg/contexts/ocm/internal/repotypes.go deleted file mode 100644 index 1b2b3b8f1..000000000 --- a/pkg/contexts/ocm/internal/repotypes.go +++ /dev/null @@ -1,158 +0,0 @@ -package internal - -import ( - "encoding/json" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/generics" - "github.com/modern-go/reflect2" - - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -type RepositoryType interface { - runtime.VersionedTypedObjectType[RepositorySpec] - - // LocalSupportForAccessSpec checks whether a repository - // provides a local version for the access spec. - // LocalSupportForAccessSpec(ctx Context, a compdesc.AccessSpec) bool -} - -type IntermediateRepositorySpecAspect = oci.IntermediateRepositorySpecAspect - -type RepositorySpec interface { - runtime.VersionedTypedObject - - AsUniformSpec(Context) *UniformRepositorySpec - Repository(Context, credentials.Credentials) (Repository, error) -} - -type ( - RepositorySpecDecoder = runtime.TypedObjectDecoder[RepositorySpec] - RepositoryTypeProvider = runtime.KnownTypesProvider[RepositorySpec, RepositoryType] -) - -type RepositoryTypeScheme interface { - runtime.TypeScheme[RepositorySpec, RepositoryType] -} - -type _Scheme = runtime.TypeScheme[RepositorySpec, RepositoryType] - -type repositoryTypeScheme struct { - _Scheme -} - -func NewRepositoryTypeScheme(defaultDecoder RepositorySpecDecoder, base ...RepositoryTypeScheme) RepositoryTypeScheme { - scheme := runtime.MustNewDefaultTypeScheme[RepositorySpec, RepositoryType](&UnknownRepositorySpec{}, true, defaultDecoder, utils.Optional(base...)) - return &repositoryTypeScheme{scheme} -} - -func NewStrictRepositoryTypeScheme(base ...RepositoryTypeScheme) RepositoryTypeScheme { - scheme := runtime.MustNewDefaultTypeScheme[RepositorySpec, RepositoryType](nil, false, nil, utils.Optional(base...)) - return &repositoryTypeScheme{scheme} -} - -func (t *repositoryTypeScheme) KnownTypes() runtime.KnownTypes[RepositorySpec, RepositoryType] { - return t._Scheme.KnownTypes() // Goland -} - -// DefaultRepositoryTypeScheme contains all globally known access serializer. -var DefaultRepositoryTypeScheme = NewRepositoryTypeScheme(nil) - -func RegisterRepositoryType(atype RepositoryType) { - DefaultRepositoryTypeScheme.Register(atype) -} - -func CreateRepositorySpec(t runtime.TypedObject) (RepositorySpec, error) { - return DefaultRepositoryTypeScheme.Convert(t) -} - -//////////////////////////////////////////////////////////////////////////////// - -type UnknownRepositorySpec struct { - runtime.UnstructuredVersionedTypedObject `json:",inline"` -} - -var ( - _ RepositorySpec = &UnknownRepositorySpec{} - _ runtime.Unknown = &UnknownRepositorySpec{} -) - -func (_ *UnknownRepositorySpec) IsUnknown() bool { - return true -} - -func (r *UnknownRepositorySpec) AsUniformSpec(Context) *UniformRepositorySpec { - return UniformRepositorySpecForUnstructured(&r.UnstructuredVersionedTypedObject) -} - -func (r *UnknownRepositorySpec) Repository(Context, credentials.Credentials) (Repository, error) { - return nil, errors.ErrUnknown("repository type", r.GetType()) -} - -//////////////////////////////////////////////////////////////////////////////// - -type GenericRepositorySpec struct { - runtime.UnstructuredVersionedTypedObject `json:",inline"` -} - -var _ RepositorySpec = &GenericRepositorySpec{} - -func ToGenericRepositorySpec(spec RepositorySpec) (*GenericRepositorySpec, error) { - if reflect2.IsNil(spec) { - return nil, nil - } - if g, ok := spec.(*GenericRepositorySpec); ok { - return g, nil - } - data, err := json.Marshal(spec) - if err != nil { - return nil, err - } - return newGenericRepositorySpec(data, runtime.DefaultJSONEncoding) -} - -func NewGenericRepositorySpec(data []byte, unmarshaler runtime.Unmarshaler) (RepositorySpec, error) { - return generics.CastPointerR[RepositorySpec](newGenericRepositorySpec(data, unmarshaler)) -} - -func newGenericRepositorySpec(data []byte, unmarshaler runtime.Unmarshaler) (*GenericRepositorySpec, error) { - unstr := &runtime.UnstructuredVersionedTypedObject{} - if unmarshaler == nil { - unmarshaler = runtime.DefaultYAMLEncoding - } - err := unmarshaler.Unmarshal(data, unstr) - if err != nil { - return nil, err - } - return &GenericRepositorySpec{*unstr}, nil -} - -func (s *GenericRepositorySpec) AsUniformSpec(ctx Context) *UniformRepositorySpec { - eff, err := s.Evaluate(ctx) - if err != nil { - return &UniformRepositorySpec{Type: s.GetKind()} - } - return eff.AsUniformSpec(ctx) -} - -func (s *GenericRepositorySpec) Evaluate(ctx Context) (RepositorySpec, error) { - raw, err := s.GetRaw() - if err != nil { - return nil, err - } - return ctx.RepositoryTypes().Decode(raw, runtime.DefaultJSONEncoding) -} - -func (s *GenericRepositorySpec) Repository(ctx Context, creds credentials.Credentials) (Repository, error) { - spec, err := s.Evaluate(ctx) - if err != nil { - return nil, err - } - return spec.Repository(ctx, creds) -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/pkg/contexts/ocm/internal/resolver.go b/pkg/contexts/ocm/internal/resolver.go deleted file mode 100644 index f08e0e6cf..000000000 --- a/pkg/contexts/ocm/internal/resolver.go +++ /dev/null @@ -1,195 +0,0 @@ -package internal - -import ( - "strings" - "sync" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/finalizer" - "github.com/mandelsoft/goutils/general" - "golang.org/x/exp/slices" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/refmgmt" - "github.com/open-component-model/ocm/pkg/registrations" - "github.com/open-component-model/ocm/pkg/utils" -) - -//////////////////////////////////////////////////////////////////////////////// - -type ResolverRule struct { - prefix string - path registrations.NamePath - spec RepositorySpec - prio int -} - -func (r *ResolverRule) GetPrefix() string { - return r.prefix -} - -func (r *ResolverRule) GetSpecification() RepositorySpec { - return r.spec -} - -func (r *ResolverRule) GetPriority() int { - return r.prio -} - -// RepositoryCache is a utility object intended to be used by higher level objects such as session or resolver. Since -// the closing of the repository objects depends on the usage context (e.g. if components have been looked up in this -// repository, these components have to be closed before the repository can be closed), it is the responsibility of the -// higher level objects to close the repositories correctly. -type RepositoryCache struct { - lock sync.Mutex - repositories map[datacontext.ObjectKey]Repository -} - -func NewRepositoryCache() *RepositoryCache { - return &RepositoryCache{ - repositories: map[datacontext.ObjectKey]Repository{}, - } -} - -func (c *RepositoryCache) Reset() { - c.lock.Lock() - defer c.lock.Unlock() - - c.repositories = map[datacontext.ObjectKey]Repository{} -} - -func (c *RepositoryCache) LookupRepository(ctx Context, spec RepositorySpec) (Repository, bool, error) { - spec, err := ctx.RepositoryTypes().Convert(spec) - if err != nil { - return nil, false, err - } - keyName, err := utils.Key(spec) - if err != nil { - return nil, false, err - } - key := datacontext.ObjectKey{ - Object: ctx, - Name: keyName, - } - - c.lock.Lock() - defer c.lock.Unlock() - - if r := c.repositories[key]; r != nil { - return r, true, nil - } - repo, err := ctx.RepositoryForSpec(spec) - if err != nil { - return nil, false, err - } - c.repositories[key] = repo - return repo, false, err -} - -func NewResolverRule(prefix string, spec RepositorySpec, prio ...int) *ResolverRule { - p := registrations.NewNamePath(prefix) - return &ResolverRule{ - prefix: prefix, - path: p, - spec: spec, - prio: general.OptionalDefaulted(10, prio...), - } -} - -func (r *ResolverRule) Compare(o *ResolverRule) int { - if d := r.prio - o.prio; d != 0 { - return d - } - return r.path.Compare(o.path) -} - -func (r *ResolverRule) Match(name string) bool { - return r.prefix == "" || r.prefix == name || strings.HasPrefix(name, r.prefix+"/") -} - -// MatchingResolver hosts rule to match component version names. -// Matched names will be mapped to a specification for repository -// which should be used to look up the component version. -// Therefore, it keeps a reference to the context to use. -// -// ATTENTION: Because such an object is used by the context -// implementation, the context must be kept as ContextProvider -// to provide context views to outbound calls. -type MatchingResolver struct { - lock sync.Mutex - ctx ContextProvider - finalize finalizer.Finalizer - cache *RepositoryCache - rules []*ResolverRule -} - -func NewMatchingResolver(ctx ContextProvider, rules ...*ResolverRule) *MatchingResolver { - return &MatchingResolver{ - lock: sync.Mutex{}, - ctx: ctx, - cache: NewRepositoryCache(), - rules: nil, - } -} - -func (r *MatchingResolver) OCMContext() Context { - return r.ctx.OCMContext() -} - -func (r *MatchingResolver) Finalize() error { - r.lock.Lock() - defer r.lock.Unlock() - defer r.cache.Reset() - return r.finalize.Finalize() -} - -func (r *MatchingResolver) GetRules() []*ResolverRule { - r.lock.Lock() - defer r.lock.Unlock() - return slices.Clone(r.rules) -} - -func (r *MatchingResolver) AddRule(prefix string, spec RepositorySpec, prio ...int) { - r.lock.Lock() - defer r.lock.Unlock() - - rule := NewResolverRule(prefix, spec, prio...) - found := len(r.rules) - for i, o := range r.rules { - if o.Compare(rule) < 0 { - found = i - break - } - } - r.rules = slices.Insert(r.rules, found, rule) -} - -func (r *MatchingResolver) LookupComponentVersion(name string, version string) (ComponentVersionAccess, error) { - r.lock.Lock() - defer r.lock.Unlock() - - ctx := r.ctx.OCMContext() - for _, rule := range r.rules { - if rule.Match(name) { - repo, cached, err := r.cache.LookupRepository(ctx, rule.spec) - if err != nil { - return nil, err - } - if !cached { - // Even though the matching resolver is closed, there might be components or component versions, which - // contain a reference to the repository. Still, it shall be possible to close the matching resolver. - refmgmt.Lazy(repo) - r.finalize.Close(repo) - } - cv, err := repo.LookupComponentVersion(name, version) - if err == nil && cv != nil { - return cv, nil - } - if !errors.IsErrNotFoundKind(err, KIND_COMPONENTVERSION) { - return nil, err - } - } - } - return nil, errors.ErrNotFound(KIND_COMPONENTVERSION, common.NewNameVersion(name, version).String()) -} diff --git a/pkg/contexts/ocm/internal/uniform.go b/pkg/contexts/ocm/internal/uniform.go deleted file mode 100644 index 0d77a7902..000000000 --- a/pkg/contexts/ocm/internal/uniform.go +++ /dev/null @@ -1,212 +0,0 @@ -package internal - -import ( - "encoding/json" - "fmt" - "sync" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/maputils" - "github.com/sirupsen/logrus" - "golang.org/x/exp/slices" - - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - dockerHubDomain = "docker.io" - dockerHubLegacyDomain = "index.docker.io" -) - -// UniformRepositorySpec is generic specification of the repository -// for handling as part of standard references. -type UniformRepositorySpec struct { - // Type - Type string `json:"type,omitempty"` - // Scheme - Scheme string `json:"scheme,omitempty"` - // Host is the hostname of an ocm ref. - Host string `json:"host,omitempty"` - // SubPath is the sub path spec used to host component versions - SubPath string `json:"subPath,omitempty"` - // Info is the file path used to host ctf component versions - Info string `json:"filePath,omitempty"` - - // CreateIfMissing indicates whether a file based or dynamic repo should be created if it does not exist - CreateIfMissing bool `json:"createIfMissing,omitempty"` - // TypeHint should be set if CreateIfMissing is true to help to decide what kind of repo to create - TypeHint string `json:"typeHint,omitempty"` -} - -// CredHost fallback to legacy docker domain if applicable -// this is how containerd translates the old domain for DockerHub to the new one, taken from containerd/reference/docker/reference.go:674. -func (r *UniformRepositorySpec) CredHost() string { - if r.Host == dockerHubDomain { - return dockerHubLegacyDomain - } - return r.Host -} - -func (u *UniformRepositorySpec) String() string { - t := u.Type - if t != "" { - t += "::" - } - if u.Info != "" { - return fmt.Sprintf("%s%s", t, u.Info) - } else { - s := u.SubPath - if s != "" { - s = "/" + s - } - return fmt.Sprintf("%s%s%s", t, u.Host, s) - } -} - -func UniformRepositorySpecForUnstructured(un *runtime.UnstructuredVersionedTypedObject) *UniformRepositorySpec { - m := un.Object.FlatCopy() - delete(m, runtime.ATTR_TYPE) - - d, err := json.Marshal(m) - if err != nil { - logrus.Error(err) - } - - return &UniformRepositorySpec{Type: un.Type, Info: string(d)} -} - -type RepositorySpecHandler interface { - MapReference(ctx Context, u *UniformRepositorySpec) (RepositorySpec, error) -} - -type RepositorySpecHandlers interface { - Register(hdlr RepositorySpecHandler, types ...string) - Copy() RepositorySpecHandlers - KnownTypeNames() []string - GetHandlers(typ string) []RepositorySpecHandler - MapUniformRepositorySpec(ctx Context, u *UniformRepositorySpec) (RepositorySpec, error) -} - -var DefaultRepositorySpecHandlers = NewRepositorySpecHandlers() - -func RegisterRepositorySpecHandler(hdlr RepositorySpecHandler, types ...string) { - DefaultRepositorySpecHandlers.Register(hdlr, types...) -} - -type specHandlers struct { - lock sync.RWMutex - handlers map[string][]RepositorySpecHandler -} - -func NewRepositorySpecHandlers() RepositorySpecHandlers { - return &specHandlers{handlers: map[string][]RepositorySpecHandler{}} -} - -func (s *specHandlers) Register(hdlr RepositorySpecHandler, types ...string) { - s.lock.Lock() - defer s.lock.Unlock() - - if hdlr != nil { - for _, typ := range types { - s.handlers[typ] = append(s.handlers[typ], hdlr) - } - } -} - -func (s *specHandlers) Copy() RepositorySpecHandlers { - s.lock.RLock() - defer s.lock.RUnlock() - - n := NewRepositorySpecHandlers().(*specHandlers) - for typ, hdlrs := range s.handlers { - n.handlers[typ] = slices.Clone(hdlrs) - } - return n -} - -func (s *specHandlers) KnownTypeNames() []string { - s.lock.RLock() - defer s.lock.RUnlock() - - return maputils.OrderedKeys(s.handlers) -} - -func (s *specHandlers) GetHandlers(typ string) []RepositorySpecHandler { - s.lock.RLock() - defer s.lock.RUnlock() - - hdlrs := s.handlers[typ] - if len(hdlrs) == 0 { - return nil - } - result := make([]RepositorySpecHandler, len(hdlrs)) - copy(result, hdlrs) - return result -} - -func (s *specHandlers) MapUniformRepositorySpec(ctx Context, u *UniformRepositorySpec) (RepositorySpec, error) { - var err error - s.lock.RLock() - defer s.lock.RUnlock() - - if u.Info != "" && string(u.Info[0]) == "{" && u.Host == "" && u.Scheme == "" && u.SubPath == "" { - data, err := runtime.CompleteSpecWithType(u.Type, []byte(u.Info)) - if err != nil { - return nil, err - } - return ctx.RepositorySpecForConfig(data, runtime.DefaultJSONEncoding) - } - - deferr := errors.ErrNotSupported("uniform repository ref", u.String()) - if u.Type == "" { - if u.Info != "" { - spec := ctx.GetAlias(u.Info) - if spec != nil { - return spec, nil - } - deferr = errors.ErrUnknown("repository", u.Info) - } - if u.Host != "" { - spec := ctx.GetAlias(u.Host) - if spec != nil { - return spec, nil - } - deferr = errors.ErrUnknown("repository", u.Host) - } - } - - spec, err := s.handle(ctx, u.Type, u) - if err != nil || spec != nil { - return spec, err - } - - if u.Info != "" { - spec := &runtime.UnstructuredVersionedTypedObject{} - err = runtime.DefaultJSONEncoding.Unmarshal([]byte(u.Info), spec) - if err == nil { - if spec.GetType() == spec.GetKind() && spec.GetVersion() == "v1" { // only type set, use it as version - spec.SetType(u.Type + runtime.VersionSeparator + spec.GetType()) - } - if spec.GetKind() != u.Type { - return nil, errors.ErrInvalid() - } - return ctx.RepositoryTypes().Convert(spec) - } - } - - spec, err = s.handle(ctx, "*", u) - if spec == nil && err == nil { - err = deferr - } - return spec, err -} - -func (s *specHandlers) handle(ctx Context, typ string, u *UniformRepositorySpec) (RepositorySpec, error) { - for _, h := range s.handlers[typ] { - spec, err := h.MapReference(ctx, u) - if err != nil || spec != nil { - return spec, err - } - } - return nil, nil -} diff --git a/pkg/contexts/ocm/labels/init.go b/pkg/contexts/ocm/labels/init.go deleted file mode 100644 index a4af271d8..000000000 --- a/pkg/contexts/ocm/labels/init.go +++ /dev/null @@ -1,5 +0,0 @@ -package labels - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip/types" -) diff --git a/pkg/contexts/ocm/labels/routingslip/entry.go b/pkg/contexts/ocm/labels/routingslip/entry.go deleted file mode 100644 index 33f71a868..000000000 --- a/pkg/contexts/ocm/labels/routingslip/entry.go +++ /dev/null @@ -1,93 +0,0 @@ -package routingslip - -import ( - "fmt" - "strings" - - "github.com/mandelsoft/goutils/errors" - "github.com/opencontainers/go-digest" - - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip/internal" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/norm/jcs" -) - -func AsGenericEntry(u *runtime.UnstructuredTypedObject) *GenericEntry { - return internal.AsGenericEntry(u) -} - -func ToGenericEntry(e Entry) (*GenericEntry, error) { - return internal.ToGenericEntry(e) -} - -func NewGenericEntryWith(typ string, attrs ...interface{}) (*GenericEntry, error) { - r := map[string]interface{}{} - i := 0 - for len(attrs) > i { - n, ok := attrs[i].(string) - if !ok { - return nil, errors.ErrInvalid("key type", fmt.Sprintf("%T", attrs[i])) - } - r[n] = attrs[i+1] - i += 2 - } - return NewGenericEntry(typ, r) -} - -func NewGenericEntry(typ string, data interface{}) (*GenericEntry, error) { - u, err := runtime.ToUnstructuredTypedObject(data) - if err != nil { - return nil, err - } - if typ != "" { - u.SetType(typ) - } - return AsGenericEntry(u), nil -} - -var excludes = signing.MapExcludes{ - "digest": nil, - "signature": nil, -} - -type HistoryEntries = []HistoryEntry - -type HistoryEntry struct { - Payload *GenericEntry `json:"payload"` - Timestamp metav1.Timestamp `json:"timestamp"` - Parent *digest.Digest `json:"parent,omitempty"` - Links []Link `json:"links,omitempty"` - Digest digest.Digest `json:"digest"` - Signature *metav1.SignatureSpec `json:"signature,omitempty"` -} - -func (e *HistoryEntry) Normalize() ([]byte, error) { - return signing.Normalize(jcs.New(), e, excludes) -} - -func (e *HistoryEntry) CalculateDigest() (digest.Digest, error) { - data, err := e.Normalize() - if err != nil { - return "", err - } - return digest.SHA256.FromBytes(data), nil -} - -type Link struct { - Name string `json:"name"` - Digest digest.Digest `json:"digest"` -} - -func (l Link) Compare(o Link) int { - r := strings.Compare(l.Name, o.Name) - if r == 0 { - r = strings.Compare(l.Digest.String(), o.Digest.String()) - } - return r -} - -func CreateEntry(t runtime.VersionedTypedObject) (Entry, error) { - return internal.CreateEntry(t) -} diff --git a/pkg/contexts/ocm/labels/routingslip/init.go b/pkg/contexts/ocm/labels/routingslip/init.go deleted file mode 100644 index f4d190177..000000000 --- a/pkg/contexts/ocm/labels/routingslip/init.go +++ /dev/null @@ -1,5 +0,0 @@ -package routingslip - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip/types" -) diff --git a/pkg/contexts/ocm/labels/routingslip/interface.go b/pkg/contexts/ocm/labels/routingslip/interface.go deleted file mode 100644 index 1a97f0c6c..000000000 --- a/pkg/contexts/ocm/labels/routingslip/interface.go +++ /dev/null @@ -1,26 +0,0 @@ -package routingslip - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip/internal" -) - -type ( - Context = internal.Context - ContextProvider = ocm.ContextProvider - EntryTypeScheme = internal.EntryTypeScheme - Entry = internal.Entry - GenericEntry = internal.GenericEntry -) - -type SlipAccess interface { - Get(name string) (*RoutingSlip, error) -} - -func DefaultEntryTypeScheme() EntryTypeScheme { - return internal.DefaultEntryTypeScheme() -} - -func For(ctx ContextProvider) EntryTypeScheme { - return internal.For(ctx) -} diff --git a/pkg/contexts/ocm/labels/routingslip/internal/attr.go b/pkg/contexts/ocm/labels/routingslip/internal/attr.go deleted file mode 100644 index d77cd3f7a..000000000 --- a/pkg/contexts/ocm/labels/routingslip/internal/attr.go +++ /dev/null @@ -1,25 +0,0 @@ -package internal - -import ( - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" -) - -//////////////////////////////////////////////////////////////////////////////// - -const ATTR_ROUTINGSLIP_ENTRYTYPES = "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip" - -func For(ctx cpi.ContextProvider) EntryTypeScheme { - if ctx == nil { - return DefaultEntryTypeScheme() - } - return ctx.OCMContext().GetAttributes().GetOrCreateAttribute(ATTR_ROUTINGSLIP_ENTRYTYPES, create).(EntryTypeScheme) -} - -func create(datacontext.Context) interface{} { - return NewEntryTypeScheme(DefaultEntryTypeScheme()) -} - -func SetFor(ctx datacontext.Context, registry EntryTypeScheme) { - ctx.GetAttributes().SetAttribute(ATTR_ROUTINGSLIP_ENTRYTYPES, registry) -} diff --git a/pkg/contexts/ocm/labels/routingslip/label.go b/pkg/contexts/ocm/labels/routingslip/label.go deleted file mode 100644 index d01714c0b..000000000 --- a/pkg/contexts/ocm/labels/routingslip/label.go +++ /dev/null @@ -1,109 +0,0 @@ -package routingslip - -import ( - "sort" - - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/handlers/maplistmerge" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/handlers/simplemapmerge" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/hpi" - "github.com/open-component-model/ocm/pkg/utils" -) - -const NAME = "routing-slips" - -type LabelValue map[string]HistoryEntries - -var spec = utils.Must(hpi.NewSpecification( - simplemapmerge.ALGORITHM, - simplemapmerge.NewConfig( - "", - utils.Must(hpi.NewSpecification( - maplistmerge.ALGORITHM, - maplistmerge.NewConfig("digest", maplistmerge.MODE_INBOUND), - )), - )), -) - -func init() { - hpi.Assign(hpi.LabelHint(NAME), spec) -} - -func (l LabelValue) Has(name string) bool { - return l[name] != nil -} - -func (l LabelValue) Get(name string) (*RoutingSlip, error) { - return NewRoutingSlip(name, l) -} - -func (l LabelValue) Query(name string) (*RoutingSlip, error) { - a := l[name] - if a == nil { - return nil, nil - } - return l.Get(name) -} - -func (l LabelValue) Leaves() []Link { - var links []Link - - for k := range l { - s, err := l.Get(k) - if err == nil { - for _, d := range s.Leaves() { - links = append(links, Link{ - Name: k, - Digest: d, - }) - } - } - } - sort.Slice(links, func(i, j int) bool { return links[i].Compare(links[j]) < 0 }) - return links -} - -func (l LabelValue) Set(slip *RoutingSlip) { - l[slip.name] = slip.entries -} - -func AddEntry(cv cpi.ComponentVersionAccess, name string, algo string, e Entry, links []Link, parent ...digest.Digest) (*HistoryEntry, error) { - var label LabelValue - _, err := cv.GetDescriptor().Labels.GetValue(NAME, &label) - if err != nil { - return nil, err - } - if label == nil { - label = LabelValue{} - } - slip, err := label.Get(name) - if err != nil { - return nil, err - } - entry, err := slip.Add(cv.GetContext(), name, algo, e, links, parent...) - if err != nil { - return nil, err - } - label.Set(slip) - - err = Set(cv, label) - if err != nil { - return nil, err - } - return entry, nil -} - -func Get(cv cpi.ComponentVersionAccess) (LabelValue, error) { - var label LabelValue - _, err := cv.GetDescriptor().Labels.GetValue(NAME, &label) - if err != nil { - return nil, err - } - return label, nil -} - -func Set(cv cpi.ComponentVersionAccess, label LabelValue) error { - return cv.GetDescriptor().Labels.SetValue(NAME, label) -} diff --git a/pkg/contexts/ocm/labels/routingslip/spi/entrytype_options.go b/pkg/contexts/ocm/labels/routingslip/spi/entrytype_options.go deleted file mode 100644 index ce3c8fca0..000000000 --- a/pkg/contexts/ocm/labels/routingslip/spi/entrytype_options.go +++ /dev/null @@ -1,20 +0,0 @@ -package spi - -import ( - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets/flagsetscheme" -) - -type EntryTypeOption = flagsetscheme.TypeOption - -func WithFormatSpec(value string) EntryTypeOption { - return flagsetscheme.WithFormatSpec(value) -} - -func WithDescription(value string) EntryTypeOption { - return flagsetscheme.WithDescription(value) -} - -func WithConfigHandler(value flagsets.ConfigOptionTypeSetHandler) EntryTypeOption { - return flagsetscheme.WithConfigHandler(value) -} diff --git a/pkg/contexts/ocm/labels/routingslip/spi/interface.go b/pkg/contexts/ocm/labels/routingslip/spi/interface.go deleted file mode 100644 index 983132175..000000000 --- a/pkg/contexts/ocm/labels/routingslip/spi/interface.go +++ /dev/null @@ -1,28 +0,0 @@ -package spi - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip/internal" - "github.com/open-component-model/ocm/pkg/runtime" -) - -type ( - Context = cpi.Context - Entry = internal.Entry - UnknownEntry = internal.UnknownEntry - GenericEntry = internal.GenericEntry - EntryType = internal.EntryType - EntryTypeScheme = internal.EntryTypeScheme -) - -func NewStrictEntryTypeScheme() runtime.VersionedTypeRegistry[Entry, EntryType] { - return internal.NewStrictEntryTypeScheme() -} - -func DefaultEntryTypeScheme() EntryTypeScheme { - return internal.DefaultEntryTypeScheme() -} - -func For(ctx cpi.ContextProvider) EntryTypeScheme { - return internal.For(ctx) -} diff --git a/pkg/contexts/ocm/labels/routingslip/spi/support.go b/pkg/contexts/ocm/labels/routingslip/spi/support.go deleted file mode 100644 index cd410de50..000000000 --- a/pkg/contexts/ocm/labels/routingslip/spi/support.go +++ /dev/null @@ -1,48 +0,0 @@ -package spi - -import ( - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets/flagsetscheme" - "github.com/open-component-model/ocm/pkg/runtime" -) - -type EntryTypeVersionScheme = runtime.TypeVersionScheme[Entry, EntryType] - -func NewEntryTypeVersionScheme(kind string) EntryTypeVersionScheme { - return runtime.NewTypeVersionScheme[Entry, EntryType](kind, NewStrictEntryTypeScheme()) -} - -//////////////////////////////////////////////////////////////////////////////// - -type EntryFormatVersionRegistry = runtime.FormatVersionRegistry[Entry] - -func NewEntryFormatVersionRegistry() EntryFormatVersionRegistry { - return runtime.NewFormatVersionRegistry[Entry]() -} - -func MustNewEntryMultiFormatVersion(kind string, formats EntryFormatVersionRegistry) runtime.FormatVersion[Entry] { - return runtime.MustNewMultiFormatVersion[Entry](kind, formats) -} - -//////////////////////////////////////////////////////////////////////////////// - -func NewEntryType[I Entry](name string, opts ...EntryTypeOption) EntryType { - return flagsetscheme.NewTypedObjectTypeObject[Entry](runtime.NewVersionedTypedObjectType[Entry, I](name), opts...) -} - -func NewEntryTypeByConverter[I Entry, V runtime.VersionedTypedObject](name string, converter runtime.Converter[I, V], opts ...EntryTypeOption) EntryType { - return flagsetscheme.NewTypedObjectTypeObject[Entry](runtime.NewVersionedTypedObjectTypeByConverter[Entry, I, V](name, converter), opts...) -} - -func NewEntryTypeByFormatVersion(name string, fmt runtime.FormatVersion[Entry], opts ...EntryTypeOption) EntryType { - return flagsetscheme.NewTypedObjectTypeObject[Entry](runtime.NewVersionedTypedObjectTypeByFormatVersion[Entry](name, fmt), opts...) -} - -//////////////////////////////////////////////////////////////////////////////// - -func Register(atype EntryType) { - DefaultEntryTypeScheme().Register(atype) -} - -func RegisterEntryTypeVersions(s EntryTypeVersionScheme) { - DefaultEntryTypeScheme().AddKnownTypes(s) -} diff --git a/pkg/contexts/ocm/labels/routingslip/transfer_test.go b/pkg/contexts/ocm/labels/routingslip/transfer_test.go deleted file mode 100644 index 74b892b6c..000000000 --- a/pkg/contexts/ocm/labels/routingslip/transfer_test.go +++ /dev/null @@ -1,108 +0,0 @@ -package routingslip_test - -import ( - "fmt" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/mandelsoft/goutils/finalizer" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/common/accessobj" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/compositionmodeattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip" - "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip/types/comment" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" - "github.com/open-component-model/ocm/pkg/env/builder" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" -) - -const ( - ARCH = "/tmp/ctf" - TARGET = "/tmp/target" - COMPONENT = "acme.org/routingslip" - VERSION = "1.0.0" - LOCAL = "local.org" -) - -var _ = Describe("management", func() { - var env *builder.Builder - - BeforeEach(func() { - env = builder.NewBuilder() - env.RSAKeyPair(ORG, LOCAL) - }) - - AfterEach(func() { - env.Cleanup() - }) - - DescribeTable("transfers and updates", func(mode bool) { - var finalize finalizer.Finalizer - - defer Defer(finalize.Finalize, "finalizer") - - compositionmodeattr.Set(env.OCMContext(), mode) - e1 := comment.New("start of routing slip") - e2 := comment.New("additional entry") - - repo := Must(ctf.Open(env, accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, ARCH, 0o700, env)) - finalize.Close(repo, "repo") - - c := Must(repo.LookupComponent(COMPONENT)) - finalize.Close(c, "comp") - cv := Must(c.NewVersion(VERSION)) - finalize.Close(cv, "vers") - cv.GetDescriptor().Provider.Name = ORG - MustBeSuccessful(routingslip.AddEntry(cv, ORG, rsa.Algorithm, e1, nil)) - MustBeSuccessful(c.AddVersion(cv)) - - target := Must(ctf.Open(env, accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, TARGET, 0o700, env)) - finalize.Close(target, "target") - pr, buf := common.NewBufferedPrinter() - - MustBeSuccessful(transfer.TransferVersion(pr, nil, cv, target, Must(standard.New()))) - - Expect(buf.String()).To(StringEqualTrimmedWithContext(` -transferring version "acme.org/routingslip:1.0.0"... -...adding component version... -`)) - nested := finalize.Nested() - tc := Must(target.LookupComponent(COMPONENT)) - nested.Close(tc, "target comp") - tcv := Must(tc.LookupVersion(VERSION)) - nested.Close(tcv) - - slip := Must(routingslip.GetSlip(tcv, ORG)) - MustBeSuccessful(routingslip.AddEntry(tcv, LOCAL, rsa.Algorithm, e1, nil)) - Expect(slip.Len()).To(Equal(1)) - - MustBeSuccessful(tc.AddVersion(tcv)) - MustBeSuccessful(nested.Finalize()) - - buf.Reset() - MustBeSuccessful(routingslip.AddEntry(cv, ORG, rsa.Algorithm, e2, nil)) - MustBeSuccessful(transfer.TransferVersion(pr, nil, cv, target, Must(standard.New()))) - Expect(buf.String()).To(StringEqualTrimmedWithContext(` -transferring version "acme.org/routingslip:1.0.0"... - updating volatile properties of "acme.org/routingslip:1.0.0" -...adding component version... -`)) - - tcv = Must(target.LookupComponentVersion(COMPONENT, VERSION)) - finalize.Close(tcv, "target") - label := Must(routingslip.Get(tcv)) - Expect(len(label)).To(Equal(2)) - Expect(len(label[ORG])).To(Equal(2)) - Expect(len(label[LOCAL])).To(Equal(1)) - fmt.Printf("*** routing slips:\n%s\n", Must(runtime.DefaultYAMLEncoding.Marshal(label))) - }, - Entry("with direct mode", false), - Entry("with composition mode", true), - ) -}) diff --git a/pkg/contexts/ocm/labels/routingslip/types/comment/cli.go b/pkg/contexts/ocm/labels/routingslip/types/comment/cli.go deleted file mode 100644 index 257d9d0b8..000000000 --- a/pkg/contexts/ocm/labels/routingslip/types/comment/cli.go +++ /dev/null @@ -1,30 +0,0 @@ -package comment - -import ( - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/options" -) - -func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { - return flagsets.NewConfigOptionTypeSetHandler( - Type, AddConfig, - options.CommentOption, - ) -} - -func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { - flagsets.AddFieldByOptionP(opts, options.CommentOption, config, "comment") - return nil -} - -var usage = ` -An unstructured comment as entry in a routing slip. -` - -var formatV1 = ` -The type specific specification fields are: - -- **comment** *string* - - Any text as entry in a routing slip. -` diff --git a/pkg/contexts/ocm/labels/routingslip/types/comment/entry.go b/pkg/contexts/ocm/labels/routingslip/types/comment/entry.go deleted file mode 100644 index 740c07fc1..000000000 --- a/pkg/contexts/ocm/labels/routingslip/types/comment/entry.go +++ /dev/null @@ -1,45 +0,0 @@ -package comment - -import ( - "fmt" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip/spi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -// Type is the access type for a blob in an OCI repository. -const ( - Type = "comment" - TypeV1 = Type + runtime.VersionSeparator + "v1" -) - -func init() { - spi.Register(spi.NewEntryType[*Entry](Type, spi.WithDescription(usage))) - spi.Register(spi.NewEntryType[*Entry](TypeV1, spi.WithFormatSpec(formatV1), spi.WithConfigHandler(ConfigHandler()))) -} - -// New creates a new Helm Chart accessor for helm repositories. -func New(comment string) *Entry { - return &Entry{ - ObjectVersionedType: runtime.NewVersionedTypedObject(Type), - Comment: comment, - } -} - -// Entry describes the access for a helm repository. -type Entry struct { - runtime.ObjectVersionedType `json:",inline"` - - // Comment is just a descriptive text in a routing slip- - Comment string `json:"comment"` -} - -var _ spi.Entry = (*Entry)(nil) - -func (a *Entry) Describe(ctx spi.Context) string { - return fmt.Sprintf("Comment: %s", a.Comment) -} - -func (a *Entry) Validate(spi.Context) error { - return nil -} diff --git a/pkg/contexts/ocm/labels/routingslip/types/init.go b/pkg/contexts/ocm/labels/routingslip/types/init.go deleted file mode 100644 index fcad65f3b..000000000 --- a/pkg/contexts/ocm/labels/routingslip/types/init.go +++ /dev/null @@ -1,5 +0,0 @@ -package types - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip/types/comment" -) diff --git a/pkg/contexts/ocm/labels/routingslip/types/plugin/cmd_test.go b/pkg/contexts/ocm/labels/routingslip/types/plugin/cmd_test.go deleted file mode 100644 index 5d416ea22..000000000 --- a/pkg/contexts/ocm/labels/routingslip/types/plugin/cmd_test.go +++ /dev/null @@ -1,74 +0,0 @@ -package plugin_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/testutils" - . "github.com/open-component-model/ocm/pkg/env" - - "github.com/mandelsoft/goutils/sliceutils" - "github.com/mandelsoft/goutils/transformer" - "github.com/spf13/pflag" - - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/options" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugincacheattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip" - "github.com/open-component-model/ocm/pkg/contexts/ocm/registration" -) - -const ( - ARCH = "/tmp/ca" - VERSION = "v1" - COMP = "test.de/x" - PROVIDER = "acme.org" -) - -var _ = Describe("Test Environment", func() { - var env *Environment - var plugins TempPluginDir - - BeforeEach(func() { - env = NewEnvironment(TestData()) - - ctx := env.OCMContext() - plugins = Must(ConfigureTestPlugins(env, "testdata")) - registry := plugincacheattr.Get(ctx) - Expect(registration.RegisterExtensions(ctx)).To(Succeed()) - p := registry.Get("test") - Expect(p).NotTo(BeNil()) - }) - - AfterEach(func() { - plugins.Cleanup() - env.Cleanup() - }) - - It("handles plugin based entry type", func() { - prov := routingslip.For(env.OCMContext()).CreateConfigTypeSetConfigProvider() - configopts := prov.CreateOptions() - Expect(sliceutils.Transform(configopts.Options(), transformer.GetName[flagsets.Option, string])).To(ConsistOf( - "entry", "comment", // default settings - "mediaType", "accessPath", // by plugin - )) - - fs := &pflag.FlagSet{} - fs.SortFlags = true - configopts.AddFlags(fs) - Expect("\n" + fs.FlagUsages()).To(Equal(` - --accessPath string file path - --comment string comment field value - --entry YAML routing slip entry specification (YAML) - --mediaType string media type for artifact blob representation -`)) - MustBeSuccessful(fs.Parse([]string{"--accessPath", "some path", "--" + options.MediatypeOption.GetName(), "media type"})) - prov.SetTypeName("test") - data := Must(prov.GetConfigFor(configopts)) - Expect(data).To(YAMLEqual(` -type: test -mediaType: media type -path: some path -`)) - }) -}) diff --git a/pkg/contexts/ocm/labels/routingslip/types/plugin/entry.go b/pkg/contexts/ocm/labels/routingslip/types/plugin/entry.go deleted file mode 100644 index 830a56f34..000000000 --- a/pkg/contexts/ocm/labels/routingslip/types/plugin/entry.go +++ /dev/null @@ -1,27 +0,0 @@ -package plugin - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip/spi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -type Entry struct { - runtime.UnstructuredVersionedTypedObject `json:",inline"` - handler *PluginHandler -} - -var _ spi.Entry = &Entry{} - -func (s *Entry) Describe(ctx cpi.Context) string { - return s.handler.Describe(s, ctx) -} - -func (s *Entry) Validate(ctx spi.Context) error { - _, err := s.handler.Validate(s) - return err -} - -func (s *Entry) Handler() *PluginHandler { - return s.handler -} diff --git a/pkg/contexts/ocm/labels/routingslip/types/plugin/plugin.go b/pkg/contexts/ocm/labels/routingslip/types/plugin/plugin.go deleted file mode 100644 index 485cf5ba5..000000000 --- a/pkg/contexts/ocm/labels/routingslip/types/plugin/plugin.go +++ /dev/null @@ -1,40 +0,0 @@ -package plugin - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" -) - -type plug = plugin.Plugin - -// PluginHandler is a shared object between the AccessMethod implementation and the Entry implementation. The -// object knows the actual plugin and can therefore forward the method calls to corresponding cli commands. -type PluginHandler struct { - plug -} - -func NewPluginHandler(p plugin.Plugin) *PluginHandler { - return &PluginHandler{plug: p} -} - -func (p *PluginHandler) Describe(spec *Entry, ctx cpi.Context) string { - sspec := p.GetValueSetDescriptor(descriptor.PURPOSE_ROUTINGSLIP, spec.GetKind(), spec.GetVersion()) - if sspec == nil { - return "unknown type " + spec.GetType() - } - info, err := p.Validate(spec) - if err != nil { - return err.Error() - } - return info.Short -} - -func (p *PluginHandler) Validate(spec *Entry) (*ppi.ValueSetInfo, error) { - data, err := spec.GetRaw() - if err != nil { - return nil, err - } - return p.plug.ValidateValueSet(descriptor.PURPOSE_ROUTINGSLIP, data) -} diff --git a/pkg/contexts/ocm/labels/routingslip/types/plugin/type.go b/pkg/contexts/ocm/labels/routingslip/types/plugin/type.go deleted file mode 100644 index 135d85b90..000000000 --- a/pkg/contexts/ocm/labels/routingslip/types/plugin/type.go +++ /dev/null @@ -1,70 +0,0 @@ -package plugin - -import ( - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/options" - "github.com/open-component-model/ocm/pkg/contexts/ocm/labels/routingslip/spi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" - "github.com/open-component-model/ocm/pkg/runtime" -) - -type entryType struct { - spi.EntryType - plug plugin.Plugin - cliopts flagsets.ConfigOptionTypeSet -} - -var _ spi.EntryType = (*entryType)(nil) - -func NewType(name string, p plugin.Plugin, desc *plugin.ValueSetDescriptor) spi.EntryType { - format := desc.Format - if format != "" { - format = "\n" + format - } - - t := &entryType{ - plug: p, - } - - cfghdlr := flagsets.NewConfigOptionTypeSetHandler(name, t.AddConfig) - for _, o := range desc.CLIOptions { - var opt flagsets.ConfigOptionType - if o.Type == "" { - opt = options.DefaultRegistry.GetOptionType(o.Name) - if opt == nil { - p.Context().Logger(plugin.TAG).Warn("unknown option", "plugin", p.Name(), "valueset", name, "option", o.Name) - } - } else { - var err error - opt, err = options.DefaultRegistry.CreateOptionType(o.Type, o.Name, o.Description) - if err != nil { - p.Context().Logger(plugin.TAG).Warn("invalid option", "plugin", p.Name(), "valueset", name, "option", o.Name, "error", err.Error()) - } - } - if opt != nil { - cfghdlr.AddOptionType(opt) - } - } - aopts := []spi.EntryTypeOption{spi.WithDescription(desc.Description), spi.WithFormatSpec(format)} - if cfghdlr.Size() > 0 { - aopts = append(aopts, spi.WithConfigHandler(cfghdlr)) - t.cliopts = cfghdlr - } - t.EntryType = spi.NewEntryType[*Entry](name, aopts...) - return t -} - -func (t *entryType) Decode(data []byte, unmarshaler runtime.Unmarshaler) (spi.Entry, error) { - spec, err := t.EntryType.Decode(data, unmarshaler) - if err != nil { - return nil, err - } - spec.(*Entry).handler = NewPluginHandler(t.plug) - return spec, nil -} - -func (t *entryType) AddConfig(opts flagsets.ConfigOptions, cfg flagsets.Config) error { - opts = opts.FilterBy(t.cliopts.HasOptionType) - return t.plug.ComposeValueSet(descriptor.PURPOSE_ROUTINGSLIP, t.GetType(), opts, cfg) -} diff --git a/pkg/contexts/ocm/labels/routingslip/usage.go b/pkg/contexts/ocm/labels/routingslip/usage.go deleted file mode 100644 index 077c5b89e..000000000 --- a/pkg/contexts/ocm/labels/routingslip/usage.go +++ /dev/null @@ -1,79 +0,0 @@ -package routingslip - -import ( - "fmt" - "strings" - - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -func EntryUsage(scheme EntryTypeScheme, cli bool) string { - s := ` -The following list describes the well-known entry types explicitly supported -by this version of the CLI, their versions and specification formats. Other -kinds of entries can be configured using the --entry option. -` - type method struct { - desc string - versions map[string]string - options flagsets.ConfigOptionTypeSetHandler - } - - descs := map[string]*method{} - - // gather info for kinds and versions - for _, n := range scheme.KnownTypeNames() { - kind, vers := runtime.KindVersion(n) - - info := descs[kind] - if info == nil { - info = &method{versions: map[string]string{}} - descs[kind] = info - } - - if vers == "" { - vers = "v1" - } - if _, ok := info.versions[vers]; !ok { - info.versions[vers] = "" - } - - t := scheme.GetType(n) - - if t.ConfigOptionTypeSetHandler() != nil { - info.options = t.ConfigOptionTypeSetHandler() - } - desc := t.Description() - if desc != "" { - info.desc = desc - } - - desc = t.Format() - if desc != "" { - info.versions[vers] = desc - } - } - - for _, t := range utils.StringMapKeys(descs) { - info := descs[t] - desc := strings.Trim(info.desc, "\n") - if desc != "" { - s = fmt.Sprintf("%s\n- Entry type %s\n\n%s\n\n", s, t, utils.IndentLines(desc, " ")) - - format := "" - for _, f := range utils.StringMapKeys(info.versions) { - desc = strings.Trim(info.versions[f], "\n") - if desc != "" { - format = fmt.Sprintf("%s\n- Version %s\n\n%s\n", format, f, utils.IndentLines(desc, " ")) - } - } - if format != "" { - s += fmt.Sprintf(" The following versions are supported:\n%s\n", strings.Trim(utils.IndentLines(format, " "), "\n")) - } - } - s += utils.IndentLines(flagsets.FormatConfigOptions(info.options), " ") - } - return s -} diff --git a/pkg/contexts/ocm/modopts.go b/pkg/contexts/ocm/modopts.go deleted file mode 100644 index 08a49c6bb..000000000 --- a/pkg/contexts/ocm/modopts.go +++ /dev/null @@ -1,118 +0,0 @@ -package ocm - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/hashattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" - "github.com/open-component-model/ocm/pkg/signing/hasher/sha256" -) - -type ( - TargetElement = internal.TargetElement - TargetOption = internal.TargetOption - TargetOptions = internal.TargetOptions - - ModificationOption = internal.ModificationOption - ModificationOptions = internal.ModificationOptions - - BlobModificationOption = internal.BlobModificationOption - BlobModificationOptions = internal.BlobModificationOptions - - BlobUploadOption = internal.BlobUploadOption - BlobUploadOptions = internal.BlobUploadOptions - - AddVersionOption = internal.AddVersionOption - AddVersionOptions = internal.AddVersionOptions -) - -//////////////////////////////////////////////////////////////////////////////// - -func NewAddVersionOptions(list ...AddVersionOption) *AddVersionOptions { - return internal.NewAddVersionOptions(list...) -} - -// Overwrite enabled the overwrite mode for adding a component version. -func Overwrite(flag ...bool) AddVersionOption { - return internal.Overwrite(flag...) -} - -//////////////////////////////////////////////////////////////////////////////// - -func NewBlobModificationOptions(list ...BlobModificationOption) *BlobModificationOptions { - return internal.NewBlobModificationOptions(list...) -} - -//////////////////////////////////////////////////////////////////////////////// - -func NewBlobUploadOptions(list ...BlobUploadOption) *BlobUploadOptions { - return internal.NewBlobUploadOptions(list...) -} - -func UseBlobHandlers(h BlobHandlerProvider) internal.BlobOptionImpl { - return internal.UseBlobHandlers(h) -} - -//////////////////////////////////////////////////////////////////////////////// - -func NewModificationOptions(list ...ModificationOption) *ModificationOptions { - return internal.NewModificationOptions(list...) -} - -func TargetIndex(idx int) internal.TargetOptionImpl { - return internal.TargetIndex(-1) -} - -const AppendElement = internal.TargetIndex(-1) - -var UpdateElement = internal.UpdateElement - -func TargetIdentity(id v1.Identity) internal.TargetOptionImpl { - return internal.TargetIdentity(id) -} - -func TargetIdentityOrCreate(id v1.Identity) internal.TargetOptionImpl { - return internal.TargetIdentityOrAppend(id) -} - -func ModifyResource(flag ...bool) internal.ModOptionImpl { - return internal.ModifyResource(flag...) -} - -func AcceptExistentDigests(flag ...bool) internal.ModOptionImpl { - return internal.AcceptExistentDigests(flag...) -} - -func WithDefaultHashAlgorithm(algo ...string) internal.ModOptionImpl { - return internal.WithDefaultHashAlgorithm(algo...) -} - -func WithHasherProvider(prov HasherProvider) internal.ModOptionImpl { - return internal.WithHasherProvider(prov) -} - -func SkipVerify(flag ...bool) internal.ModOptionImpl { - return internal.SkipVerify(flag...) -} - -// SkipDigest disables digest creation if enabled. -// -// Deprecated: for legacy code, only. -func SkipDigest(flag ...bool) internal.ModOptionImpl { - return internal.SkipDigest(flag...) -} - -/////////////////////////////////////////////////////// - -func CompleteModificationOptions(ctx ContextProvider, m *ModificationOptions) { - attr := hashattr.Get(ctx.OCMContext()) - if m.DefaultHashAlgorithm == "" { - m.DefaultHashAlgorithm = attr.DefaultHasher - } - if m.DefaultHashAlgorithm == "" { - m.DefaultHashAlgorithm = sha256.Algorithm - } - if m.HasherProvider == nil { - m.HasherProvider = signingattr.Get(ctx.OCMContext()) - } -} diff --git a/pkg/contexts/ocm/plugin/cache/exec.go b/pkg/contexts/ocm/plugin/cache/exec.go deleted file mode 100644 index f42135498..000000000 --- a/pkg/contexts/ocm/plugin/cache/exec.go +++ /dev/null @@ -1,70 +0,0 @@ -package cache - -import ( - "encoding/json" - "fmt" - "io" - "os/exec" - "strings" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds" -) - -func Exec(execpath string, config json.RawMessage, r io.Reader, w io.Writer, args ...string) ([]byte, error) { - if len(config) > 0 { - args = append([]string{"-c", string(config)}, args...) - } - cmd := exec.Command(execpath, args...) - stdout := w - if w == nil { - stdout = accessio.LimitBuffer(accessio.DESCRIPTOR_LIMIT) - } - - stderr := accessio.LimitBuffer(accessio.DESCRIPTOR_LIMIT) - - cmd.Stdin = r - cmd.Stdout = stdout - cmd.Stderr = stderr - - err := cmd.Run() - if err != nil { - var result cmds.Error - var cerr error - data := strings.TrimSpace(string(stderr.Bytes())) - if len(data) == 0 { - cerr = errors.New(err.Error()) - } else { - // handle implicit error output from go run - i := strings.LastIndex(data, "\n") - for i > 0 && (i == len(data)-1 || strings.HasPrefix(data[i+1:], "exit status")) { - data = data[:i] - i = strings.LastIndex(data, "\n") - } - if err := json.Unmarshal([]byte(data), &result); err == nil { - cerr = errors.New(result.Error) - } else { - if err := json.Unmarshal([]byte(data[i+1:]), &result); err == nil { - cerr = errors.New(result.Error) - // TODO pass effective stderr from CLI - data = strings.TrimSpace(data[:i]) - if len(data) > 0 { - cerr = fmt.Errorf("%w: with stderr\n%s", cerr, data) - } - } else { - cerr = fmt.Errorf("[%s]", data) - } - } - } - return nil, cerr - } - if l, ok := stdout.(*accessio.LimitedBuffer); ok { - if l.Exceeded() { - return nil, fmt.Errorf("stdout limit exceeded") - } - return l.Bytes(), nil - } - return nil, nil -} diff --git a/pkg/contexts/ocm/plugin/cache/plugin.go b/pkg/contexts/ocm/plugin/cache/plugin.go deleted file mode 100644 index 4a764a212..000000000 --- a/pkg/contexts/ocm/plugin/cache/plugin.go +++ /dev/null @@ -1,243 +0,0 @@ -package cache - -import ( - "golang.org/x/exp/slices" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" -) - -type Plugin = *pluginImpl - -// //nolint: errname // is no error. -type pluginImpl struct { - name string - info *PluginInstallationInfo - descriptor *descriptor.Descriptor - path string - error string - uploaders *ConstraintRegistry[descriptor.UploaderDescriptor, descriptor.UploaderKey] - downloaders *ConstraintRegistry[descriptor.DownloaderDescriptor, descriptor.DownloaderKey] -} - -func NewPlugin(name string, path string, desc *descriptor.Descriptor, errmsg string) Plugin { - p := &pluginImpl{ - name: name, - path: path, - descriptor: desc, - error: errmsg, - } - if desc != nil { - p.uploaders = NewConstraintRegistry[descriptor.UploaderDescriptor, descriptor.UploaderKey](desc.Uploaders) - p.downloaders = NewConstraintRegistry[descriptor.DownloaderDescriptor, descriptor.DownloaderKey](desc.Downloaders) - } else { - p.uploaders = NewConstraintRegistry[descriptor.UploaderDescriptor, descriptor.UploaderKey](nil) - p.downloaders = NewConstraintRegistry[descriptor.DownloaderDescriptor, descriptor.DownloaderKey](nil) - } - return p -} - -func (p *pluginImpl) GetSourceInfo() *PluginSourceInfo { - if p.info == nil { - return nil - } - if p.info.HasSourceInfo() { - return &p.info.PluginSourceInfo - } - return nil -} - -func (p *pluginImpl) GetDescriptor() *descriptor.Descriptor { - return p.descriptor -} - -func (p *pluginImpl) Name() string { - return p.name -} - -func (p *pluginImpl) Path() string { - return p.path -} - -func (p *pluginImpl) Version() string { - if !p.IsValid() { - return "-" - } - return p.descriptor.PluginVersion -} - -func (p *pluginImpl) IsValid() bool { - return p.descriptor != nil -} - -func (p *pluginImpl) Error() string { - return p.error -} - -func (p *pluginImpl) GetActionDescriptor(name string) *descriptor.ActionDescriptor { - if !p.IsValid() { - return nil - } - - for _, a := range p.descriptor.Actions { - if a.Name == name { - return &a - } - } - return nil -} - -func (p *pluginImpl) GetValueMergeHandlerDescriptor(name string) *descriptor.ValueMergeHandlerDescriptor { - if !p.IsValid() { - return nil - } - - for _, a := range p.descriptor.ValueMergeHandlers { - if a.Name == name { - return &a - } - } - return nil -} - -func (p *pluginImpl) GetValueMappingDescriptor(name string) *descriptor.ValueMergeHandlerDescriptor { - if !p.IsValid() { - return nil - } - - for _, a := range p.descriptor.ValueMergeHandlers { - if a.Name == name { - return &a - } - } - return nil -} - -func (p *pluginImpl) GetLabelMergeSpecification(name, version string) *descriptor.LabelMergeSpecification { - if !p.IsValid() { - return nil - } - - var fallback *descriptor.LabelMergeSpecification - for i, s := range p.descriptor.LabelMergeSpecifications { - if s.Name == name { - if s.Version == version { - return &s - } - if s.Version == "" { - fallback = &p.descriptor.LabelMergeSpecifications[i] - } - } - } - return fallback -} - -func (p *pluginImpl) GetAccessMethodDescriptor(name, version string) *descriptor.AccessMethodDescriptor { - if !p.IsValid() { - return nil - } - - var fallback descriptor.AccessMethodDescriptor - fallbackFound := false - for _, m := range p.descriptor.AccessMethods { - if m.Name == name { - if m.Version == version { - return &m - } - if m.Version == "" || m.Version == "v1" { - fallback = m - fallbackFound = true - } - } - } - if fallbackFound && (version == "" || version == "v1") { - return &fallback - } - return nil -} - -func (p *pluginImpl) GetValueSetDescriptor(purpose, name, version string) *descriptor.ValueSetDescriptor { - if !p.IsValid() { - return nil - } - - var fallback descriptor.ValueSetDescriptor - fallbackFound := false - for _, s := range p.descriptor.ValueSets { - if !slices.Contains(s.Purposes, purpose) { - continue - } - if s.Name == name { - if s.Version == version { - return &s - } - if s.Version == "" || s.Version == "v1" { - fallback = s - fallbackFound = true - } - } - } - if fallbackFound && (version == "" || version == "v1") { - return &fallback - } - return nil -} - -func (p *pluginImpl) LookupDownloader(name string, artType, mediaType string) descriptor.List[*descriptor.DownloaderDescriptor] { - if !p.IsValid() { - return nil - } - - return p.downloaders.LookupFor(name, descriptor.NewDownloaderKey(artType, mediaType)) -} - -func (p *pluginImpl) GetDownloaderDescriptor(name string) *descriptor.DownloaderDescriptor { - if !p.IsValid() { - return nil - } - return p.descriptor.Downloaders.Get(name) -} - -func (p *pluginImpl) LookupUploader(name string, artType, mediaType string) descriptor.List[*descriptor.UploaderDescriptor] { - if !p.IsValid() { - return nil - } - - return p.uploaders.LookupFor(name, descriptor.UploaderKey{}.SetArtifact(artType, mediaType)) -} - -func (p *pluginImpl) LookupUploadersForKeys(name string, keys descriptor.UploaderKeySet) descriptor.List[*descriptor.UploaderDescriptor] { - if !p.IsValid() { - return nil - } - - var r descriptor.List[*descriptor.UploaderDescriptor] - for k := range keys { - r = r.MergeWith(p.uploaders.LookupFor(name, k)) - } - return r -} - -func (p *pluginImpl) LookupUploaderKeys(name string, artType, mediaType string) descriptor.UploaderKeySet { - if !p.IsValid() { - return nil - } - - return p.uploaders.LookupKeysFor(name, descriptor.UploaderKey{}.SetArtifact(artType, mediaType)) -} - -func (p *pluginImpl) GetUploaderDescriptor(name string) *descriptor.UploaderDescriptor { - if !p.IsValid() { - return nil - } - return p.descriptor.Uploaders.Get(name) -} - -func (p *pluginImpl) Message() string { - if p.IsValid() { - return p.descriptor.Short - } - if p.error != "" { - return "Error: " + p.error - } - return "unknown state" -} diff --git a/pkg/contexts/ocm/plugin/cache/registry.go b/pkg/contexts/ocm/plugin/cache/registry.go deleted file mode 100644 index d6d2e2c2a..000000000 --- a/pkg/contexts/ocm/plugin/cache/registry.go +++ /dev/null @@ -1,66 +0,0 @@ -package cache - -import ( - "github.com/mandelsoft/goutils/set" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils/registry" -) - -type ConstraintRegistry[T any, K registry.Key[K]] struct { - mapping *registry.Registry[*T, K] - elems map[string]*registry.Registry[*T, K] -} - -func (r *ConstraintRegistry[T, K]) Lookup(key K) []*T { - return r.mapping.LookupHandler(key) -} - -func (r *ConstraintRegistry[T, K]) LookupKeys(key K) set.Set[K] { - return r.mapping.LookupKeys(key) -} - -func (r *ConstraintRegistry[T, K]) LookupFor(name string, key K) []*T { - if name == "" { - return r.Lookup(key) - } - m := r.elems[name] - if m == nil { - return nil - } - return m.LookupHandler(key) -} - -func (r *ConstraintRegistry[T, K]) LookupKeysFor(name string, key K) set.Set[K] { - if name == "" { - return r.LookupKeys(key) - } - m := r.elems[name] - if m == nil { - return nil - } - return m.LookupKeys(key) -} - -func NewConstraintRegistry[T descriptor.Element[K], K registry.Key[K]](list []T) *ConstraintRegistry[T, K] { - reg := registry.NewRegistry[*T, K]() - m := map[string]*registry.Registry[*T, K]{} - - for i := range list { - d := list[i] - nested := registry.NewRegistry[*T, K]() - if len(d.GetConstraints()) == 0 { - var zero K - nested.Register(zero, &d) - } else { - for _, c := range d.GetConstraints() { - if c.IsValid() { - reg.Register(c, &d) - nested.Register(c, &d) - } - } - } - m[d.GetName()] = nested - } - return &ConstraintRegistry[T, K]{reg, m} -} diff --git a/pkg/contexts/ocm/plugin/config/type.go b/pkg/contexts/ocm/plugin/config/type.go deleted file mode 100644 index a5d9c12fb..000000000 --- a/pkg/contexts/ocm/plugin/config/type.go +++ /dev/null @@ -1,67 +0,0 @@ -package config - -import ( - "encoding/json" - - "github.com/open-component-model/ocm/pkg/contexts/config" - cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - ConfigType = "plugin" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX - ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" -) - -func init() { - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1, usage)) -} - -// Config describes a memory based config interface for plugins. -type Config struct { - runtime.ObjectVersionedType `json:",inline"` - Plugin string `json:"plugin"` - Config json.RawMessage `json:"config,omitempty"` - DisableAutoRegistration bool `json:"disableAutoRegistration,omitempty"` -} - -// New creates a new memory ConfigSpec. -func New(name string, data []byte) *Config { - return &Config{ - ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), - Plugin: name, - Config: data, - } -} - -func (a *Config) GetType() string { - return ConfigType -} - -func (a *Config) ApplyTo(ctx config.Context, target interface{}) error { - t, ok := target.(Target) - if !ok { - return config.ErrNoContext(ConfigType) - } - t.ConfigurePlugin(a.Plugin, a.Config) - t.DisableAutoConfiguration(a.Plugin, a.DisableAutoRegistration) - return nil -} - -type Target interface { - ConfigurePlugin(name string, config json.RawMessage) - DisableAutoConfiguration(name string, flag bool) -} - -const usage = ` -The config type ` + ConfigType + ` can be used to configure a -plugin. - -
-    type: ` + ConfigType + `
-    plugin: <plugin name>
-    config: <arbitrary configuration structure>
-    disableAutoRegistration: <boolean flag to disable auto registration for up- and download handlers>
-
-` diff --git a/pkg/contexts/ocm/plugin/descriptor/const.go b/pkg/contexts/ocm/plugin/descriptor/const.go deleted file mode 100644 index 8174569b4..000000000 --- a/pkg/contexts/ocm/plugin/descriptor/const.go +++ /dev/null @@ -1,19 +0,0 @@ -package descriptor - -import ( - "github.com/open-component-model/ocm/pkg/contexts/datacontext/action" - "github.com/open-component-model/ocm/pkg/errkind" - ocmlog "github.com/open-component-model/ocm/pkg/logging" -) - -const ( - KIND_PLUGIN = "plugin" - KIND_DOWNLOADER = "downloader" - KIND_UPLOADER = "uploader" - KIND_ACCESSMETHOD = errkind.KIND_ACCESSMETHOD - KIND_ACTION = action.KIND_ACTION - KIND_VALUESET = "value set" - KIND_PURPOSE = "purposet" -) - -var REALM = ocmlog.DefineSubRealm("OCM plugin handling", "plugins") diff --git a/pkg/contexts/ocm/plugin/descriptor/utils.go b/pkg/contexts/ocm/plugin/descriptor/utils.go deleted file mode 100644 index 87692d255..000000000 --- a/pkg/contexts/ocm/plugin/descriptor/utils.go +++ /dev/null @@ -1,58 +0,0 @@ -package descriptor - -import ( - "sort" - - "github.com/mandelsoft/goutils/sliceutils" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils/registry" -) - -type Named interface { - GetName() string -} - -type StringName string - -func (e StringName) GetName() string { - return string(e) -} - -type Element[K registry.Key[K]] interface { - Named - GetConstraints() []K -} - -type List[T Named] []T - -func (l List[T]) Get(name string) *T { - for _, m := range l { - if m.GetName() == name { - return &m - } - } - return nil -} - -func (l List[T]) GetNames() []string { - var n []string - for _, e := range l { - n = append(n, e.GetName()) - } - sort.Strings(n) - return n -} - -func (l List[T]) MergeWith(o List[T]) List[T] { - var list []T -next: - for _, e := range o { - for _, f := range l { - if e.GetName() == f.GetName() { - continue next - } - } - list = append(list, e) - } - return sliceutils.CopyAppend(l, list...) -} diff --git a/pkg/contexts/ocm/plugin/interface.go b/pkg/contexts/ocm/plugin/interface.go deleted file mode 100644 index fcb166bfd..000000000 --- a/pkg/contexts/ocm/plugin/interface.go +++ /dev/null @@ -1,33 +0,0 @@ -package plugin - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/internal" -) - -const ( - KIND_PLUGIN = descriptor.KIND_PLUGIN - KIND_UPLOADER = descriptor.KIND_UPLOADER - KIND_ACCESSMETHOD = descriptor.KIND_ACCESSMETHOD - KIND_ACTION = descriptor.KIND_ACTION -) - -var TAG = descriptor.REALM - -type ( - Descriptor = descriptor.Descriptor - ActionDescriptor = descriptor.ActionDescriptor - ValueMergeHandlerDescriptor = descriptor.ValueMergeHandlerDescriptor - AccessMethodDescriptor = descriptor.AccessMethodDescriptor - DownloaderDescriptor = descriptor.DownloaderDescriptor - DownloaderKey = descriptor.DownloaderKey - UploaderDescriptor = descriptor.UploaderDescriptor - UploaderKey = descriptor.UploaderKey - UploaderKeySet = descriptor.UploaderKeySet - ValueSetDefinition = descriptor.ValueSetDefinition - ValueSetDescriptor = descriptor.ValueSetDescriptor - CommandDescriptor = descriptor.CommandDescriptor - - AccessSpecInfo = internal.AccessSpecInfo - UploadTargetSpecInfo = internal.UploadTargetSpecInfo -) diff --git a/pkg/contexts/ocm/plugin/internal/access.go b/pkg/contexts/ocm/plugin/internal/access.go deleted file mode 100644 index c78beaa81..000000000 --- a/pkg/contexts/ocm/plugin/internal/access.go +++ /dev/null @@ -1,12 +0,0 @@ -package internal - -import ( - "github.com/open-component-model/ocm/pkg/contexts/credentials" -) - -type AccessSpecInfo struct { - Short string `json:"short"` - MediaType string `json:"mediaType"` - Hint string `json:"hint"` - ConsumerId credentials.ConsumerIdentity `json:"consumerId"` -} diff --git a/pkg/contexts/ocm/plugin/internal/upload.go b/pkg/contexts/ocm/plugin/internal/upload.go deleted file mode 100644 index 09a0460d4..000000000 --- a/pkg/contexts/ocm/plugin/internal/upload.go +++ /dev/null @@ -1,9 +0,0 @@ -package internal - -import ( - "github.com/open-component-model/ocm/pkg/contexts/credentials" -) - -type UploadTargetSpecInfo struct { - ConsumerId credentials.ConsumerIdentity `json:"consumerId"` -} diff --git a/pkg/contexts/ocm/plugin/plugin.go b/pkg/contexts/ocm/plugin/plugin.go deleted file mode 100644 index ed5742881..000000000 --- a/pkg/contexts/ocm/plugin/plugin.go +++ /dev/null @@ -1,445 +0,0 @@ -package plugin - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "os" - "sync" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/finalizer" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/cobrautils/flagsets" - "github.com/open-component-model/ocm/pkg/cobrautils/logopts/logging" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "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/datacontext/attrs/clicfgattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/cache" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/compose" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get" - accval "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/validate" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/action" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/action/execute" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/command" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/download" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/mergehandler" - merge "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/mergehandler/execute" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/upload" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/upload/put" - uplval "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/upload/validate" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/valueset" - vscompose "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/valueset/compose" - vsval "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/valueset/validate" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler" - "github.com/open-component-model/ocm/pkg/runtime" -) - -type Plugin = *pluginImpl - -type impl = cache.Plugin - -// //nolint: errname // is no error. -type pluginImpl struct { - lock sync.RWMutex - ctx ocm.Context - impl - config json.RawMessage - disableAutoConfiguration bool -} - -func NewPlugin(ctx ocm.Context, impl cache.Plugin, config json.RawMessage) Plugin { - return &pluginImpl{ - ctx: ctx, - impl: impl, - config: config, - } -} - -func (p *pluginImpl) Context() ocm.Context { - return p.ctx -} - -func (p *pluginImpl) DisableAutoConfiguration(flag bool) { - p.disableAutoConfiguration = flag -} - -func (p *pluginImpl) IsAutoConfigurationEnabled() bool { - return !p.disableAutoConfiguration -} - -func (p *pluginImpl) SetConfig(config json.RawMessage) { - p.lock.Lock() - defer p.lock.Unlock() - p.config = config -} - -func (p *pluginImpl) Exec(r io.Reader, w io.Writer, args ...string) (result []byte, rerr error) { - var ( - finalize finalizer.Finalizer - err error - logfile *os.File - ) - - defer finalize.FinalizeWithErrorPropagationf(&rerr, "error processing plugin command %s", args[0]) - - if p.GetDescriptor().ForwardLogging { - logfile, err = os.CreateTemp("", "ocm-plugin-log-*") - if rerr != nil { - return nil, err - } - logfile.Close() - finalize.With(func() error { - return os.Remove(logfile.Name()) - }, "failed to remove temporary log file %s", logfile.Name()) - - lcfg := &logging.LoggingConfiguration{} - _, err = p.Context().ConfigContext().ApplyTo(0, lcfg) - if err != nil { - return nil, errors.Wrapf(err, "cannot extract plugin logging configration") - } - lcfg.LogFileName = logfile.Name() - data, err := json.Marshal(lcfg) - if err != nil { - return nil, errors.Wrapf(err, "cannot marshal plugin logging configration") - } - args = append([]string{"--" + ppi.OptPlugingLogConfig, string(data)}, args...) - } - - if len(p.config) == 0 { - p.ctx.Logger(TAG).Debug("execute plugin action", "path", p.Path(), "args", args) - } else { - p.ctx.Logger(TAG).Debug("execute plugin action", "path", p.Path(), "args", args, "config", p.config) - } - data, err := cache.Exec(p.Path(), p.config, r, w, args...) - - if logfile != nil { - r, oerr := os.OpenFile(logfile.Name(), vfs.O_RDONLY, 0o600) - if oerr == nil { - finalize.Close(r, "plugin logfile", logfile.Name()) - w := p.ctx.LoggingContext().Tree().LogWriter() - if w == nil { - if logging.GlobalLogFile != nil { - w = logging.GlobalLogFile.File() - } - if w == nil { - w = os.Stderr - } - } - - // weaken the sync problem when merging log files. - // If a SyncWriter is used, the copy is done under a write lock. - // This is only a solution, if the log records are written - // by single write calls. - // The underlying logging apis do not expose their - // sync mechanism for writing log records. - if writer, ok := w.(io.ReaderFrom); ok { - writer.ReadFrom(r) - } else { - io.Copy(w, r) - } - } - } - return data, err -} - -func (p *pluginImpl) MergeValue(specification *valuemergehandler.Specification, local, inbound valuemergehandler.Value) (bool, *valuemergehandler.Value, error) { - desc := p.GetValueMappingDescriptor(specification.Algorithm) - if desc == nil { - return false, nil, errors.ErrNotSupported(valuemergehandler.KIND_VALUE_MERGE_ALGORITHM, specification.Algorithm, KIND_PLUGIN, p.Name()) - } - input, err := json.Marshal(ppi.ValueMergeData{ - Local: local, - Inbound: inbound, - }) - if err != nil { - return false, nil, err - } - - args := []string{mergehandler.Name, merge.Name, specification.Algorithm} - if len(specification.Config) > 0 { - args = append(args, string(specification.Config)) - } - - var buf bytes.Buffer - _, err = p.Exec(bytes.NewReader(input), &buf, args...) - if err != nil { - return false, nil, errors.Wrapf(err, "plugin %s", p.Name()) - } - var r ppi.ValueMergeResult - - err = json.Unmarshal(buf.Bytes(), &r) - if err != nil { - if r.Message != "" { - return false, nil, fmt.Errorf("%w: %s", err, r.Message) - } - return false, nil, err - } - return r.Modified, &r.Value, nil -} - -func (p *pluginImpl) Action(spec ppi.ActionSpec, creds json.RawMessage) (ppi.ActionResult, error) { - desc := p.GetActionDescriptor(spec.GetKind()) - if desc == nil { - return nil, errors.ErrNotSupported(KIND_ACTION, spec.GetKind(), KIND_PLUGIN, p.Name()) - } - if desc.ConsumerType != "" { - cid := spec.GetConsumerAttributes() - cid[cpi.ID_TYPE] = desc.ConsumerType - c, err := credentials.CredentialsForConsumer(p.Context(), credentials.ConsumerIdentity(cid), hostpath.Matcher) - if err != nil || c == nil { - return nil, errors.ErrNotFound(credentials.KIND_CREDENTIALS, cid.String()) - } - creds, err = json.Marshal(c.Properties()) - if err != nil { - return nil, errors.Wrapf(err, "cannot marshal credentials") - } - } - - data, err := p.ctx.GetActions().GetActionTypes().EncodeActionSpec(spec, runtime.DefaultJSONEncoding) - if err != nil { - return nil, err - } - - args := []string{action.Name, execute.Name, string(data)} - if creds != nil { - args = append(args, "--"+get.OptCreds, string(creds)) - } - - result, err := p.Exec(nil, nil, args...) - if err != nil { - return nil, errors.Wrapf(err, "plugin %s", p.Name()) - } - - info, err := p.ctx.GetActions().GetActionTypes().DecodeActionResult(result, runtime.DefaultJSONEncoding) - if err != nil { - return nil, errors.Wrapf(err, "plugin %s: cannot unmarshal action result", p.Name()) - } - return info, nil -} - -func (p *pluginImpl) ValidateAccessMethod(spec []byte) (*ppi.AccessSpecInfo, error) { - result, err := p.Exec(nil, nil, accessmethod.Name, accval.Name, string(spec)) - if err != nil { - return nil, errors.Wrapf(err, "plugin %s", p.Name()) - } - - var info ppi.AccessSpecInfo - err = json.Unmarshal(result, &info) - if err != nil { - return nil, errors.Wrapf(err, "plugin %s: cannot unmarshal access spec info", p.Name()) - } - return &info, nil -} - -func (p *pluginImpl) ComposeAccessMethod(name string, opts flagsets.ConfigOptions, base flagsets.Config) error { - cfg := flagsets.Config{} - for _, o := range opts.Options() { - cfg[o.GetName()] = o.Value() - } - optsdata, err := json.Marshal(cfg) - if err != nil { - return errors.Wrapf(err, "cannot marshal option values") - } - basedata, err := json.Marshal(base) - if err != nil { - return errors.Wrapf(err, "cannot marshal access specification base value") - } - result, err := p.Exec(nil, nil, accessmethod.Name, compose.Name, name, string(optsdata), string(basedata)) - if err != nil { - return err - } - var r flagsets.Config - err = json.Unmarshal(result, &r) - if err != nil { - return errors.Wrapf(err, "cannot unmarshal composition result") - } - - for k := range base { - delete(base, k) - } - for k, v := range r { - base[k] = v - } - return nil -} - -func (p *pluginImpl) ValidateUploadTarget(name string, spec []byte) (*ppi.UploadTargetSpecInfo, error) { - result, err := p.Exec(nil, nil, upload.Name, uplval.Name, name, string(spec)) - if err != nil { - return nil, errors.Wrapf(err, "plugin uploader %s/%s", p.Name(), name) - } - - var info ppi.UploadTargetSpecInfo - err = json.Unmarshal(result, &info) - if err != nil { - return nil, errors.Wrapf(err, "plugin uploader %s/%s: cannot unmarshal upload target info", p.Name(), name) - } - return &info, nil -} - -func (p *pluginImpl) Get(w io.Writer, creds, spec json.RawMessage) error { - args := []string{accessmethod.Name, get.Name, string(spec)} - if creds != nil { - args = append(args, "--"+get.OptCreds, string(creds)) - } - _, err := p.Exec(nil, w, args...) - return err -} - -func (p *pluginImpl) Put(name string, r io.Reader, artType, mimeType, hint string, creds, target json.RawMessage) (ocm.AccessSpec, error) { - args := []string{upload.Name, put.Name, name, string(target)} - - if creds != nil { - args = append(args, "--"+put.OptCreds, string(creds)) - } - if hint != "" { - args = append(args, "--"+put.OptHint, hint) - } - if mimeType != "" { - args = append(args, "--"+put.OptMedia, mimeType) - } - if artType != "" { - args = append(args, "--"+put.OptArt, artType) - } - result, err := p.Exec(r, nil, args...) - if err != nil { - return nil, err - } - var m map[string]interface{} - err = json.Unmarshal(result, &m) - if err != nil { - return nil, errors.Wrapf(err, "cannot unmarshal put result") - } - if len(m) == 0 { - return nil, nil // not used - } - return p.ctx.AccessSpecForConfig(result, runtime.DefaultJSONEncoding) -} - -func (p *pluginImpl) Download(name string, r io.Reader, artType, mimeType, target string, config json.RawMessage) (bool, string, error) { - args := []string{download.Name, name, target} - - if mimeType != "" { - args = append(args, "--"+download.OptMedia, mimeType) - } - if artType != "" { - args = append(args, "--"+download.OptArt, artType) - } - - // new attribute can only be set for extended plugin format version - // so, omitting config if not set is compatible with former CLI. - if d := p.GetDescriptor().Downloaders.Get(name); len(config) > 0 && d != nil && d.ConfigScheme != "" { - args = append(args, "--"+download.OptConfig, string(config)) - } - result, err := p.Exec(r, nil, args...) - if err != nil { - return true, "", err - } - var m download.Result - err = json.Unmarshal(result, &m) - if err != nil { - return true, "", errors.Wrapf(err, "cannot unmarshal put result") - } - if m.Error != "" { - return true, "", fmt.Errorf("%s", m.Error) - } - return m.Path != "", m.Path, nil -} - -func (p *pluginImpl) ValidateValueSet(purpose string, spec []byte) (*ppi.ValueSetInfo, error) { - result, err := p.Exec(nil, nil, valueset.Name, vsval.Name, purpose, string(spec)) - if err != nil { - return nil, errors.Wrapf(err, "plugin %s", p.Name()) - } - - var info ppi.ValueSetInfo - err = json.Unmarshal(result, &info) - if err != nil { - return nil, errors.Wrapf(err, "plugin %s: cannot unmarshal value set info", p.Name()) - } - return &info, nil -} - -func (p *pluginImpl) ComposeValueSet(purpose, name string, opts flagsets.ConfigOptions, base flagsets.Config) error { - cfg := flagsets.Config{} - for _, o := range opts.Options() { - cfg[o.GetName()] = o.Value() - } - optsdata, err := json.Marshal(cfg) - if err != nil { - return errors.Wrapf(err, "cannot marshal option values") - } - basedata, err := json.Marshal(base) - if err != nil { - return errors.Wrapf(err, "cannot marshal access specification base value") - } - result, err := p.Exec(nil, nil, valueset.Name, vscompose.Name, purpose, name, string(optsdata), string(basedata)) - if err != nil { - return err - } - var r flagsets.Config - err = json.Unmarshal(result, &r) - if err != nil { - return errors.Wrapf(err, "cannot unmarshal composition result") - } - - for k := range base { - delete(base, k) - } - for k, v := range r { - base[k] = v - } - return nil -} - -func (p *pluginImpl) Command(name string, reader io.Reader, writer io.Writer, cmdargs []string) (rerr error) { - var finalize finalizer.Finalizer - cmd := p.GetDescriptor().Commands.Get(name) - if cmd == nil { - return errors.ErrNotFound("command", name) - } - - defer finalize.FinalizeWithErrorPropagation(&rerr) - - var f vfs.File - - args := []string{command.Name} - - a := clicfgattr.Get(p.Context()) - if a != nil && cmd.CLIConfigRequired { - cfgdata, err := json.Marshal(a) - if err != nil { - return errors.Wrapf(err, "cannot marshal CLI config") - } - // cannot use a vfs here, since it's not possible to pass it to the plugin - f, err = os.CreateTemp("", "cli-om-config-*") - if err != nil { - return err - } - finalize.With(func() error { - return os.Remove(f.Name()) - }, "failed to remove temporary config file %s", f.Name()) - - _, err = f.Write(cfgdata) - if err != nil { - f.Close() - return err - } - err = f.Close() - if err != nil { - return err - } - args = append(args, "--"+command.OptCliConfig, f.Name()) - } - args = append(append(args, name), cmdargs...) - - _, err := p.Exec(reader, writer, args...) - return err -} diff --git a/pkg/contexts/ocm/plugin/ppi/clicmd/utils.go b/pkg/contexts/ocm/plugin/ppi/clicmd/utils.go deleted file mode 100644 index b70369c90..000000000 --- a/pkg/contexts/ocm/plugin/ppi/clicmd/utils.go +++ /dev/null @@ -1,85 +0,0 @@ -package clicmd - -import ( - _ "github.com/open-component-model/ocm/cmds/ocm/clippi/config" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/optionutils" - "github.com/spf13/cobra" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" -) - -//////////////////////////////////////////////////////////////////////////////// - -type CobraCommand struct { - cmd *cobra.Command - verb string - realm string - objname string - cliConfigRequired bool -} - -var _ ppi.Command = (*CobraCommand)(nil) - -// NewCLICommand created a CLI command based on a preconfigured cobra.Command. -// Optionally, a verb can be specified. If given additionally a realm -// can be given. -// verb and realm are used to add the command at the appropriate places in -// the command hierarchy of the ocm CLI. -// If nothing is specified, the command will be a new top-level command. -// To access the configured ocm context use the Context attribute -// of the cobra command. The ocm context is bound to it. -// -// ocm.FromContext(cmd.Context()) -func NewCLICommand(cmd *cobra.Command, opts ...Option) (ppi.Command, error) { - eff := optionutils.EvalOptions(opts...) - if eff.Verb == "" && eff.Realm != "" { - return nil, errors.New("realm without verb not allowed") - } - cmd.DisableFlagsInUseLine = true - return &CobraCommand{cmd, eff.Verb, eff.Realm, eff.ObjectType, optionutils.AsBool(eff.RequireCLIConfig, false)}, nil -} - -func (c *CobraCommand) Name() string { - return c.cmd.Name() -} - -func (c *CobraCommand) Description() string { - return c.cmd.Long -} - -func (c *CobraCommand) Usage() string { - return c.cmd.Use -} - -func (c *CobraCommand) Short() string { - return c.cmd.Short -} - -func (c *CobraCommand) Example() string { - return c.cmd.Example -} - -func (c *CobraCommand) ObjectType() string { - if c.objname == "" { - return c.Name() - } - return c.objname -} - -func (c *CobraCommand) Verb() string { - return c.verb -} - -func (c *CobraCommand) Realm() string { - return c.realm -} - -func (c *CobraCommand) CLIConfigRequired() bool { - return c.cliConfigRequired -} - -func (c *CobraCommand) Command() *cobra.Command { - return c.cmd -} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/cmd.go deleted file mode 100644 index 7c549dc3b..000000000 --- a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/cmd.go +++ /dev/null @@ -1,26 +0,0 @@ -package accessmethod - -import ( - "github.com/spf13/cobra" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/compose" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/validate" -) - -const Name = "accessmethod" - -func New(p ppi.Plugin) *cobra.Command { - cmd := &cobra.Command{ - Use: Name, - Short: "access method operations", - Long: `This command group provides all commands used to implement an access method -described by an access method descriptor (` + p.Name() + ` descriptor.`, - } - - cmd.AddCommand(validate.New(p)) - cmd.AddCommand(get.New(p)) - cmd.AddCommand(compose.New(p)) - return cmd -} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/compose/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/compose/cmd.go deleted file mode 100644 index 8e7fd31ab..000000000 --- a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/compose/cmd.go +++ /dev/null @@ -1,94 +0,0 @@ -package compose - -import ( - "encoding/json" - - "github.com/mandelsoft/goutils/errors" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/options" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/errkind" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const Name = "compose" - -func New(p ppi.Plugin) *cobra.Command { - opts := Options{} - - cmd := &cobra.Command{ - Use: Name + " ", - Short: "compose access specification from options and base specification", - Long: ` -The task of this command is to compose an access specification based on some -explicitly given input options and preconfigured specifications. - -The finally composed access specification has to be returned as JSON document -on *stdout*. - -This command is only used, if for an access method descriptor configuration -options are defined (` + p.Name() + ` descriptor). - -If possible, predefined standard options should be used. In such a case only the -name field should be defined for an option. If required, new options can be -defined by additionally specifying a type and a description. New options should -be used very carefully. The chosen names MUST not conflict with names provided -by other plugins. Therefore, it is highly recommended to use names prefixed -by the plugin name. - -` + options.DefaultRegistry.Usage(), - Args: cobra.ExactArgs(3), - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - return opts.Complete(args) - }, - RunE: func(cmd *cobra.Command, args []string) error { - return Command(p, cmd, &opts) - }, - } - opts.AddFlags(cmd.Flags()) - return cmd -} - -type Options struct { - Name string - Options ppi.Config - Base ppi.Config -} - -func (o *Options) AddFlags(fs *pflag.FlagSet) { -} - -func (o *Options) Complete(args []string) error { - o.Name = args[0] - if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[1]), &o.Options); err != nil { - return errors.Wrapf(err, "invalid access specification options") - } - if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[2]), &o.Base); err != nil { - return errors.Wrapf(err, "invalid base access specification") - } - return nil -} - -func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { - k, v := runtime.KindVersion(opts.Name) - m := p.GetAccessMethod(k, v) - if m == nil { - return errors.ErrUnknown(errkind.KIND_ACCESSMETHOD, opts.Name) - } - err := opts.Options.ConvertFor(m.Options()...) - if err != nil { - return err - } - err = m.ComposeAccessSpecification(p, opts.Options, opts.Base) - if err != nil { - return err - } - data, err := json.Marshal(opts.Base) - if err != nil { - return err - } - cmd.Printf("%s\n", string(data)) - return nil -} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go deleted file mode 100644 index 53cf12c2b..000000000 --- a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/get/cmd.go +++ /dev/null @@ -1,87 +0,0 @@ -package get - -import ( - "encoding/json" - "fmt" - "io" - "os" - - "github.com/mandelsoft/goutils/errors" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - - "github.com/open-component-model/ocm/pkg/cobrautils/flag" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - commonppi "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/common" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - Name = "get" - OptCreds = commonppi.OptCreds -) - -func New(p ppi.Plugin) *cobra.Command { - opts := Options{} - - cmd := &cobra.Command{ - Use: Name + " [] ", - Short: "get blob", - Long: ` -Evaluate the given access specification and return the described blob on -*stdout*.`, - Args: cobra.ExactArgs(1), - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - return opts.Complete(args) - }, - RunE: func(cmd *cobra.Command, args []string) error { - return Command(p, cmd, &opts) - }, - } - opts.AddFlags(cmd.Flags()) - return cmd -} - -type Options struct { - Credentials credentials.DirectCredentials - Specification json.RawMessage -} - -func (o *Options) AddFlags(fs *pflag.FlagSet) { - flag.YAMLVarP(fs, &o.Credentials, OptCreds, "c", nil, "credentials") - flag.StringToStringVarPFA(fs, &o.Credentials, "credential", "C", nil, "dedicated credential value") -} - -func (o *Options) Complete(args []string) error { - if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[0]), &o.Specification); err != nil { - return errors.Wrapf(err, "invalid repository specification") - } - - fmt.Fprintf(os.Stderr, "credentials: %s\n", o.Credentials.String()) - return nil -} - -func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { - spec, err := p.DecodeAccessSpecification(opts.Specification) - if err != nil { - return errors.Wrapf(err, "access specification") - } - - m := p.GetAccessMethod(runtime.KindVersion(spec.GetType())) - if m == nil { - return errors.ErrUnknown(descriptor.KIND_ACCESSMETHOD, spec.GetType()) - } - _, err = m.ValidateSpecification(p, spec) - if err != nil { - return err - } - r, err := m.Reader(p, spec, opts.Credentials) - if err != nil { - return err - } - _, err = io.Copy(os.Stdout, r) - r.Close() - return err -} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/validate/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/validate/cmd.go deleted file mode 100644 index 8e6f3a741..000000000 --- a/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod/validate/cmd.go +++ /dev/null @@ -1,105 +0,0 @@ -package validate - -import ( - "encoding/json" - - "github.com/mandelsoft/goutils/errors" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/errkind" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const Name = "validate" - -func New(p ppi.Plugin) *cobra.Command { - opts := Options{} - - cmd := &cobra.Command{ - Use: Name + " ", - Short: "validate access specification", - Long: ` -This command accepts an access specification as argument. It is used to -validate the specification and to provide some metadata for the given -specification. - -This metadata has to be provided as JSON string on *stdout* and has the -following fields: - -- **mediaType** *string* - - The media type of the artifact described by the specification. It may be part - of the specification or implicitly determined by the access method. - -- **description** *string* - - A short textual description of the described location. - -- **hint** *string* - - A name hint of the described location used to reconstruct a useful - name for local blobs uploaded to a dedicated repository technology. - -- **consumerId** *map[string]string* - - The consumer id used to determine optional credentials for the - underlying repository. If specified, at least the type field must be set. -`, - Args: cobra.ExactArgs(1), - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - return opts.Complete(args) - }, - RunE: func(cmd *cobra.Command, args []string) error { - return Command(p, cmd, &opts) - }, - } - opts.AddFlags(cmd.Flags()) - return cmd -} - -type Options struct { - Specification json.RawMessage -} - -func (o *Options) AddFlags(fs *pflag.FlagSet) { -} - -func (o *Options) Complete(args []string) error { - if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[0]), &o.Specification); err != nil { - return errors.Wrapf(err, "invalid access specification") - } - return nil -} - -type Result struct { - MediaType string `json:"mediaType"` - Short string `json:"description"` - Hint string `json:"hint"` - ConsumerId credentials.ConsumerIdentity `json:"consumerId"` -} - -func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { - spec, err := p.DecodeAccessSpecification(opts.Specification) - if err != nil { - return errors.Wrapf(err, "access specification") - } - - m := p.GetAccessMethod(runtime.KindVersion(spec.GetType())) - if m == nil { - return errors.ErrUnknown(errkind.KIND_ACCESSMETHOD, spec.GetType()) - } - info, err := m.ValidateSpecification(p, spec) - if err != nil { - return err - } - result := Result{MediaType: info.MediaType, ConsumerId: info.ConsumerId, Hint: info.Hint, Short: info.Short} - data, err := json.Marshal(result) - if err != nil { - return err - } - cmd.Printf("%s\n", string(data)) - return nil -} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/action/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/action/cmd.go deleted file mode 100644 index e87c56345..000000000 --- a/pkg/contexts/ocm/plugin/ppi/cmds/action/cmd.go +++ /dev/null @@ -1,21 +0,0 @@ -package action - -import ( - "github.com/spf13/cobra" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/action/execute" -) - -const Name = "action" - -func New(p ppi.Plugin) *cobra.Command { - cmd := &cobra.Command{ - Use: Name, - Short: "action operations", - Long: `This command group provides all commands used to implement an action.`, - } - - cmd.AddCommand(execute.New(p)) - return cmd -} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/action/execute/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/action/execute/cmd.go deleted file mode 100644 index 5c7b46be3..000000000 --- a/pkg/contexts/ocm/plugin/ppi/cmds/action/execute/cmd.go +++ /dev/null @@ -1,97 +0,0 @@ -package execute - -import ( - "encoding/json" - - "github.com/mandelsoft/goutils/errors" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - - "github.com/open-component-model/ocm/pkg/cobrautils/flag" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/action" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/action/api" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/common" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - Name = "execute" - OptCreds = common.OptCreds -) - -func New(p ppi.Plugin) *cobra.Command { - opts := Options{} - - cmd := &cobra.Command{ - Use: Name + " ", - Short: "execute an action", - Long: ` -This command executes an action. - -This action has to provide an execution result as JSON string on *stdout*. It has the -following fields: - -- **name** *string* - - The name and version of the action result. It must match the value - from the action specification. - -- **message** *string* - - An error message. - -Additional fields depend on the kind of action. -`, - Args: cobra.ExactArgs(1), - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - return opts.Complete(args) - }, - RunE: func(cmd *cobra.Command, args []string) error { - return Command(p, cmd, &opts) - }, - } - opts.AddFlags(cmd.Flags()) - return cmd -} - -type Options struct { - Credentials credentials.DirectCredentials - Specification json.RawMessage -} - -func (o *Options) AddFlags(fs *pflag.FlagSet) { - flag.YAMLVarP(fs, &o.Credentials, OptCreds, "c", nil, "credentials") - flag.StringToStringVarPFA(fs, &o.Credentials, "credential", "C", nil, "dedicated credential value") -} - -func (o *Options) Complete(args []string) error { - if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[0]), &o.Specification); err != nil { - return errors.Wrapf(err, "invalid access specification") - } - return nil -} - -func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { - spec, err := action.DefaultRegistry().DecodeActionSpec(opts.Specification, runtime.DefaultJSONEncoding) - if err != nil { - return errors.Wrapf(err, "action specification") - } - - a := p.GetAction(spec.GetKind()) - if a == nil { - return errors.ErrUnknown(api.KIND_ACTION, spec.GetKind()) - } - result, err := a.Execute(p, spec, opts.Credentials) - if err != nil { - return err - } - result.SetType(spec.GetType()) - data, err := action.DefaultRegistry().EncodeActionResult(result, runtime.DefaultJSONEncoding) - if err != nil { - return err - } - cmd.Printf("%s\n", string(data)) - return nil -} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/app.go b/pkg/contexts/ocm/plugin/ppi/cmds/app.go deleted file mode 100644 index 87591f1e1..000000000 --- a/pkg/contexts/ocm/plugin/ppi/cmds/app.go +++ /dev/null @@ -1,106 +0,0 @@ -//go:generate go run -mod=mod ./doc ../../../../../../docs/pluginreference - -package cmds - -import ( - "encoding/json" - "os" - - "github.com/spf13/cobra" - - "github.com/open-component-model/ocm/pkg/cobrautils" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/accessmethod" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/action" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/command" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/describe" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/download" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/info" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/mergehandler" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/topics/descriptor" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/upload" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/valueset" -) - -type PluginCommand struct { - command *cobra.Command - plugin ppi.Plugin -} - -func (p *PluginCommand) Command() *cobra.Command { - return p.command -} - -func NewPluginCommand(p ppi.Plugin) *PluginCommand { - short := p.Descriptor().Short - if short == "" { - short = "OCM plugin " + p.Name() - } - - pcmd := &PluginCommand{ - plugin: p, - } - cmd := &cobra.Command{ - Use: p.Name() + " ", - Short: short, - Long: p.Descriptor().Long, - Version: p.Version(), - PersistentPreRunE: pcmd.PreRunE, - TraverseChildren: true, - SilenceUsage: true, - DisableFlagsInUseLine: true, - SilenceErrors: true, - } - - cmd.SetOut(os.Stdout) - cmd.SetErr(os.Stderr) - - cobrautils.TweakCommand(cmd, nil) - - cmd.AddCommand(describe.New(p)) - cmd.AddCommand(info.New(p)) - cmd.AddCommand(action.New(p)) - cmd.AddCommand(mergehandler.New(p)) - cmd.AddCommand(accessmethod.New(p)) - cmd.AddCommand(upload.New(p)) - cmd.AddCommand(download.New(p)) - cmd.AddCommand(valueset.New(p)) - cmd.AddCommand(command.New(p)) - - cmd.InitDefaultHelpCmd() - help := cobrautils.GetHelpCommand(cmd) - - // help.Use="help " - help.DisableFlagsInUseLine = true - cmd.AddCommand(descriptor.New()) - - help.AddCommand(descriptor.New()) - - p.GetOptions().AddFlags(cmd.Flags()) - pcmd.command = cmd - return pcmd -} - -type Error struct { - Error string `json:"error"` -} - -func (p *PluginCommand) PreRunE(cmd *cobra.Command, args []string) error { - if handler != nil { - return handler.HandleConfig(p.plugin.GetOptions().LogConfig) - } - return nil -} - -func (p *PluginCommand) Execute(args []string) error { - p.command.SetArgs(args) - err := p.command.Execute() - if err != nil { - result, err2 := json.Marshal(Error{err.Error()}) - if err2 != nil { - return err2 - } - p.command.PrintErrln(string(result)) - } - return err -} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/command/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/command/cmd.go deleted file mode 100644 index 053b65c11..000000000 --- a/pkg/contexts/ocm/plugin/ppi/cmds/command/cmd.go +++ /dev/null @@ -1,71 +0,0 @@ -package command - -import ( - "context" - "os" - - "github.com/spf13/cobra" - - "github.com/open-component-model/ocm/pkg/cobrautils" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/common" -) - -const ( - Name = "command" - OptCliConfig = common.OptCliConfig -) - -func New(p ppi.Plugin) *cobra.Command { - cmd := &cobra.Command{ - Use: Name, - Short: "CLI command extensions", - Long: `This command group provides all CLI command extensions -described by an access method descriptor (` + p.Name() + ` descriptor.`, - TraverseChildren: true, - } - var cliconfig string - cmd.Flags().StringVarP(&cliconfig, OptCliConfig, "", "", "path to cli configuration file") - - found := false - for _, n := range p.Commands() { - found = true - c := n.Command() - c.TraverseChildren = true - - nested := c.PreRunE - c.PreRunE = func(cmd *cobra.Command, args []string) error { - var err error - - ctx := context.Background() - if cliconfig != "" { - ctx, err = ConfigureFromFile(ctx, cliconfig) - if err != nil { - return err - } - } - c.SetContext(ctx) - if nested != nil { - return nested(cmd, args) - } - return nil - } - cmd.AddCommand(n.Command()) - } - if found { - cobrautils.TweakHelpCommandFor(cmd) - } - return cmd -} - -func ConfigureFromFile(ctx context.Context, path string) (context.Context, error) { - data, err := os.ReadFile(path) - if err != nil { - return ctx, err - } - - if handler != nil { - return handler.HandleConfig(ctx, data) - } - return ctx, nil -} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/describe/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/describe/cmd.go deleted file mode 100644 index a29e05958..000000000 --- a/pkg/contexts/ocm/plugin/ppi/cmds/describe/cmd.go +++ /dev/null @@ -1,28 +0,0 @@ -package describe - -import ( - "os" - - "github.com/spf13/cobra" - - common2 "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/action" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" -) - -const NAME = "describe" - -func New(p ppi.Plugin) *cobra.Command { - return &cobra.Command{ - Use: NAME, - Short: "describe plugin", - Long: "Display a detailed description of the capabilities of this OCM plugin.", - Args: cobra.MaximumNArgs(0), - RunE: func(cmd *cobra.Command, args []string) error { - d := p.Descriptor() - common.DescribePluginDescriptor(action.DefaultRegistry(), &d, common2.NewPrinter(os.Stdout)) - return nil - }, - } -} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/doc/generate.go b/pkg/contexts/ocm/plugin/ppi/cmds/doc/generate.go deleted file mode 100644 index 153aceb7e..000000000 --- a/pkg/contexts/ocm/plugin/ppi/cmds/doc/generate.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/open-component-model/ocm/hack/generate-docs/cobradoc" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds" - "github.com/open-component-model/ocm/pkg/version" -) - -func main() { - fmt.Println("> Generate Docs for OCM Plugins") - - if len(os.Args) != 2 { // expect 2 as the first one is the program name - fmt.Fprintf(os.Stderr, "Expected exactly one argument, but got %d", len(os.Args)-1) - os.Exit(1) - } - - p := ppi.NewPlugin("plugin", version.Get().String()) - p.SetLong(cmds.Description(p.Name())) - p.SetShort("OCM Plugin") - cmd := cmds.NewPluginCommand(p).Command() - cmd.DisableAutoGenTag = true - cobradoc.Generate("OCM Plugin", cmd, os.Args[1], true) -} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/download/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/download/cmd.go deleted file mode 100644 index 077a8e828..000000000 --- a/pkg/contexts/ocm/plugin/ppi/cmds/download/cmd.go +++ /dev/null @@ -1,112 +0,0 @@ -package download - -import ( - "encoding/json" - "fmt" - "io" - "os" - - "github.com/mandelsoft/goutils/errors" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/common" -) - -const ( - Name = "download" - OptMedia = common.OptMedia - OptArt = common.OptArt - OptConfig = common.OptConfig -) - -func New(p ppi.Plugin) *cobra.Command { - opts := Options{} - - cmd := &cobra.Command{ - Use: Name + " [] ", - Short: "download blob into filesystem", - Long: ` -This command accepts a target filepath as argument. It is used as base name -to store the downloaded content. The blob content is provided on the -*stdin*. The first argument specified the downloader to use for the operation. - -The task of this command is to transform the content of the provided -blob into a filesystem structure applicable to the type specific tools working -with content of the given artifact type. -`, - Args: cobra.ExactArgs(2), - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - return opts.Complete(args) - }, - RunE: func(cmd *cobra.Command, args []string) error { - return Command(p, cmd, &opts) - }, - } - opts.AddFlags(cmd.Flags()) - return cmd -} - -type Options struct { - Name string - Path string - - MediaType string - ArtifactType string - Config string -} - -func (o *Options) AddFlags(fs *pflag.FlagSet) { - fs.StringVarP(&o.MediaType, OptMedia, "m", "", "media type of input blob") - fs.StringVarP(&o.ArtifactType, OptArt, "a", "", "artifact type of input blob") - fs.StringVarP(&o.Config, OptConfig, "c", "", "registration config") -} - -func (o *Options) Complete(args []string) error { - o.Name = args[0] - o.Path = args[1] - return nil -} - -type Result struct { - Path string `json:"path"` - Error string `json:"error"` -} - -func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { - d := p.GetDownloader(opts.Name) - if d == nil { - return errors.ErrNotFound(descriptor.KIND_DOWNLOADER, fmt.Sprintf("%s:%s", opts.ArtifactType, opts.MediaType)) - } - var cfg []byte - if opts.Config != "" { - cfg = []byte(opts.Config) - } - w, h, err := d.Writer(p, opts.ArtifactType, opts.MediaType, opts.Path, cfg) - if err != nil { - return err - } - _, err = io.Copy(w, os.Stdin) - if err != nil { - w.Close() - return err - } - err = w.Close() - if err != nil { - return err - } - path, err := h() - result := Result{ - Path: path, - } - if err != nil { - result.Error = err.Error() - } - data, err := json.Marshal(result) - if err == nil { - cmd.Printf("%s\n", string(data)) - } - return err -} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/info/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/info/cmd.go deleted file mode 100644 index b446b381c..000000000 --- a/pkg/contexts/ocm/plugin/ppi/cmds/info/cmd.go +++ /dev/null @@ -1,28 +0,0 @@ -package info - -import ( - "encoding/json" - - "github.com/spf13/cobra" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" -) - -const NAME = "info" - -func New(p ppi.Plugin) *cobra.Command { - return &cobra.Command{ - Use: NAME, - Short: "show plugin descriptor", - Long: "", - Args: cobra.MaximumNArgs(0), - RunE: func(cmd *cobra.Command, args []string) error { - data, err := json.Marshal(p.Descriptor()) - if err != nil { - return err - } - cmd.Printf("%s\n", string(data)) - return nil - }, - } -} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/logging.go b/pkg/contexts/ocm/plugin/ppi/cmds/logging.go deleted file mode 100644 index 7fe97c1ec..000000000 --- a/pkg/contexts/ocm/plugin/ppi/cmds/logging.go +++ /dev/null @@ -1,29 +0,0 @@ -package cmds - -import ( - "encoding/json" -) - -type LoggingHandler interface { - HandleConfig(data []byte) error -} - -var handler LoggingHandler - -// RegisterLoggingConfigHandler is used to register a configuration handler -// for logging configration passed by the OCM library. -// If standard mandelsoft logging is used, it can be adapted -// by adding the ananymous import of the ppi/logging package. -func RegisterLoggingConfigHandler(h LoggingHandler) { - handler = h -} - -// LoggingConfiguration describes logging configuration for a slave executables like -// plugins. -// If mandelsoft logging is used please use github.com/open-component-model/ocm/pkg/cobrautils/logging.LoggingConfiguration, -// instead. -type LoggingConfiguration struct { - LogFileName string `json:"logFileName"` - LogConfig json.RawMessage `json:"logConfig"` - Json bool `json:"json,omitempty"` -} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/mergehandler/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/mergehandler/cmd.go deleted file mode 100644 index 54651305f..000000000 --- a/pkg/contexts/ocm/plugin/ppi/cmds/mergehandler/cmd.go +++ /dev/null @@ -1,21 +0,0 @@ -package mergehandler - -import ( - "github.com/spf13/cobra" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/mergehandler/execute" -) - -const Name = "valuemergehandler" - -func New(p ppi.Plugin) *cobra.Command { - cmd := &cobra.Command{ - Use: Name, - Short: "value merge handler operations", - Long: `This command group provides all commands used to implement an value merge handlers.`, - } - - cmd.AddCommand(execute.New(p)) - return cmd -} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/mergehandler/execute/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/mergehandler/execute/cmd.go deleted file mode 100644 index 176cc4667..000000000 --- a/pkg/contexts/ocm/plugin/ppi/cmds/mergehandler/execute/cmd.go +++ /dev/null @@ -1,116 +0,0 @@ -package execute - -import ( - "encoding/json" - "fmt" - "io" - "os" - - "github.com/mandelsoft/goutils/errors" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/hpi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - Name = "execute" -) - -func New(p ppi.Plugin) *cobra.Command { - opts := Options{} - - cmd := &cobra.Command{ - Use: Name + " ", - Short: "execute a value merge", - Long: ` -This command executes a value merge. The values are taken from *stdin* as JSON -string. It has the following fields: - -- **local** *any* - - The local value to merge into the inbound value. - -- **inbound** *any* - - The value to merge into. This value is based on the original inbound value. - -This action has to provide an execution result as JSON string on *stdout*. It has the -following fields: - -- **modified** *bool* - - Whether the inbound value has been modified by merging with the local value. - -- **value** *string* - - The merged value - -- **message** *string* - - An error message. -`, - Args: cobra.ExactArgs(2), - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - return opts.Complete(args) - }, - RunE: func(cmd *cobra.Command, args []string) error { - return Command(p, cmd, &opts) - }, - } - opts.AddFlags(cmd.Flags()) - return cmd -} - -type Options struct { - Name string - Configuration json.RawMessage -} - -func (o *Options) AddFlags(fs *pflag.FlagSet) { -} - -func (o *Options) Complete(args []string) error { - if len(args) == 0 { - return fmt.Errorf("algorithm name missing") - } - o.Name = args[0] - if len(args) > 1 { - o.Configuration = []byte(args[1]) - } - if len(args) > 2 { - return fmt.Errorf("too many arguments") - } - return nil -} - -func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { - h := p.GetValueMergeHandler(opts.Name) - if h == nil { - return errors.ErrUnknown(hpi.KIND_VALUE_MERGE_ALGORITHM, opts.Name) - } - - data, err := io.ReadAll(os.Stdin) - if err != nil { - return err - } - - var input ppi.ValueMergeData - err = json.Unmarshal(data, &input) - if err != nil { - return err - } - - result, err := h.Execute(p, input.Local, input.Inbound, opts.Configuration) - if err != nil { - return err - } - data, err = runtime.DefaultJSONEncoding.Marshal(result) - if err != nil { - return err - } - cmd.Printf("%s\n", string(data)) - return nil -} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/upload/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/upload/cmd.go deleted file mode 100644 index d6a9e5cd3..000000000 --- a/pkg/contexts/ocm/plugin/ppi/cmds/upload/cmd.go +++ /dev/null @@ -1,25 +0,0 @@ -package upload - -import ( - "github.com/spf13/cobra" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/upload/put" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/upload/validate" -) - -const Name = "upload" - -func New(p ppi.Plugin) *cobra.Command { - cmd := &cobra.Command{ - Use: Name, - Short: "upload specific operations", - Long: ` -This command group provides all commands used to implement an uploader -described by an uploader descriptor.`, - } - - cmd.AddCommand(validate.New(p)) - cmd.AddCommand(put.New(p)) - return cmd -} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/upload/put/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/upload/put/cmd.go deleted file mode 100644 index b5e1f1a0d..000000000 --- a/pkg/contexts/ocm/plugin/ppi/cmds/upload/put/cmd.go +++ /dev/null @@ -1,110 +0,0 @@ -package put - -import ( - "encoding/json" - "fmt" - "io" - "os" - - "github.com/mandelsoft/goutils/errors" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - - "github.com/open-component-model/ocm/pkg/cobrautils/flag" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/common" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - Name = "put" - OptCreds = common.OptCreds - OptHint = common.OptHint - OptMedia = common.OptMedia - OptArt = common.OptArt -) - -func New(p ppi.Plugin) *cobra.Command { - opts := Options{} - - cmd := &cobra.Command{ - Use: Name + " [] ", - Short: "upload blob to external repository", - Long: ` -Read the blob content from *stdin*, store the blob in the repository specified -by the given repository specification and return the access specification -(as JSON document string) usable to retrieve the blob, again, on * stdout*. -The uploader to use is specified by the first argument. This might only be -relevant, if the plugin supports multiple uploaders. -`, - Args: cobra.ExactArgs(2), - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - return opts.Complete(args) - }, - RunE: func(cmd *cobra.Command, args []string) error { - return Command(p, cmd, &opts) - }, - } - opts.AddFlags(cmd.Flags()) - return cmd -} - -type Options struct { - Name string - Specification json.RawMessage - - Credentials credentials.DirectCredentials - MediaType string - ArtifactType string - - Hint string -} - -func (o *Options) AddFlags(fs *pflag.FlagSet) { - flag.YAMLVarP(fs, &o.Credentials, OptCreds, "c", nil, "credentials") - flag.StringToStringVarPF(fs, &o.Credentials, "credential", "C", nil, "dedicated credential value") - fs.StringVarP(&o.MediaType, OptMedia, "m", "", "media type of input blob") - fs.StringVarP(&o.ArtifactType, OptArt, "a", "", "artifact type of input blob") - fs.StringVarP(&o.Hint, OptHint, "H", "", "reference hint for storing blob") -} - -func (o *Options) Complete(args []string) error { - o.Name = args[0] - if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[1]), &o.Specification); err != nil { - return errors.Wrapf(err, "invalid repository specification") - } - return nil -} - -func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { - spec, err := p.DecodeUploadTargetSpecification(opts.Specification) - if err != nil { - return errors.Wrapf(err, "target specification") - } - - u := p.GetUploader(opts.Name) - if u == nil { - return errors.ErrNotFound(descriptor.KIND_UPLOADER, fmt.Sprintf("%s:%s", opts.ArtifactType, opts.MediaType)) - } - w, h, err := u.Writer(p, opts.ArtifactType, opts.MediaType, opts.Hint, spec, opts.Credentials) - if err != nil { - return err - } - _, err = io.Copy(w, os.Stdin) - if err != nil { - w.Close() - return err - } - err = w.Close() - if err != nil { - return err - } - acc := h() - data, err := json.Marshal(acc) - if err == nil { - cmd.Printf("%s\n", string(data)) - } - return err -} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/upload/validate/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/upload/validate/cmd.go deleted file mode 100644 index a26e75b14..000000000 --- a/pkg/contexts/ocm/plugin/ppi/cmds/upload/validate/cmd.go +++ /dev/null @@ -1,101 +0,0 @@ -package validate - -import ( - "encoding/json" - - "github.com/mandelsoft/goutils/errors" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/common" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - Name = "validate" - OptMedia = common.OptMedia - OptArt = common.OptArt -) - -func New(p ppi.Plugin) *cobra.Command { - opts := Options{} - - cmd := &cobra.Command{ - Use: Name + " [] ", - Short: "validate upload specification", - Long: ` -This command accepts a target specification as argument. It is used to -validate the specification for the specified upoader and to provide some -metadata for the given specification. - -This metadata has to be provided as JSON document string on *stdout* and has the -following fields: - -- **consumerId** *map[string]string* - - The consumer id used to determine optional credentials for the - underlying repository. If specified, at least the type field must - be set. -`, - Args: cobra.ExactArgs(2), - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - return opts.Complete(args) - }, - RunE: func(cmd *cobra.Command, args []string) error { - return Command(p, cmd, &opts) - }, - } - opts.AddFlags(cmd.Flags()) - return cmd -} - -type Options struct { - Name string - Specification json.RawMessage - - ArtifactType string - MediaType string -} - -func (o *Options) AddFlags(fs *pflag.FlagSet) { - fs.StringVarP(&o.MediaType, OptMedia, "m", "", "media type of input blob") - fs.StringVarP(&o.ArtifactType, OptArt, "a", "", "artifact type of input blob") -} - -func (o *Options) Complete(args []string) error { - o.Name = args[0] - if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[1]), &o.Specification); err != nil { - return errors.Wrapf(err, "invalid repository specification") - } - return nil -} - -type Result struct { - ConsumerId credentials.ConsumerIdentity `json:"consumerId"` -} - -func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { - spec, err := p.DecodeUploadTargetSpecification(opts.Specification) - if err != nil { - return errors.Wrapf(err, "target specification") - } - - m := p.GetUploader(opts.Name) - if m == nil { - return errors.ErrUnknown(descriptor.KIND_UPLOADER, spec.GetType()) - } - info, err := m.ValidateSpecification(p, spec) - if err != nil { - return err - } - result := Result{info.ConsumerId} - data, err := json.Marshal(result) - if err != nil { - return err - } - cmd.Printf("%s\n", string(data)) - return nil -} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/valueset/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/valueset/cmd.go deleted file mode 100644 index 2652308e7..000000000 --- a/pkg/contexts/ocm/plugin/ppi/cmds/valueset/cmd.go +++ /dev/null @@ -1,24 +0,0 @@ -package valueset - -import ( - "github.com/spf13/cobra" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/valueset/compose" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/valueset/validate" -) - -const Name = "valueset" - -func New(p ppi.Plugin) *cobra.Command { - cmd := &cobra.Command{ - Use: Name, - Short: "valueset operations", - Long: `This command group provides all commands used to implement a value set -described by a value set descriptor (` + p.Name() + ` descriptor.`, - } - - cmd.AddCommand(compose.New(p)) - cmd.AddCommand(validate.New(p)) - return cmd -} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/valueset/compose/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/valueset/compose/cmd.go deleted file mode 100644 index a9f3ec09f..000000000 --- a/pkg/contexts/ocm/plugin/ppi/cmds/valueset/compose/cmd.go +++ /dev/null @@ -1,96 +0,0 @@ -package compose - -import ( - "encoding/json" - - "github.com/mandelsoft/goutils/errors" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/options" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const Name = "compose" - -func New(p ppi.Plugin) *cobra.Command { - opts := Options{} - - cmd := &cobra.Command{ - Use: Name + " ", - Short: "compose value set from options and base specification", - Long: ` -The task of this command is ued to compose and validate a value set based on -some explicitly given input options and preconfigured specifications. - -The finally composed set has to be returned as JSON document -on *stdout*. - -This command is only used, if for a value set descriptor configuration -na direct composition rules are configured (` + p.Name() + ` descriptor). - -If possible, predefined standard options should be used. In such a case only the -name field should be defined for an option. If required, new options can be -defined by additionally specifying a type and a description. New options should -be used very carefully. The chosen names MUST not conflict with names provided -by other plugins. Therefore, it is highly recommended to use names prefixed -by the plugin name. - -` + options.DefaultRegistry.Usage(), - Args: cobra.ExactArgs(4), - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - return opts.Complete(args) - }, - RunE: func(cmd *cobra.Command, args []string) error { - return Command(p, cmd, &opts) - }, - } - opts.AddFlags(cmd.Flags()) - return cmd -} - -type Options struct { - Purpose string - Name string - Options ppi.Config - Base ppi.Config -} - -func (o *Options) AddFlags(fs *pflag.FlagSet) { -} - -func (o *Options) Complete(args []string) error { - o.Purpose = args[0] - o.Name = args[1] - if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[2]), &o.Options); err != nil { - return errors.Wrapf(err, "invalid avalue set options") - } - if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[3]), &o.Base); err != nil { - return errors.Wrapf(err, "invalid base set specification") - } - return nil -} - -func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { - k, v := runtime.KindVersion(opts.Name) - s := p.GetValueSet(opts.Purpose, k, v) - if s == nil { - return errors.ErrUnknown(descriptor.KIND_VALUESET, opts.Name) - } - err := opts.Options.ConvertFor(s.Options()...) - if err != nil { - return err - } - err = s.ComposeSpecification(p, opts.Options, opts.Base) - if err != nil { - return err - } - data, err := json.Marshal(opts.Base) - if err != nil { - return err - } - cmd.Printf("%s\n", string(data)) - return nil -} diff --git a/pkg/contexts/ocm/plugin/ppi/cmds/valueset/validate/cmd.go b/pkg/contexts/ocm/plugin/ppi/cmds/valueset/validate/cmd.go deleted file mode 100644 index 8b9adcbee..000000000 --- a/pkg/contexts/ocm/plugin/ppi/cmds/valueset/validate/cmd.go +++ /dev/null @@ -1,89 +0,0 @@ -package validate - -import ( - "encoding/json" - - "github.com/mandelsoft/goutils/errors" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const Name = "validate" - -func New(p ppi.Plugin) *cobra.Command { - opts := Options{} - - cmd := &cobra.Command{ - Use: Name + " ", - Short: "validate value set", - Long: ` -This command accepts a value set as argument. It is used to -validate the specification and to provide some metadata for the given -specification. - -This metadata has to be provided as JSON string on *stdout* and has the -following fields: - -- **description** *string* - - A short textual description of the described value set. -`, - Args: cobra.ExactArgs(2), - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - return opts.Complete(args) - }, - RunE: func(cmd *cobra.Command, args []string) error { - return Command(p, cmd, &opts) - }, - } - opts.AddFlags(cmd.Flags()) - return cmd -} - -type Options struct { - Purpose string - Specification json.RawMessage -} - -func (o *Options) AddFlags(fs *pflag.FlagSet) { -} - -func (o *Options) Complete(args []string) error { - o.Purpose = args[0] - if err := runtime.DefaultYAMLEncoding.Unmarshal([]byte(args[1]), &o.Specification); err != nil { - return errors.Wrapf(err, "invalid valueset specification") - } - return nil -} - -type Result struct { - Short string `json:"description"` -} - -func Command(p ppi.Plugin, cmd *cobra.Command, opts *Options) error { - spec, err := p.DecodeValueSet(opts.Purpose, opts.Specification) - if err != nil { - return errors.Wrapf(err, "access specification") - } - - k, v := runtime.KindVersion(spec.GetType()) - m := p.GetValueSet(opts.Purpose, k, v) - if m == nil { - return errors.ErrUnknown(descriptor.KIND_VALUESET, spec.GetType()) - } - info, err := m.ValidateSpecification(p, spec) - if err != nil { - return err - } - result := Result{Short: info.Short} - data, err := json.Marshal(result) - if err != nil { - return err - } - cmd.Printf("%s\n", string(data)) - return nil -} diff --git a/pkg/contexts/ocm/plugin/ppi/config/config.go b/pkg/contexts/ocm/plugin/ppi/config/config.go deleted file mode 100644 index db1658368..000000000 --- a/pkg/contexts/ocm/plugin/ppi/config/config.go +++ /dev/null @@ -1,28 +0,0 @@ -package config - -import ( - "context" - - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds/command" - "github.com/open-component-model/ocm/pkg/runtime" -) - -func init() { - command.RegisterCommandConfigHandler(&commandHandler{}) -} - -type commandHandler struct{} - -func (c commandHandler) HandleConfig(ctx context.Context, data []byte) (context.Context, error) { - var err error - - octx := ocm.DefaultContext() - ctx = octx.BindTo(ctx) - if len(data) != 0 { - _, err = octx.ConfigContext().ApplyData(data, runtime.DefaultYAMLEncoding, " cli config") - // Ugly, enforce configuration update - octx.GetResolver() - } - return ctx, err -} diff --git a/pkg/contexts/ocm/plugin/ppi/interface.go b/pkg/contexts/ocm/plugin/ppi/interface.go deleted file mode 100644 index 22041a903..000000000 --- a/pkg/contexts/ocm/plugin/ppi/interface.go +++ /dev/null @@ -1,213 +0,0 @@ -package ppi - -import ( - "encoding/json" - "io" - - "github.com/spf13/cobra" - - "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/action" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/options" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/internal" - "github.com/open-component-model/ocm/pkg/runtime" -) - -type ( - Descriptor = descriptor.Descriptor - UploaderKey = descriptor.UploaderKey - UploaderDescriptor = descriptor.UploaderDescriptor - DownloaderKey = descriptor.DownloaderKey - DownloaderDescriptor = descriptor.DownloaderDescriptor - AccessMethodDescriptor = descriptor.AccessMethodDescriptor - CLIOption = descriptor.CLIOption - - ActionSpecInfo = internal.ActionSpecInfo - AccessSpecInfo = internal.AccessSpecInfo - ValueSetInfo = internal.ValueSetInfo - UploadTargetSpecInfo = internal.UploadTargetSpecInfo -) - -var REALM = descriptor.REALM - -type Plugin interface { - Name() string - Version() string - Descriptor() descriptor.Descriptor - - SetDescriptorTweaker(func(descriptor descriptor.Descriptor) descriptor.Descriptor) - - SetShort(s string) - SetLong(s string) - SetConfigParser(config func(raw json.RawMessage) (interface{}, error)) - ForwardLogging(b ...bool) - - RegisterDownloader(arttype, mediatype string, u Downloader) error - GetDownloader(name string) Downloader - GetDownloaderFor(arttype, mediatype string) Downloader - - RegisterUploader(arttype, mediatype string, u Uploader) error - GetUploader(name string) Uploader - GetUploaderFor(arttype, mediatype string) Uploader - DecodeUploadTargetSpecification(data []byte) (UploadTargetSpec, error) - - RegisterAccessMethod(m AccessMethod) error - DecodeAccessSpecification(data []byte) (AccessSpec, error) - GetAccessMethod(name string, version string) AccessMethod - - RegisterAction(a Action) error - DecodeAction(data []byte) (ActionSpec, error) - GetAction(name string) Action - - RegisterValueMergeHandler(h ValueMergeHandler) error - GetValueMergeHandler(name string) ValueMergeHandler - - RegisterValueSet(h ValueSet) error - DecodeValueSet(purpose string, data []byte) (runtime.TypedObject, error) - GetValueSet(purpose, name, version string) ValueSet - - RegisterCommand(c Command) error - GetCommand(name string) Command - Commands() []Command - - RegisterConfigType(c cpi.ConfigType) error - GetConfigType(name string) *descriptor.ConfigTypeDescriptor - ConfigTypes() []descriptor.ConfigTypeDescriptor - - GetOptions() *Options - GetConfig() (interface{}, error) -} - -type AccessMethod interface { - runtime.TypedObjectDecoder[AccessSpec] - - Name() string - Version() string - - // Options provides the list of CLI options supported to compose the access - // specification. - Options() []options.OptionType - - // Description provides a general description for the access mehod kind. - Description() string - // Format describes the attributes of the dedicated version. - Format() string - - ValidateSpecification(p Plugin, spec AccessSpec) (info *AccessSpecInfo, err error) - Reader(p Plugin, spec AccessSpec, creds credentials.Credentials) (io.ReadCloser, error) - ComposeAccessSpecification(p Plugin, opts Config, config Config) error -} - -type AccessSpec = runtime.TypedObject - -type AccessSpecProvider func() AccessSpec - -type UploadFormats runtime.KnownTypes[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]] - -type Uploader interface { - Decoders() UploadFormats - - Name() string - Description() string - - ValidateSpecification(p Plugin, spec UploadTargetSpec) (info *UploadTargetSpecInfo, err error) - Writer(p Plugin, arttype, mediatype string, hint string, spec UploadTargetSpec, creds credentials.Credentials) (io.WriteCloser, AccessSpecProvider, error) -} - -type UploadTargetSpec = runtime.TypedObject - -type DownloadResultProvider func() (string, error) - -type Downloader interface { - Name() string - Description() string - ConfigSchema() []byte - - Writer(p Plugin, arttype, mediatype string, filepath string, config []byte) (io.WriteCloser, DownloadResultProvider, error) -} - -type ActionSpec = action.ActionSpec - -type ActionResult = action.ActionResult - -type Action interface { - Name() string - Description() string - DefaultSelectors() []string - ConsumerType() string - - Execute(p Plugin, spec ActionSpec, creds credentials.DirectCredentials) (result ActionResult, err error) -} - -type Value = runtime.RawValue - -type ValueMergeResult struct { - Modified bool `json:"modified"` - Value Value `json:"value"` - Message string `json:"message,omitempty"` -} - -type ValueMergeData struct { - Local Value `json:"local"` - Inbound Value `json:"inbound"` -} - -type ValueMergeHandler interface { - Name() string - Description() string - - Execute(p Plugin, local Value, inbound Value, config json.RawMessage) (result ValueMergeResult, err error) -} - -type ValueSet interface { - runtime.TypedObjectDecoder[AccessSpec] - - Name() string - Version() string - - // Purposes describes the purposes the set should be ued for. - // So far, only the purpose PURPOSE_ROUTINGSLIP is defined. - Purposes() []string - - // Options provides the list of CLI options supported to compose the access - // specification. - Options() []options.OptionType - - // Description provides a general description for the access mehod kind. - Description() string - // Format describes the attributes of the dedicated version. - Format() string - - ValidateSpecification(p Plugin, spec runtime.TypedObject) (info *ValueSetInfo, err error) - ComposeSpecification(p Plugin, opts Config, config Config) error -} - -// Command is the interface for a CLI command provided by a plugin. -type Command interface { - // Name of command used in the plugin. - // This is also the default object type and is used to - // name top-level commands in the CLI. - Name() string - Description() string - Usage() string - Short() string - Example() string - // ObjectType is optional and can be used - // together with a verb. It then is used as - // sub command name for the object type. - // By default, the command name is used. - ObjectType() string - // Verb is optional and can be set - // to place the command in the verb hierarchy of - // the OCM CLI. It is used together with the ObjectType. - // (command will be *ocm *. - Verb() string - // Realm is optional and is used to place the command - // in a realm. This requires a verb. - Realm() string - CLIConfigRequired() bool - - Command() *cobra.Command -} diff --git a/pkg/contexts/ocm/plugin/ppi/logging/config.go b/pkg/contexts/ocm/plugin/ppi/logging/config.go deleted file mode 100644 index 598c2a247..000000000 --- a/pkg/contexts/ocm/plugin/ppi/logging/config.go +++ /dev/null @@ -1,25 +0,0 @@ -package logging - -import ( - "sigs.k8s.io/yaml" - - "github.com/open-component-model/ocm/pkg/cobrautils/logopts/logging" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/ppi/cmds" -) - -func init() { - cmds.RegisterLoggingConfigHandler(&loggingConfigHandler{}) -} - -type loggingConfigHandler struct{} - -func (l loggingConfigHandler) HandleConfig(data []byte) error { - var cfg logging.LoggingConfiguration - - err := yaml.Unmarshal(data, &cfg) - if err != nil { - return err - } - - return cfg.Apply() -} diff --git a/pkg/contexts/ocm/plugin/ppi/options.go b/pkg/contexts/ocm/plugin/ppi/options.go deleted file mode 100644 index 77cffcf5b..000000000 --- a/pkg/contexts/ocm/plugin/ppi/options.go +++ /dev/null @@ -1,24 +0,0 @@ -package ppi - -import ( - "encoding/json" - - "github.com/spf13/pflag" - - "github.com/open-component-model/ocm/pkg/cobrautils/flag" -) - -const ( - OptPluginConfig = "config" - OptPlugingLogConfig = "log-config" -) - -type Options struct { - Config json.RawMessage - LogConfig json.RawMessage -} - -func (o *Options) AddFlags(fs *pflag.FlagSet) { - flag.YAMLVarP(fs, &o.Config, OptPluginConfig, "c", nil, "plugin configuration") - flag.YAMLVarP(fs, &o.LogConfig, OptPlugingLogConfig, "", nil, "ocm logging configuration") -} diff --git a/pkg/contexts/ocm/plugin/ppi/plugin.go b/pkg/contexts/ocm/plugin/ppi/plugin.go deleted file mode 100644 index 081969be8..000000000 --- a/pkg/contexts/ocm/plugin/ppi/plugin.go +++ /dev/null @@ -1,640 +0,0 @@ -package ppi - -import ( - "encoding/json" - "fmt" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/general" - "github.com/mandelsoft/goutils/generics" - "github.com/mandelsoft/goutils/maputils" - "github.com/spf13/cobra" - "golang.org/x/exp/slices" - - "github.com/open-component-model/ocm/pkg/cobrautils" - "github.com/open-component-model/ocm/pkg/contexts/config" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/action" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/options" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils/registry" - "github.com/open-component-model/ocm/pkg/errkind" - "github.com/open-component-model/ocm/pkg/runtime" -) - -type plugin struct { - name string - version string - descriptor descriptor.Descriptor - tweaker func(descriptor descriptor.Descriptor) descriptor.Descriptor - options Options - - downloaders map[string]Downloader - downmappings *registry.Registry[Downloader, DownloaderKey] - - uploaders map[string]Uploader - upmappings *registry.Registry[Uploader, UploaderKey] - uploaderScheme runtime.Scheme[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]] - - methods map[string]AccessMethod - accessScheme runtime.Scheme[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]] - - actions map[string]Action - mergehandlers map[string]ValueMergeHandler - mergespecs map[string]*descriptor.LabelMergeSpecification - - valuesets map[string]map[string]ValueSet - setScheme map[string]runtime.Scheme[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]] - - clicmds map[string]Command - - configParser func(message json.RawMessage) (interface{}, error) -} - -func NewPlugin(name string, version string) Plugin { - return &plugin{ - name: name, - version: version, - methods: map[string]AccessMethod{}, - - downloaders: map[string]Downloader{}, - downmappings: registry.NewRegistry[Downloader, DownloaderKey](), - - uploaders: map[string]Uploader{}, - upmappings: registry.NewRegistry[Uploader, UploaderKey](), - - accessScheme: runtime.MustNewDefaultScheme[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]](&runtime.UnstructuredVersionedTypedObject{}, false, nil), - uploaderScheme: runtime.MustNewDefaultScheme[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]](&runtime.UnstructuredVersionedTypedObject{}, false, nil), - - actions: map[string]Action{}, - mergehandlers: map[string]ValueMergeHandler{}, - mergespecs: map[string]*descriptor.LabelMergeSpecification{}, - - valuesets: map[string]map[string]ValueSet{}, - setScheme: map[string]runtime.Scheme[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]]{}, - - clicmds: map[string]Command{}, - - descriptor: descriptor.Descriptor{ - Version: descriptor.VERSION, - PluginName: name, - PluginVersion: version, - }, - } -} - -func (p *plugin) Name() string { - return p.name -} - -func (p *plugin) Version() string { - return p.version -} - -func (p *plugin) Descriptor() descriptor.Descriptor { - if p.tweaker != nil { - return p.tweaker(p.descriptor) - } - return p.descriptor -} - -func (p *plugin) GetOptions() *Options { - return &p.options -} - -func (p *plugin) SetLong(s string) { - p.descriptor.Long = s -} - -func (p *plugin) SetShort(s string) { - p.descriptor.Short = s -} - -func (p *plugin) SetDescriptorTweaker(t func(descriptor descriptor.Descriptor) descriptor.Descriptor) { - p.tweaker = t -} - -func (p *plugin) SetConfigParser(config func(raw json.RawMessage) (interface{}, error)) { - p.configParser = config -} - -func (p *plugin) ForwardLogging(b ...bool) { - p.descriptor.ForwardLogging = general.OptionalDefaultedBool(true, b...) -} - -func (p *plugin) GetConfig() (interface{}, error) { - if len(p.options.Config) == 0 { - return nil, nil - } - if p.configParser == nil { - var cfg interface{} - if err := json.Unmarshal(p.options.Config, &cfg); err != nil { - return nil, err - } - return &cfg, nil - } - return p.configParser(p.options.Config) -} - -//////////////////////////////////////////////////////////////////////////////// - -func (p *plugin) RegisterDownloader(arttype, mediatype string, hdlr Downloader) error { - key := DownloaderKey{}.SetArtifact(arttype, mediatype) - if !key.IsValid() { - return errors.ErrInvalid("artifact context") - } - - old := p.downloaders[hdlr.Name()] - if old != nil && old != hdlr { - return fmt.Errorf("downloader name %q already in use", hdlr.Name()) - } - - var desc *DownloaderDescriptor - if old == nil { - schema := "" - if len(hdlr.ConfigSchema()) > 0 { - schema = string(hdlr.ConfigSchema()) - } - desc = &DownloaderDescriptor{ - Name: hdlr.Name(), - Description: hdlr.Description(), - Constraints: []DownloaderKey{}, - ConfigScheme: schema, - } - p.descriptor.Downloaders = append(p.descriptor.Downloaders, *desc) - desc = &p.descriptor.Downloaders[len(p.descriptor.Downloaders)-1] - } else { - for i := range p.descriptor.Downloaders { - if p.descriptor.Downloaders[i].Name == hdlr.Name() { - desc = &p.descriptor.Downloaders[i] - } - } - } - - cur := p.downmappings.GetHandler(key) - if len(cur) > 0 && cur[0] != hdlr { - return fmt.Errorf("downloader mapping key %q already in use", key) - } - if cur == nil { - p.downmappings.Register(key, hdlr) - desc.Constraints = append(desc.Constraints, DownloaderKey{ArtifactType: key.ArtifactType, MediaType: key.MediaType}) - } - p.downloaders[hdlr.Name()] = hdlr - return nil -} - -func (p *plugin) GetDownloader(name string) Downloader { - return p.downloaders[name] -} - -func (p *plugin) GetDownloaderFor(arttype, mediatype string) Downloader { - h := p.downmappings.LookupHandler(DownloaderKey{}.SetArtifact(arttype, mediatype)) - if len(h) == 0 { - return nil - } - return h[0] -} - -//////////////////////////////////////////////////////////////////////////////// - -func (p *plugin) RegisterRepositoryContextUploader(contexttype, repotype, arttype, mediatype string, u Uploader) error { - if contexttype == "" || repotype == "" { - return fmt.Errorf("repository context required") - } - return p.registerUploader(UploaderKey{}.SetArtifact(arttype, mediatype).SetRepo(contexttype, repotype), u) -} - -func (p *plugin) RegisterUploader(arttype, mediatype string, u Uploader) error { - return p.registerUploader(UploaderKey{}.SetArtifact(arttype, mediatype), u) -} - -func (p *plugin) registerUploader(key UploaderKey, hdlr Uploader) error { - if !key.RepositoryContext.IsValid() { - return errors.ErrInvalid("repository context") - } - if !key.ArtifactContext.IsValid() { - return errors.ErrInvalid("artifact context") - } - old := p.uploaders[hdlr.Name()] - if old != nil && old != hdlr { - return fmt.Errorf("uploader name %q already in use", hdlr.Name()) - } - - var desc *UploaderDescriptor - if old == nil { - desc = &UploaderDescriptor{ - Name: hdlr.Name(), - Description: hdlr.Description(), - Constraints: []UploaderKey{}, - } - p.descriptor.Uploaders = append(p.descriptor.Uploaders, *desc) - desc = &p.descriptor.Uploaders[len(p.descriptor.Uploaders)-1] - } else { - for i := range p.descriptor.Uploaders { - if p.descriptor.Uploaders[i].Name == hdlr.Name() { - desc = &p.descriptor.Uploaders[i] - } - } - } - - cur := p.upmappings.GetHandler(key) - if len(cur) > 0 && cur[0] != hdlr { - return fmt.Errorf("uploader mapping key %q already in use", key) - } - list := errors.ErrListf("uploader decoders") - for n, d := range hdlr.Decoders() { - list.Add(p.uploaderScheme.RegisterByDecoder(n, d)) - } - if list.Len() > 0 { - return list.Result() - } - if cur == nil { - p.upmappings.Register(key, hdlr) - desc.Constraints = append(desc.Constraints, key) - } - p.uploaders[hdlr.Name()] = hdlr - return nil -} - -func (p *plugin) GetUploader(name string) Uploader { - return p.uploaders[name] -} - -func (p *plugin) GetUploaderFor(arttype, mediatype string) Uploader { - h := p.upmappings.LookupHandler(UploaderKey{}.SetArtifact(arttype, mediatype)) - if len(h) == 0 { - return nil - } - return h[0] -} - -func (p *plugin) DecodeUploadTargetSpecification(data []byte) (UploadTargetSpec, error) { - o, err := p.uploaderScheme.Decode(data, nil) - if err != nil { - return nil, err - } - return o, nil -} - -//////////////////////////////////////////////////////////////////////////////// - -func (p *plugin) RegisterAccessMethod(m AccessMethod) error { - if p.GetAccessMethod(m.Name(), m.Version()) != nil { - n := m.Name() - if m.Version() != "" { - n += runtime.VersionSeparator + m.Version() - } - return errors.ErrAlreadyExists(errkind.KIND_ACCESSMETHOD, n) - } - - var optlist []CLIOption - for _, o := range m.Options() { - known := options.DefaultRegistry.GetOptionType(o.GetName()) - if known != nil { - if o.ValueType() != known.ValueType() { - return fmt.Errorf("option type %s[%s] conflicts with standard option type using value type %s", o.GetName(), o.ValueType(), known.ValueType()) - } - optlist = append(optlist, CLIOption{ - Name: o.GetName(), - }) - } else { - optlist = append(optlist, CLIOption{ - Name: o.GetName(), - Type: o.ValueType(), - Description: o.GetDescriptionText(), - }) - } - } - vers := m.Version() - if vers == "" { - meth := descriptor.AccessMethodDescriptor{ - ValueSetDefinition: descriptor.ValueSetDefinition{ - ValueTypeDefinition: descriptor.ValueTypeDefinition{ - Name: m.Name(), - Description: m.Description(), - Format: m.Format(), - }, - }, - } - p.descriptor.AccessMethods = append(p.descriptor.AccessMethods, meth) - p.accessScheme.RegisterByDecoder(m.Name(), m) - p.methods[m.Name()] = m - vers = "v1" - } - meth := descriptor.AccessMethodDescriptor{ - ValueSetDefinition: descriptor.ValueSetDefinition{ - ValueTypeDefinition: descriptor.ValueTypeDefinition{ - Name: m.Name(), - Version: vers, - Description: m.Description(), - Format: m.Format(), - }, - CLIOptions: optlist, - }, - } - p.descriptor.AccessMethods = append(p.descriptor.AccessMethods, meth) - p.accessScheme.RegisterByDecoder(m.Name()+"/"+vers, m) - p.methods[m.Name()+"/"+vers] = m - return nil -} - -func (p *plugin) DecodeAccessSpecification(data []byte) (AccessSpec, error) { - return p.accessScheme.Decode(data, nil) -} - -func (p *plugin) GetAccessMethod(name string, version string) AccessMethod { - n := name - if version != "" { - n += "/" + version - } - return p.methods[n] -} - -//////////////////////////////////////////////////////////////////////////////// - -func (p *plugin) RegisterAction(a Action) error { - if p.GetAction(a.Name()) != nil { - return errors.ErrAlreadyExists("action", a.Name()) - } - vers := action.DefaultRegistry().SupportedActionVersions(a.Name()) - if len(vers) == 0 { - return errors.ErrNotSupported("action", a.Name()) - } - - act := descriptor.ActionDescriptor{ - Name: a.Name(), - Versions: vers, - Description: a.Description(), - DefaultSelectors: a.DefaultSelectors(), - ConsumerType: a.ConsumerType(), - } - p.descriptor.Actions = append(p.descriptor.Actions, act) - p.actions[a.Name()] = a - return nil -} - -func (p *plugin) DecodeAction(data []byte) (ActionSpec, error) { - return action.DefaultRegistry().DecodeActionSpec(data, runtime.DefaultJSONEncoding) -} - -func (p *plugin) GetAction(name string) Action { - return p.actions[name] -} - -//////////////////////////////////////////////////////////////////////////////// - -func (p *plugin) RegisterValueMergeHandler(a ValueMergeHandler) error { - if p.GetValueMergeHandler(a.Name()) != nil { - return errors.ErrAlreadyExists("value mergehandler", a.Name()) - } - - hd := descriptor.ValueMergeHandlerDescriptor{ - Name: a.Name(), - Description: a.Description(), - } - p.descriptor.ValueMergeHandlers = append(p.descriptor.ValueMergeHandlers, hd) - p.mergehandlers[a.Name()] = a - return nil -} - -func (p *plugin) GetValueMergeHandler(name string) ValueMergeHandler { - return p.mergehandlers[name] -} - -func (p *plugin) RegisterLabelMergeSpecification(name, version string, spec *metav1.MergeAlgorithmSpecification, desc string) error { - e := descriptor.LabelMergeSpecification{ - Name: name, - Version: version, - Description: desc, - MergeAlgorithmSpecification: *spec, - } - - if p.GetLabelMergeSpecification(e.GetName()) != nil { - return errors.ErrAlreadyExists("label merge spec", e.GetName()) - } - - p.descriptor.LabelMergeSpecifications = append(p.descriptor.LabelMergeSpecifications, e) - p.mergespecs[e.GetName()] = &e - return nil -} - -func (p *plugin) GetLabelMergeSpecification(id string) *descriptor.LabelMergeSpecification { - return p.mergespecs[id] -} - -//////////////////////////////////////////////////////////////////////////////// - -func (p *plugin) DecodeValueSet(purpose string, data []byte) (runtime.TypedObject, error) { - schemes := p.setScheme[purpose] - if schemes == nil { - return nil, errors.ErrUnknown(descriptor.KIND_PURPOSE) - } - return schemes.Decode(data, nil) -} - -func (p *plugin) GetValueSet(purpose string, name string, version string) ValueSet { - n := name - if version != "" { - n += "/" + version - } - set := p.valuesets[purpose] - if set == nil { - return nil - } - return set[n] -} - -func (p *plugin) RegisterValueSet(s ValueSet) error { - n := s.Name() - if s.Version() != "" { - n += runtime.VersionSeparator + s.Version() - } - for _, pp := range s.Purposes() { - if p.GetValueSet(pp, s.Name(), s.Version()) != nil { - return errors.ErrAlreadyExists(descriptor.KIND_VALUESET, n) - } - } - - var optlist []CLIOption - for _, o := range s.Options() { - known := options.DefaultRegistry.GetOptionType(o.GetName()) - if known != nil { - if o.ValueType() != known.ValueType() { - return fmt.Errorf("option type %s[%s] conflicts with standard option type using value type %s", o.GetName(), o.ValueType(), known.ValueType()) - } - optlist = append(optlist, CLIOption{ - Name: o.GetName(), - }) - } else { - optlist = append(optlist, CLIOption{ - Name: o.GetName(), - Type: o.ValueType(), - Description: o.GetDescriptionText(), - }) - } - } - vers := s.Version() - if vers == "" { - set := descriptor.ValueSetDescriptor{ - ValueSetDefinition: descriptor.ValueSetDefinition{ - ValueTypeDefinition: descriptor.ValueTypeDefinition{ - Name: s.Name(), - Description: s.Description(), - Format: s.Format(), - }, - }, - Purposes: slices.Clone(s.Purposes()), - } - p.descriptor.ValueSets = append(p.descriptor.ValueSets, set) - for _, pp := range s.Purposes() { - schemes := p.setScheme[pp] - if schemes == nil { - schemes = runtime.MustNewDefaultScheme[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]](&runtime.UnstructuredVersionedTypedObject{}, false, nil) - p.setScheme[pp] = schemes - } - schemes.RegisterByDecoder(s.Name(), s) - sets := p.valuesets[pp] - if sets == nil { - sets = map[string]ValueSet{} - p.valuesets[pp] = sets - } - sets[s.Name()] = s - } - vers = "v1" - } - set := descriptor.ValueSetDescriptor{ - ValueSetDefinition: descriptor.ValueSetDefinition{ - ValueTypeDefinition: descriptor.ValueTypeDefinition{ - Name: s.Name(), - Version: vers, - Description: s.Description(), - Format: s.Format(), - }, - CLIOptions: optlist, - }, - Purposes: slices.Clone(s.Purposes()), - } - p.descriptor.ValueSets = append(p.descriptor.ValueSets, set) - for _, pp := range s.Purposes() { - schemes := p.setScheme[pp] - if schemes == nil { - schemes = runtime.MustNewDefaultScheme[runtime.TypedObject, runtime.TypedObjectDecoder[runtime.TypedObject]](&runtime.UnstructuredVersionedTypedObject{}, false, nil) - p.setScheme[pp] = schemes - } - schemes.RegisterByDecoder(s.Name()+"/"+vers, s) - sets := p.valuesets[pp] - if sets == nil { - sets = map[string]ValueSet{} - p.valuesets[pp] = sets - } - sets[s.Name()+"/"+vers] = s - } - return nil -} - -//////////////////////////////////////////////////////////////////////////////// - -func (p *plugin) GetCommand(name string) Command { - return p.clicmds[name] -} - -func (p *plugin) RegisterCommand(c Command) error { - if p.GetCommand(c.Name()) != nil { - return errors.ErrAlreadyExists("cli command spec", c.Name()) - } - if c.Realm() != "" && c.Verb() == "" { - return errors.Newf("realm requires verb") - } - cmd := c.Command() - if cmd.HasSubCommands() && c.Verb() != "" { - return errors.Newf("no sub commands allowd for CLI command for verb") - } - - objtype := c.ObjectType() - if objtype == c.Name() { - objtype = "" - } - p.descriptor.Commands = append(p.descriptor.Commands, descriptor.CommandDescriptor{ - Name: c.Name(), - Description: c.Description(), - Usage: c.Usage(), - Short: c.Short(), - Example: c.Example(), - Realm: c.Realm(), - ObjectType: objtype, - Verb: c.Verb(), - CLIConfigRequired: c.CLIConfigRequired(), - }) - - path := []string{"ocm"} - if c.Verb() != "" { - path = append(path, c.Verb(), c.ObjectType()) - cobrautils.SetCommandSubstitutionForTree(cmd, 3, path) - } else { - cobrautils.SetCommandSubstitutionForTree(cmd, 2, path) - } - - orig := cmd.HelpFunc() - cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { - var err error - // look for arguments of the command. - // wrong args passed to help function, instead - // of the sub command args, the complete command line is - // passed. - _, args, err = cmd.Root().Traverse(args) - if len(args) > 0 && err == nil { - cmd, args, _ = cmd.Find(args) - } - orig(cmd, args) - }) - p.clicmds[c.Name()] = c - return nil -} - -func (p *plugin) Commands() []Command { - return maputils.OrderedValues(p.clicmds) -} - -//////////////////////////////////////////////////////////////////////////////// - -func (p *plugin) GetConfigType(name string) *descriptor.ConfigTypeDescriptor { - var def *descriptor.ConfigTypeDescriptor - for _, d := range p.descriptor.ConfigTypes { - v := d.Name - if d.Version != "" { - v += runtime.VersionSeparator + d.Version - } - if v == name { - return &d - } - if d.Name == name && (def == nil || d.Version == "v1") { - def = generics.Pointer(d) - } - } - return def -} - -func (p *plugin) RegisterConfigType(t config.ConfigType) error { - name := t.GetKind() - version := "" - if t.GetType() != t.GetKind() { - version = t.GetVersion() - } - if f := p.GetConfigType(t.GetType()); f != nil { - if version == f.Version { - return errors.ErrAlreadyExists("config type", t.GetType()) - } - } - - p.descriptor.ConfigTypes = append(p.descriptor.ConfigTypes, descriptor.ConfigTypeDescriptor{ - Name: name, - Version: version, - Description: t.Usage(), - // TODO: separate format and description - }) - return nil -} - -func (p *plugin) ConfigTypes() []descriptor.ConfigTypeDescriptor { - return slices.Clone(p.descriptor.ConfigTypes) -} diff --git a/pkg/contexts/ocm/plugin/ppi/utils.go b/pkg/contexts/ocm/plugin/ppi/utils.go deleted file mode 100644 index 1eb8f9ab5..000000000 --- a/pkg/contexts/ocm/plugin/ppi/utils.go +++ /dev/null @@ -1,145 +0,0 @@ -package ppi - -import ( - "encoding/json" - "reflect" - - "github.com/pkg/errors" - "golang.org/x/exp/slices" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/options" - "github.com/open-component-model/ocm/pkg/runtime" -) - -type decoder runtime.TypedObjectDecoder[runtime.TypedObject] - -type AccessMethodBase struct { - decoder - nameDescription - - version string - format string -} - -func MustNewAccessMethodBase(name, version string, proto AccessSpec, desc string, format string) AccessMethodBase { - decoder, err := runtime.NewDirectDecoder(proto) - if err != nil { - panic(err) - } - - return AccessMethodBase{ - decoder: decoder, - nameDescription: nameDescription{ - name: name, - desc: desc, - }, - version: version, - format: format, - } -} - -func (b *AccessMethodBase) Version() string { - return b.version -} - -func (b *AccessMethodBase) Format() string { - return b.format -} - -//////////////////////////////////////////////////////////////////////////////// - -type UploaderBase = nameDescription - -func MustNewUploaderBase(name, desc string) UploaderBase { - return UploaderBase{ - name: name, - desc: desc, - } -} - -//////////////////////////////////////////////////////////////////////////////// - -type ValueSetBase struct { - decoder - nameDescription - - version string - format string - - purposes []string -} - -func MustNewValueSetBase(name, version string, proto runtime.TypedObject, purposes []string, desc string, format string) ValueSetBase { - decoder, err := runtime.NewDirectDecoder(proto) - if err != nil { - panic(err) - } - return ValueSetBase{ - decoder: decoder, - nameDescription: nameDescription{ - name: name, - desc: desc, - }, - version: version, - format: format, - purposes: slices.Clone(purposes), - } -} - -func (b *ValueSetBase) Version() string { - return b.version -} - -func (b *ValueSetBase) Format() string { - return b.format -} - -func (b *ValueSetBase) Purposes() []string { - return b.purposes -} - -//////////////////////////////////////////////////////////////////////////////// - -type nameDescription struct { - name string - desc string -} - -func (b *nameDescription) Name() string { - return b.name -} - -func (b *nameDescription) Description() string { - return b.desc -} - -//////////////////////////////////////////////////////////////////////////////// - -// Config is a generic structured config stored in a string map. -type Config map[string]interface{} - -func (c Config) GetValue(name string) (interface{}, bool) { - v, ok := c[name] - return v, ok -} - -func (c Config) ConvertFor(list ...options.OptionType) error { - for _, o := range list { - if v, ok := c[o.GetName()]; ok { - t := reflect.TypeOf(o.Create().Value()) - if t != reflect.TypeOf(v) { - data, err := json.Marshal(v) - if err != nil { - return errors.Wrapf(err, "cannot marshal option value for %q", o.GetName()) - } - value := reflect.New(t) - err = json.Unmarshal(data, value.Interface()) - if err != nil { - return errors.Wrapf(err, "cannot unmarshal option value for %q[%s]", o.GetName(), o.ValueType()) - } - c[o.GetName()] = value.Elem().Interface() - } - } - } - return nil -} diff --git a/pkg/contexts/ocm/plugin/testutils/plugintests.go b/pkg/contexts/ocm/plugin/testutils/plugintests.go deleted file mode 100644 index 6bc3352b0..000000000 --- a/pkg/contexts/ocm/plugin/testutils/plugintests.go +++ /dev/null @@ -1,31 +0,0 @@ -package testutils - -import ( - "github.com/mandelsoft/goutils/testutils" - - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugincacheattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/plugindirattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/cache" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/plugins" -) - -type TempPluginDir = testutils.TempDir - -func ConfigureTestPlugins2(ctx ocm.ContextProvider, path string) (TempPluginDir, plugins.Set, error) { - t, err := ConfigureTestPlugins(ctx, path) - if err != nil { - return nil, nil, err - } - return t, plugincacheattr.Get(ctx), nil -} - -func ConfigureTestPlugins(ctx ocm.ContextProvider, path string) (TempPluginDir, error) { - t, err := testutils.NewTempDir(testutils.WithDirContent(path)) - if err != nil { - return nil, err - } - cache.DirectoryCache.Reset() - plugindirattr.Set(ctx.OCMContext(), t.Path()) - return t, nil -} diff --git a/pkg/contexts/ocm/plugin/utils.go b/pkg/contexts/ocm/plugin/utils.go deleted file mode 100644 index c89061327..000000000 --- a/pkg/contexts/ocm/plugin/utils.go +++ /dev/null @@ -1,30 +0,0 @@ -package plugin - -import ( - "encoding/json" - - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/iotools" -) - -type AccessDataWriter struct { - plugin Plugin - creds json.RawMessage - accspec json.RawMessage -} - -func NewAccessDataWriter(p Plugin, creds, accspec json.RawMessage) *AccessDataWriter { - return &AccessDataWriter{p, creds, accspec} -} - -func (d *AccessDataWriter) WriteTo(w accessio.Writer) (int64, digest.Digest, error) { - dw := iotools.NewDefaultDigestWriter(accessio.NopWriteCloser(w)) - err := d.plugin.Get(dw, d.creds, d.accspec) - if err != nil { - return blobaccess.BLOB_UNKNOWN_SIZE, blobaccess.BLOB_UNKNOWN_DIGEST, err - } - return dw.Size(), dw.Digest(), nil -} diff --git a/pkg/contexts/ocm/pubsub/attr.go b/pkg/contexts/ocm/pubsub/attr.go deleted file mode 100644 index 34b170dd2..000000000 --- a/pkg/contexts/ocm/pubsub/attr.go +++ /dev/null @@ -1,40 +0,0 @@ -package pubsub - -import ( - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" -) - -const ATTR_PUBSUB_TYPES = "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub" - -type Attribute struct { - ProviderRegistry - TypeScheme -} - -func For(ctx cpi.ContextProvider) *Attribute { - if ctx == nil { - return &Attribute{ - ProviderRegistry: DefaultRegistry, - TypeScheme: DefaultTypeScheme, - } - } - return ctx.OCMContext().GetAttributes().GetOrCreateAttribute(ATTR_PUBSUB_TYPES, create).(*Attribute) -} - -func create(datacontext.Context) interface{} { - return &Attribute{ - ProviderRegistry: NewProviderRegistry(DefaultRegistry), - TypeScheme: NewTypeScheme(DefaultTypeScheme), - } -} - -func SetSchemeFor(ctx cpi.ContextProvider, registry TypeScheme) { - attr := For(ctx) - attr.TypeScheme = registry -} - -func SetProvidersFor(ctx cpi.ContextProvider, registry ProviderRegistry) { - attr := For(ctx) - attr.ProviderRegistry = registry -} diff --git a/pkg/contexts/ocm/pubsub/interface.go b/pkg/contexts/ocm/pubsub/interface.go deleted file mode 100644 index 5ea017b9b..000000000 --- a/pkg/contexts/ocm/pubsub/interface.go +++ /dev/null @@ -1,225 +0,0 @@ -package pubsub - -import ( - "encoding/json" - "fmt" - "slices" - "sync" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/general" - "github.com/mandelsoft/goutils/generics" - "github.com/mandelsoft/goutils/optionutils" - "github.com/modern-go/reflect2" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/errkind" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/runtime/descriptivetype" -) - -const KIND_PUBSUBTYPE = "pub/sub" - -type Option = descriptivetype.Option - -func WithFormatSpec(fmt string) Option { - return descriptivetype.WithFormatSpec(fmt) -} - -func WithDesciption(desc string) Option { - return descriptivetype.WithDescription(desc) -} - -//////////////////////////////////////////////////////////////////////////////// - -type PubSubType descriptivetype.TypedObjectType[PubSubSpec] - -// PubSubSpec is the interface publish/subscribe specifications -// must fulfill. The main task is to map the specification -// to a concrete implementation of the pub/sub adapter -// which forwards events to the described system. -type PubSubSpec interface { - runtime.VersionedTypedObject - - PubSubMethod(repo cpi.Repository) (PubSubMethod, error) - Describe(ctx cpi.Context) string -} - -type ( - PubSubSpecDecoder = runtime.TypedObjectDecoder[PubSubSpec] - PubSubTypeProvider = runtime.KnownTypesProvider[PubSubSpec, PubSubType] -) - -// PubSubMethod is the handler able to publish -// an OCM component version event. -type PubSubMethod interface { - NotifyComponentVersion(version common.NameVersion) error -} - -// TypeScheme is the registry for specification types for -// PubSub types. A PubSub type is finally able to -// provide an implementation for notifying a dedicated -// PubSub instance. -type TypeScheme descriptivetype.TypeScheme[PubSubSpec, PubSubType] - -func NewTypeScheme(base ...TypeScheme) TypeScheme { - return descriptivetype.NewTypeScheme[PubSubSpec, PubSubType, TypeScheme]("PubSub type", nil, &UnknownPubSubSpec{}, false, base...) -} - -func NewStrictTypeScheme(base ...TypeScheme) runtime.VersionedTypeRegistry[PubSubSpec, PubSubType] { - return descriptivetype.NewTypeScheme[PubSubSpec, PubSubType, TypeScheme]("PubSub type", nil, &UnknownPubSubSpec{}, false, base...) -} - -// DefaultTypeScheme contains all globally known PubSub serializers. -var DefaultTypeScheme = NewTypeScheme() - -func RegisterType(atype PubSubType) { - DefaultTypeScheme.Register(atype) -} - -func CreatePubSubSpec(t runtime.TypedObject) (PubSubSpec, error) { - return DefaultTypeScheme.Convert(t) -} - -func NewPubSubType[I PubSubSpec](name string, opts ...Option) PubSubType { - t := descriptivetype.NewTypedObjectTypeObject[PubSubSpec](runtime.NewVersionedTypedObjectType[PubSubSpec, I](name)) - ta := descriptivetype.NewTypeObjectTarget[PubSubSpec](t) - optionutils.ApplyOptions[descriptivetype.OptionTarget](ta, opts...) - return t -} - -//////////////////////////////////////////////////////////////////////////////// - -type UnknownPubSubSpec struct { - runtime.UnstructuredVersionedTypedObject `json:",inline"` -} - -var ( - _ runtime.TypedObject = &UnknownPubSubSpec{} - _ runtime.Unknown = &UnknownPubSubSpec{} -) - -func (_ *UnknownPubSubSpec) IsUnknown() bool { - return true -} - -func (s *UnknownPubSubSpec) PubSubMethod(repository cpi.Repository) (PubSubMethod, error) { - return nil, errors.ErrUnknown(KIND_PUBSUBTYPE, s.GetType()) -} - -func (s *UnknownPubSubSpec) Describe(ctx cpi.Context) string { - return fmt.Sprintf("unknown PubSub specification type %q", s.GetType()) -} - -var _ PubSubSpec = &UnknownPubSubSpec{} - -//////////////////////////////////////////////////////////////////////////////// - -type Unwrapable interface { - Unwrap(ctx cpi.Context) []PubSubSpec -} - -type Evaluatable interface { - Evaluate(ctx cpi.Context) (PubSubSpec, error) -} - -//////////////////////////////////////////////////////////////////////////////// - -type GenericPubSubSpec struct { - runtime.UnstructuredVersionedTypedObject `json:",inline"` - - lock sync.Mutex - cached PubSubSpec - cachedData []byte -} - -var ( - _ PubSubSpec = &GenericPubSubSpec{} - _ Unwrapable = &GenericPubSubSpec{} - _ Evaluatable = &GenericPubSubSpec{} -) - -func ToGenericPubSubSpec(spec PubSubSpec) (*GenericPubSubSpec, error) { - if reflect2.IsNil(spec) { - return nil, nil - } - if g, ok := spec.(*GenericPubSubSpec); ok { - return g, nil - } - data, err := json.Marshal(spec) - if err != nil { - return nil, err - } - return newGenericPubSubSpec(data, runtime.DefaultJSONEncoding) -} - -func NewGenericPubSubSpec(data []byte, unmarshaler ...runtime.Unmarshaler) (PubSubSpec, error) { - return generics.CastPointerR[PubSubSpec](newGenericPubSubSpec(data, general.Optional(unmarshaler...))) -} - -func newGenericPubSubSpec(data []byte, unmarshaler runtime.Unmarshaler) (*GenericPubSubSpec, error) { - unstr := &runtime.UnstructuredVersionedTypedObject{} - if unmarshaler == nil { - unmarshaler = runtime.DefaultYAMLEncoding - } - err := unmarshaler.Unmarshal(data, unstr) - if err != nil { - return nil, err - } - return &GenericPubSubSpec{UnstructuredVersionedTypedObject: *unstr}, nil -} - -func (s *GenericPubSubSpec) Unwrap(ctx cpi.Context) []PubSubSpec { - eff, err := s.Evaluate(ctx) - if err != nil { - return nil - } - if u, ok := eff.(Unwrapable); ok { - return u.Unwrap(ctx) - } - return nil -} - -func (s *GenericPubSubSpec) Describe(ctx cpi.Context) string { - eff, err := s.Evaluate(ctx) - if err != nil { - return fmt.Sprintf("invalid access specification: %s", err.Error()) - } - return eff.Describe(ctx) -} - -func (s *GenericPubSubSpec) Evaluate(ctx cpi.Context) (PubSubSpec, error) { - s.lock.Lock() - defer s.lock.Unlock() - - if s.cached != nil && s.cachedData != nil { - if d, err := s.GetRaw(); err == nil { - if slices.Equal(d, s.cachedData) { - return s.cached, nil - } - } - s.cached = nil - s.cachedData = nil - } - raw, err := s.GetRaw() - if err != nil { - return nil, err - } - s.cached, err = For(ctx).TypeScheme.Decode(raw, runtime.DefaultJSONEncoding) - if err == nil { - s.cachedData = raw - } - return s.cached, err -} - -func (s *GenericPubSubSpec) PubSubMethod(repository cpi.Repository) (PubSubMethod, error) { - spec, err := s.Evaluate(repository.GetContext()) - if err != nil { - return nil, err - } - if _, ok := spec.(*GenericPubSubSpec); ok { - return nil, errors.ErrUnknown(errkind.KIND_ACCESSMETHOD, s.GetType()) - } - return spec.PubSubMethod(repository) -} diff --git a/pkg/contexts/ocm/pubsub/provider.go b/pkg/contexts/ocm/pubsub/provider.go deleted file mode 100644 index f6b18aedc..000000000 --- a/pkg/contexts/ocm/pubsub/provider.go +++ /dev/null @@ -1,99 +0,0 @@ -package pubsub - -import ( - "sync" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/general" - "golang.org/x/exp/maps" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" -) - -// ProviderRegistry holds handlers able to extract -// a PubSub specification for an OCM repository of a dedicated kind. -type ProviderRegistry interface { - Register(repoKind string, prov Provider) - KnownProviders() map[string]Provider - AddKnownProviders(registry ProviderRegistry) - - For(repo string) Provider -} - -var DefaultRegistry = NewProviderRegistry() - -func RegisterProvider(repokind string, prov Provider) { - DefaultRegistry.Register(repokind, prov) -} - -// A Provider is able to extract a pub sub configuration for -// an ocm repository (typically registered for a dedicated type of repository). -// It does not handle the pub sub system, but just the persistence of -// a pub sub specification configured for a dedicated type of repository. -type Provider interface { - GetPubSubSpec(repo cpi.Repository) (PubSubSpec, error) - SetPubSubSpec(repo cpi.Repository, spec PubSubSpec) error -} - -type NopProvider struct{} - -func (p NopProvider) GetPubSubSpec(repo cpi.Repository) (PubSubSpec, error) { - return nil, nil -} - -func (p NopProvider) SetPubSubSpec(repo cpi.Repository, spec PubSubSpec) error { - return errors.ErrNotSupported("pub/sub configuration") -} - -func NewProviderRegistry(base ...ProviderRegistry) ProviderRegistry { - return &providers{ - base: general.Optional(base...), - providers: map[string]Provider{}, - } -} - -type providers struct { - lock sync.Mutex - - base ProviderRegistry - providers map[string]Provider -} - -func (p *providers) Register(repoKind string, prov Provider) { - p.lock.Lock() - defer p.lock.Unlock() - - p.providers[repoKind] = prov -} - -func (p *providers) For(repo string) Provider { - p.lock.Lock() - defer p.lock.Unlock() - prov := p.providers[repo] - if prov != nil { - return prov - } - if p.base != nil { - return p.base.For(repo) - } - return nil -} - -func (p *providers) KnownProviders() map[string]Provider { - if p.base != nil { - m := p.base.KnownProviders() - for n, e := range p.providers { - if m[n] == nil { - m[n] = e - } - } - return m - } - return maps.Clone(p.providers) -} - -func (p *providers) AddKnownProviders(base ProviderRegistry) { - for n, e := range base.KnownProviders() { - p.providers[n] = e - } -} diff --git a/pkg/contexts/ocm/pubsub/providers/init.go b/pkg/contexts/ocm/pubsub/providers/init.go deleted file mode 100644 index 8a5668e24..000000000 --- a/pkg/contexts/ocm/pubsub/providers/init.go +++ /dev/null @@ -1,5 +0,0 @@ -package providers - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub/providers/ocireg" -) diff --git a/pkg/contexts/ocm/pubsub/providers/ocireg/provider.go b/pkg/contexts/ocm/pubsub/providers/ocireg/provider.go deleted file mode 100644 index aab9f6a8e..000000000 --- a/pkg/contexts/ocm/pubsub/providers/ocireg/provider.go +++ /dev/null @@ -1,180 +0,0 @@ -package ocireg - -import ( - "encoding/json" - "fmt" - "path" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/repocpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub" - compound2 "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub/types/compound" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/genericocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/genericocireg/componentmapping" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ocireg" -) - -const ( - ConfigMimeType = "application/vnd.ocm.software.repository.config.v1+json" - PubSubLayerMimeTye = "application/vnd.ocm.software.repository.config.pubsub.v1+json" -) - -const META = "meta" - -func init() { - pubsub.RegisterProvider(ocireg.Type, &Provider{}) -} - -type Provider struct{} - -var _ pubsub.Provider = (*Provider)(nil) - -func (p *Provider) GetPubSubSpec(repo repocpi.Repository) (pubsub.PubSubSpec, error) { - impl, err := repocpi.GetRepositoryImplementation(repo) - if err != nil { - return nil, err - } - gen, ok := impl.(*genericocireg.RepositoryImpl) - if !ok { - return nil, errors.ErrNotSupported("non-oci based ocm repository") - } - - ocirepo := path.Join(gen.Meta().SubPath, componentmapping.ComponentDescriptorNamespace) - acc, err := gen.OCIRepository().LookupArtifact(ocirepo, META) - if errors.IsErrNotFound(err) || errors.IsErrUnknown(err) { - return nil, nil - } - if err != nil { - return nil, errors.Wrapf(err, "cannot access meta data manifest version") - } - defer acc.Close() - m := acc.ManifestAccess() - if m == nil { - return nil, fmt.Errorf("meta data artifact is no manifest artifact") - } - if m.GetDescriptor().Config.MediaType != ConfigMimeType { - return nil, fmt.Errorf("meta data artifact has unexpected mime type %q", m.GetDescriptor().Config.MediaType) - } - compound, _ := compound2.New() - for _, l := range m.GetDescriptor().Layers { - if l.MediaType == PubSubLayerMimeTye { - var ps pubsub.GenericPubSubSpec - - blob, err := m.GetBlob(l.Digest) - if err != nil { - return nil, err - } - data, err := blob.Get() - blob.Close() - if err != nil { - return nil, err - } - err = json.Unmarshal(data, &ps) - if err != nil { - return nil, err - } - compound.Specifications = append(compound.Specifications, &ps) - } - } - return compound.Effective(), nil -} - -func (p *Provider) SetPubSubSpec(repo cpi.Repository, spec pubsub.PubSubSpec) error { - impl, err := repocpi.GetRepositoryImplementation(repo) - if err != nil { - return err - } - gen, ok := impl.(*genericocireg.RepositoryImpl) - if !ok { - return errors.ErrNotSupported("non-oci based ocm repository") - } - - var data []byte - if spec != nil { - data, err = json.Marshal(spec) - if err != nil { - return err - } - } - - ocirepo := path.Join(gen.Meta().SubPath, componentmapping.ComponentDescriptorNamespace) - ns, err := gen.OCIRepository().LookupNamespace(ocirepo) - if err != nil { - return err - } - defer ns.Close() - - acc, err := ns.GetArtifact(META) - if err != nil { - if errors.IsErrNotFound(err) || errors.IsErrUnknown(err) { - if spec == nil { - return nil - } - } else { - return err - } - } - if acc == nil { - acc, err = ns.NewArtifact() - if err != nil { - return err - } - m, err := acc.Manifest() - if err != nil { - return err - } - config := blobaccess.ForString(ConfigMimeType, "{}") - m.Config.MediaType = config.MimeType() - m.Config.Digest = config.Digest() - err = acc.AddBlob(config) - if err != nil { - return err - } - } - defer acc.Close() - - m := acc.ManifestAccess() - if m == nil { - return fmt.Errorf("meta data artifact is no manifest artifact") - } - if m.GetDescriptor().Config.MediaType != ConfigMimeType { - return fmt.Errorf("meta data artifact has unexpected mime type %q", m.GetDescriptor().Config.MediaType) - } - - blob := blobaccess.ForData(PubSubLayerMimeTye, data) - defer blob.Close() - - layers := m.GetDescriptor().Layers - for i := 0; i < len(layers); i++ { - l := layers[i] - if l.MediaType == PubSubLayerMimeTye { - if data != nil { - m.AddBlob(blob) - l.Digest = blob.Digest() - b, err := ns.AddArtifact(m, META) - if b != nil { - b.Close() - } - return err - } else { - layers = append(layers[:i], layers[i+1:]...) - i-- - } - } - } - m.GetDescriptor().Layers = layers - if data != nil { - _, err = m.AddLayer(blob, nil) - if err != nil { - return err - } - } - b, err := ns.AddArtifact(m, META) - if b != nil { - b.Close() - } - return err -} diff --git a/pkg/contexts/ocm/pubsub/setup.go b/pkg/contexts/ocm/pubsub/setup.go deleted file mode 100644 index db5921335..000000000 --- a/pkg/contexts/ocm/pubsub/setup.go +++ /dev/null @@ -1,34 +0,0 @@ -package pubsub - -import ( - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" -) - -func init() { - datacontext.RegisterSetupHandler(datacontext.SetupHandlerFunction(setupContext)) -} - -func setupContext(mode datacontext.BuilderMode, ctx datacontext.Context) { - if octx, ok := ctx.(cpi.Context); ok { - switch mode { - case datacontext.MODE_SHARED: - fallthrough - case datacontext.MODE_DEFAULTED: - // do nothing, fallback to the default attribute lookup - case datacontext.MODE_EXTENDED: - SetSchemeFor(octx, NewTypeScheme(DefaultTypeScheme)) - SetProvidersFor(octx, NewProviderRegistry(DefaultRegistry)) - case datacontext.MODE_CONFIGURED: - s := NewTypeScheme(nil) - s.AddKnownTypes(DefaultTypeScheme) - SetSchemeFor(octx, s) - r := NewProviderRegistry(nil) - r.AddKnownProviders(DefaultRegistry) - SetProvidersFor(octx, r) - case datacontext.MODE_INITIAL: - SetSchemeFor(octx, NewTypeScheme()) - SetProvidersFor(octx, NewProviderRegistry()) - } - } -} diff --git a/pkg/contexts/ocm/pubsub/types/compound/type.go b/pkg/contexts/ocm/pubsub/types/compound/type.go deleted file mode 100644 index 29f132cd4..000000000 --- a/pkg/contexts/ocm/pubsub/types/compound/type.go +++ /dev/null @@ -1,102 +0,0 @@ -package compound - -import ( - "fmt" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/sliceutils" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - Type = "compound" - TypeV1 = Type + runtime.VersionSeparator + "v1" -) - -func init() { - pubsub.RegisterType(pubsub.NewPubSubType[*Spec](Type, - pubsub.WithDesciption("A pub/sub system forwarding events to described sub-level systems."))) - pubsub.RegisterType(pubsub.NewPubSubType[*Spec](TypeV1, - pubsub.WithFormatSpec(`It is described by the following field: - -- **specifications** *list of pubsub specs* - - A list of nested sub-level specifications the events should be - forwarded to. -`))) -} - -// Spec provides a pub sub adapter registering events at its provider. -type Spec struct { - runtime.ObjectVersionedType - Specifications []*pubsub.GenericPubSubSpec `json:"specifications,omitempty"` -} - -var ( - _ pubsub.PubSubSpec = (*Spec)(nil) - _ pubsub.Unwrapable = (*Spec)(nil) -) - -func New(specs ...pubsub.PubSubSpec) (*Spec, error) { - var gen []*pubsub.GenericPubSubSpec - - for _, s := range specs { - g, err := pubsub.ToGenericPubSubSpec(s) - if err != nil { - return nil, err - } - gen = append(gen, g) - } - return &Spec{runtime.NewVersionedObjectType(Type), gen}, nil -} - -func (s *Spec) PubSubMethod(repo cpi.Repository) (pubsub.PubSubMethod, error) { - var meths []pubsub.PubSubMethod - - for _, e := range s.Specifications { - m, err := e.PubSubMethod(repo) - if err != nil { - return nil, err - } - meths = append(meths, m) - } - return &Method{meths}, nil -} - -func (s *Spec) Unwrap(ctx cpi.Context) []pubsub.PubSubSpec { - return sliceutils.Convert[pubsub.PubSubSpec](s.Specifications) -} - -func (s *Spec) Describe(_ cpi.Context) string { - return fmt.Sprintf("compound pub/sub specification with %d entries", len(s.Specifications)) -} - -func (s *Spec) Effective() pubsub.PubSubSpec { - switch len(s.Specifications) { - case 0: - return nil - case 1: - return s.Specifications[0] - default: - return s - } -} - -// Method finally registers events at contained methods. -type Method struct { - meths []pubsub.PubSubMethod -} - -var _ pubsub.PubSubMethod = (*Method)(nil) - -func (m *Method) NotifyComponentVersion(version common.NameVersion) error { - list := errors.ErrList() - for _, m := range m.meths { - list.Add(m.NotifyComponentVersion(version)) - } - return list.Result() -} diff --git a/pkg/contexts/ocm/pubsub/types/init.go b/pkg/contexts/ocm/pubsub/types/init.go deleted file mode 100644 index f1720a9d5..000000000 --- a/pkg/contexts/ocm/pubsub/types/init.go +++ /dev/null @@ -1,6 +0,0 @@ -package types - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub/types/compound" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub/types/redis" -) diff --git a/pkg/contexts/ocm/pubsub/types/redis/identity/identity.go b/pkg/contexts/ocm/pubsub/types/redis/identity/identity.go deleted file mode 100644 index 82041233c..000000000 --- a/pkg/contexts/ocm/pubsub/types/redis/identity/identity.go +++ /dev/null @@ -1,120 +0,0 @@ -package identity - -import ( - "fmt" - "strconv" - "strings" - - "github.com/mandelsoft/goutils/errors" - - "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/listformat" -) - -const CONSUMER_TYPE = "Github" - -// identity properties. -const ( - ID_HOSTNAME = hostpath.ID_HOSTNAME - ID_PORT = hostpath.ID_PORT - ID_PATHPREFIX = hostpath.ID_PATHPREFIX - ID_CHANNEL = "channel" - ID_DATABASE = "database" -) - -// credential properties. -const ( - ATTR_USERNAME = cpi.ATTR_USERNAME - ATTR_PASSWORD = cpi.ATTR_PASSWORD -) - -func IdentityMatcher(request, cur, id cpi.ConsumerIdentity) bool { - match, better := hostpath.Match(CONSUMER_TYPE, request, cur, id) - if !match { - return false - } - - if request[ID_CHANNEL] != "" { - if id[ID_CHANNEL] != "" && id[ID_CHANNEL] != request[ID_CHANNEL] { - return false - } - } - if request[ID_DATABASE] != "" { - if id[ID_DATABASE] != "" && id[ID_DATABASE] != request[ID_DATABASE] { - return false - } - } - - // ok now it basically matches, check against current match - - if cur[ID_CHANNEL] == "" && request[ID_CHANNEL] != "" { - return true - } - if cur[ID_DATABASE] == "" && request[ID_DATABASE] != "" { - return true - } - return better -} - -func init() { - attrs := listformat.FormatListElements("", listformat.StringElementDescriptionList{ - ATTR_USERNAME, "Redis username", - ATTR_PASSWORD, "Redis password", - }) - cpi.RegisterStandardIdentity(CONSUMER_TYPE, IdentityMatcher, - `Redis PubSub credential matcher - -This matcher is a hostpath matcher with additional attributes: - -- *`+ID_CHANNEL+`* (required if set in pattern): the channel name -- *`+ID_DATABASE+`* the database number -`, - attrs) -} - -func PATCredentials(user, pass string) cpi.Credentials { - return cpi.DirectCredentials{ - ATTR_USERNAME: user, - ATTR_PASSWORD: pass, - } -} - -func GetConsumerId(serveraddr string, channel string, db int) cpi.ConsumerIdentity { - p := "" - host, port, err := ParseAddress(serveraddr) - if err == nil { - host = serveraddr - } - - id := cpi.ConsumerIdentity{ - cpi.ID_TYPE: CONSUMER_TYPE, - ID_HOSTNAME: host, - ID_CHANNEL: channel, - ID_DATABASE: fmt.Sprintf("%d", db), - } - if port != 0 { - id[ID_PORT] = fmt.Sprintf("%d", port) - } - if p != "" { - id[ID_PATHPREFIX] = p - } - return id -} - -func GetCredentials(ctx cpi.ContextProvider, serverurl string, channel string, db int) (cpi.Credentials, error) { - id := GetConsumerId(serverurl, channel, db) - return cpi.CredentialsForConsumer(ctx.CredentialsContext(), id, IdentityMatcher) -} - -func ParseAddress(addr string) (string, int, error) { - idx := strings.Index(addr, ":") - if idx < 0 { - return addr, 6379, nil - } - p, err := strconv.ParseInt(addr[idx+1:], 10, 32) - if err != nil { - return "", 0, errors.Wrapf(err, "invalid port in redis address") - } - return addr[:idx], int(p), nil -} diff --git a/pkg/contexts/ocm/pubsub/types/redis/redis_test.go b/pkg/contexts/ocm/pubsub/types/redis/redis_test.go deleted file mode 100644 index 72e55fff7..000000000 --- a/pkg/contexts/ocm/pubsub/types/redis/redis_test.go +++ /dev/null @@ -1,65 +0,0 @@ -//go:build redis_test - -package redis_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "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/common" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub" - "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub/providers/ocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub/types/redis" - "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub/types/redis/identity" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/composition" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" -) - -const ( - ARCH = "ctf" - COMP = "acme.org/component" - VERS = "v1" -) - -var _ = Describe("Test Environment", func() { - var env *Builder - var repo ocm.Repository - - BeforeEach(func() { - env = NewBuilder() - env.OCMCommonTransport(ARCH, accessio.FormatDirectory) - attr := pubsub.For(env) - attr.ProviderRegistry.Register(ctf.Type, &ocireg.Provider{}) - - env.CredentialsContext().SetCredentialsForConsumer( - identity.GetConsumerId("localhost:6379", "ocm", 0), - credentials.NewCredentials(common.Properties{identity.ATTR_PASSWORD: "redis-test-0815"}), - ) - - repo = Must(ctf.Open(env, ctf.ACC_WRITABLE, ARCH, 0o600, env)) - }) - - AfterEach(func() { - if repo != nil { - MustBeSuccessful(repo.Close()) - } - env.Cleanup() - }) - - Context("local redis server", func() { - It("tests local server", func() { - MustBeSuccessful(pubsub.SetForRepo(repo, Must(redis.New("localhost:6379", "ocm", 0)))) - - cv := composition.NewComponentVersion(env, COMP, VERS) - defer Close(cv) - - Expect(repo.GetSpecification().GetKind()).To(Equal(ctf.Type)) - MustBeSuccessful(repo.AddComponentVersion(cv)) - }) - }) -}) diff --git a/pkg/contexts/ocm/pubsub/types/redis/type.go b/pkg/contexts/ocm/pubsub/types/redis/type.go deleted file mode 100644 index 9809ce077..000000000 --- a/pkg/contexts/ocm/pubsub/types/redis/type.go +++ /dev/null @@ -1,95 +0,0 @@ -package redis - -import ( - "context" - "fmt" - - "github.com/redis/go-redis/v9" - - "github.com/open-component-model/ocm/pkg/common" - credcpi "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub" - "github.com/open-component-model/ocm/pkg/contexts/ocm/pubsub/types/redis/identity" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - Type = "redis" - TypeV1 = Type + runtime.VersionSeparator + "v1" -) - -func init() { - pubsub.RegisterType(pubsub.NewPubSubType[*Spec](Type, - pubsub.WithDesciption("a redis pubsub sytsem."))) - pubsub.RegisterType(pubsub.NewPubSubType[*Spec](TypeV1, - pubsub.WithFormatSpec(`It is describe by the following field: - -- **serverAddr** *Address of redis server* -- **channel** *pubsub channel* -- **database** *database number* - - Publishing using the redis pubsub API. For every change a string message - with the format : is published. If multiple repositories - should be used, each repository should be configured with a different - channel. -`))) -} - -// Spec provides a pub sub adapter registering events at its provider. -type Spec struct { - runtime.ObjectVersionedType - ServerAddr string `json:"serverAddr"` - Channel string `json:"channel"` - Database int `json:"database"` -} - -var _ pubsub.PubSubSpec = (*Spec)(nil) - -func New(serverurl, channel string, db int) (*Spec, error) { - return &Spec{ - runtime.NewVersionedObjectType(Type), - serverurl, channel, db, - }, nil -} - -func (s *Spec) PubSubMethod(repo cpi.Repository) (pubsub.PubSubMethod, error) { - _, _, err := identity.ParseAddress(s.ServerAddr) - if err != nil { - return nil, err - } - - creds, err := identity.GetCredentials(repo.GetContext(), s.ServerAddr, s.Channel, s.Database) - if err != nil { - return nil, err - } - return &Method{s, creds}, nil -} - -func (s *Spec) Describe(_ cpi.Context) string { - return fmt.Sprintf("redis pubsub system %s channel %s, database %d", s.ServerAddr, s.Channel, s.Database) -} - -// Method finally publishes events. -type Method struct { - spec *Spec - creds credcpi.Credentials -} - -var _ pubsub.PubSubMethod = (*Method)(nil) - -func (m *Method) NotifyComponentVersion(version common.NameVersion) error { - // TODO: ipdate to credential provider interface - opts := &redis.Options{ - Addr: m.spec.ServerAddr, - DB: m.spec.Database, - } - if m.creds != nil { - opts.Username = m.creds.GetProperty(identity.ATTR_USERNAME) - opts.Password = m.creds.GetProperty(identity.ATTR_PASSWORD) - } - - rdb := redis.NewClient(opts) - defer rdb.Close() - return rdb.Publish(context.Background(), m.spec.Channel, version.String()).Err() -} diff --git a/pkg/contexts/ocm/pubsub/utils.go b/pkg/contexts/ocm/pubsub/utils.go deleted file mode 100644 index 671b8c906..000000000 --- a/pkg/contexts/ocm/pubsub/utils.go +++ /dev/null @@ -1,68 +0,0 @@ -package pubsub - -import ( - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/maputils" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/listformat" - "github.com/open-component-model/ocm/pkg/runtime" -) - -func SetForRepo(repo cpi.Repository, spec PubSubSpec) error { - prov := For(repo.GetContext()).For(repo.GetSpecification().GetKind()) - if prov != nil { - return prov.SetPubSubSpec(repo, spec) - } - return errors.ErrNotSupported("pub/sub config") -} - -func SpecForRepo(repo cpi.Repository) (PubSubSpec, error) { - prov := For(repo.GetContext()).For(repo.GetSpecification().GetKind()) - if prov != nil { - return prov.GetPubSubSpec(repo) - } - return nil, nil -} - -func SpecForData(ctx cpi.ContextProvider, data []byte) (PubSubSpec, error) { - return For(ctx).TypeScheme.Decode(data, runtime.DefaultYAMLEncoding) -} - -func PubSubForRepo(repo cpi.Repository) (PubSubMethod, error) { - spec, err := SpecForRepo(repo) - if spec == nil || err != nil { - return nil, err - } - return spec.PubSubMethod(repo) -} - -func Notify(repo cpi.Repository, nv common.NameVersion) error { - m, err := PubSubForRepo(repo) - if m == nil || err != nil { - return err - } - return m.NotifyComponentVersion(nv) -} - -func PubSubUsage(scheme TypeScheme, providers ProviderRegistry, cli bool) string { - s := ` -The following list describes the supported publish/subscribe system types, their -specification versions, and formats: -` - if len(scheme.KnownTypes()) == 0 { - s += "There are currently no known pub/sub types!" - } else { - s += scheme.Describe() - } - - list := maputils.OrderedKeys(providers.KnownProviders()) - if len(list) == 0 { - s += "There are currently no persistence providers!" - } else { - s += "There are persistence providers for the following repository types:\n" - s += listformat.FormatList("", list...) - } - return s -} diff --git a/pkg/contexts/ocm/ref.go b/pkg/contexts/ocm/ref.go deleted file mode 100644 index 7d4fdf55d..000000000 --- a/pkg/contexts/ocm/ref.go +++ /dev/null @@ -1,317 +0,0 @@ -package ocm - -import ( - "fmt" - "strings" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/general" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/grammar" -) - -const ( - KIND_OCM_REFERENCE = "ocm reference" -) - -// ParseRepo parses a standard ocm repository reference into a internal representation. -func ParseRepo(ref string) (UniformRepositorySpec, error) { - create := false - if strings.HasPrefix(ref, "+") { - create = true - ref = ref[1:] - } - if strings.HasPrefix(ref, ".") || strings.HasPrefix(ref, "/") { - return cpi.HandleRef(UniformRepositorySpec{ - Info: ref, - CreateIfMissing: create, - }) - } - match := grammar.AnchoredRepositoryRegexp.FindSubmatch([]byte(ref)) - if match != nil { - h := string(match[1]) - t, _ := grammar.SplitTypeSpec(h) - return cpi.HandleRef(UniformRepositorySpec{ - Type: t, - TypeHint: h, - Scheme: string(match[2]), - Host: string(match[3]), - SubPath: string(match[4]), - CreateIfMissing: create, - }) - } - - match = grammar.AnchoredSchemedHostPortRepositoryRegexp.FindSubmatch([]byte(ref)) - if match != nil { - h := string(match[1]) - t, _ := grammar.SplitTypeSpec(h) - return cpi.HandleRef(UniformRepositorySpec{ - Type: t, - TypeHint: h, - Scheme: string(match[2]), - Host: string(match[3]), - SubPath: string(match[4]), - CreateIfMissing: create, - }) - } - - match = grammar.AnchoredHostWithPortRepositoryRegexp.FindSubmatch([]byte(ref)) - if match != nil { - h := string(match[1]) - t, _ := grammar.SplitTypeSpec(h) - return cpi.HandleRef(UniformRepositorySpec{ - Type: t, - TypeHint: h, - Scheme: string(match[2]), - Host: string(match[3]), - SubPath: string(match[4]), - CreateIfMissing: create, - }) - } - - match = grammar.AnchoredGenericRepositoryRegexp.FindSubmatch([]byte(ref)) - if match == nil { - return UniformRepositorySpec{}, errors.ErrInvalid(KIND_OCM_REFERENCE, ref) - } - h := string(match[1]) - t, _ := grammar.SplitTypeSpec(h) - return cpi.HandleRef(UniformRepositorySpec{ - Type: t, - TypeHint: h, - Info: string(match[2]), - CreateIfMissing: create, - }) -} - -func ParseRepoToSpec(ctx Context, ref string, create ...bool) (RepositorySpec, error) { - uni, err := ParseRepo(ref) - if err != nil { - return nil, errors.ErrInvalidWrap(err, KIND_REPOSITORYSPEC, ref) - } - if !uni.CreateIfMissing { - uni.CreateIfMissing = general.Optional(create...) - } - repoSpec, err := ctx.MapUniformRepositorySpec(&uni) - if err != nil { - return nil, errors.ErrInvalidWrap(err, KIND_REPOSITORYSPEC, ref) - } - return repoSpec, nil -} - -// RefSpec is a go internal representation of a oci reference. -type RefSpec struct { - UniformRepositorySpec - CompSpec -} - -// ParseRef parses a standard ocm reference into an internal representation. -func ParseRef(ref string) (RefSpec, error) { - create := false - if strings.HasPrefix(ref, "+") { - create = true - ref = ref[1:] - } - - var spec RefSpec - v := "" - match := grammar.AnchoredReferenceRegexp.FindSubmatch([]byte(ref)) - if match != nil { - v = string(match[6]) - s := string(match[2]) - h := string(match[1]) - t, _ := grammar.SplitTypeSpec(h) - spec = RefSpec{ - UniformRepositorySpec{ - Type: t, - TypeHint: h, - Scheme: s, - Host: string(match[3]), - SubPath: string(match[4]), - CreateIfMissing: create, - }, - CompSpec{ - Component: string(match[5]), - Version: nil, - }, - } - } - - if match == nil { - match = grammar.AnchoredSchemedHostPortReferenceRegexp.FindSubmatch([]byte(ref)) - if match != nil { - v = string(match[6]) - s := string(match[2]) - h := string(match[1]) - t, _ := grammar.SplitTypeSpec(h) - spec = RefSpec{ - UniformRepositorySpec{ - Type: t, - TypeHint: h, - Scheme: s, - Host: string(match[3]), - SubPath: string(match[4]), - CreateIfMissing: create, - }, - CompSpec{ - Component: string(match[5]), - Version: nil, - }, - } - } - } - - if match == nil { - match = grammar.AnchoredHostWithPortReferenceRegexp.FindSubmatch([]byte(ref)) - if match != nil { - v = string(match[6]) - s := string(match[2]) - h := string(match[1]) - t, _ := grammar.SplitTypeSpec(h) - spec = RefSpec{ - UniformRepositorySpec{ - Type: t, - TypeHint: h, - Scheme: s, - Host: string(match[3]), - SubPath: string(match[4]), - CreateIfMissing: create, - }, - CompSpec{ - Component: string(match[5]), - Version: nil, - }, - } - } - } - - if match == nil { - match = grammar.AnchoredGenericReferenceRegexp.FindSubmatch([]byte(ref)) - if match == nil { - return RefSpec{}, errors.ErrInvalid(KIND_OCM_REFERENCE, ref) - } - v = string(match[4]) - h := string(match[1]) - t, _ := grammar.SplitTypeSpec(h) - spec = RefSpec{ - UniformRepositorySpec{ - Type: t, - TypeHint: h, - Info: string(match[2]), - CreateIfMissing: create, - }, - CompSpec{ - Component: string(match[3]), - Version: nil, - }, - } - } - - if v != "" { - spec.Version = &v - } - var err error - if spec.Info == "" || !(string(spec.Info[0]) == "{") { - spec.UniformRepositorySpec, err = cpi.HandleRef(spec.UniformRepositorySpec) - } - return spec, err -} - -func (r *RefSpec) Name() string { - if r.SubPath == "" { - return fmt.Sprintf("%s//%s", r.Host, r.Component) - } - return fmt.Sprintf("%s/%s//%s", r.Host, r.SubPath, r.Component) -} - -func (r *RefSpec) HostPort() (string, string) { - i := strings.Index(r.Host, ":") - if i < 0 { - return r.Host, "" - } - return r.Host[:i], r.Host[i+1:] -} - -func (r *RefSpec) Reference() string { - t := r.Type - if t != "" { - t += "::" - } - s := r.SubPath - if s != "" { - s = "/" + s - } - v := "" - if r.Version != nil && *r.Version != "" { - v = ":" + *r.Version - } - return fmt.Sprintf("%s%s%s//%s%s", t, r.Host, s, r.Component, v) -} - -func (r *RefSpec) IsVersion() bool { - return r.Version != nil -} - -func (r *RefSpec) String() string { - return r.Reference() -} - -func (r RefSpec) DeepCopy() RefSpec { - if r.Version != nil { - v := *r.Version - r.Version = &v - } - return r -} - -//////////////////////////////////////////////////////////////////////////////// - -func ParseComp(ref string) (CompSpec, error) { - match := grammar.AnchoredComponentVersionRegexp.FindSubmatch([]byte(ref)) - - if match == nil { - return CompSpec{}, errors.ErrInvalid(KIND_COMPONENTVERSION, ref) - } - - v := string(match[2]) - r := CompSpec{ - Component: string(match[1]), - Version: nil, - } - if v != "" { - r.Version = &v - } - return r, nil -} - -// CompSpec is a go internal representation of a ocm component version name. -type CompSpec struct { - // Component is the component name part of a component version - Component string - // +optional - Version *string -} - -func (r *CompSpec) IsVersion() bool { - return r.Version != nil -} - -func (r *CompSpec) NameVersion() common.NameVersion { - if r.Version != nil { - return common.NewNameVersion(r.Component, *r.Version) - } - return common.NewNameVersion(r.Component, "-") -} - -func (r *CompSpec) Reference() string { - v := "" - if r.Version != nil && *r.Version != "" { - v = ":" + *r.Version - } - return fmt.Sprintf("%s%s", r.Component, v) -} - -func (r *CompSpec) String() string { - return r.Reference() -} diff --git a/pkg/contexts/ocm/ref_test.go b/pkg/contexts/ocm/ref_test.go deleted file mode 100644 index 27a07f699..000000000 --- a/pkg/contexts/ocm/ref_test.go +++ /dev/null @@ -1,344 +0,0 @@ -package ocm_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "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/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/utils" -) - -func Type(t string) string { - if t == "" { - return t - } - return t + "::" -} - -func FileFormat(t, f string) string { - if t == "" { - return f - } - if f == "" { - return t - } - return t + "+" + f -} - -func FileType(t, f string) string { - if t != "" { - return t - } else { - return f - } -} - -func Scheme(s string) string { - if s == "" { - return s - } - return s + "://" -} - -func Sub(t string) string { - if t == "" { - return t - } - return "/" + t -} - -func Vers(t string) string { - if t == "" { - return t - } - return ":" + t -} - -func CheckRef(ref, ut, scheme, h, us, c, uv, i string, th ...string) { - var v *string - if uv != "" { - v = &uv - } - if len(th) == 0 && ut != "" { - th = []string{ut} - } - spec, err := ocm.ParseRef(ref) - Expect(err).WithOffset(1).To(Succeed()) - Expect(spec).WithOffset(1).To(Equal(ocm.RefSpec{ - UniformRepositorySpec: ocm.UniformRepositorySpec{ - Type: ut, - Scheme: scheme, - Host: h, - SubPath: us, - Info: i, - TypeHint: utils.Optional(th...), - CreateIfMissing: ref[0] == '+', - }, - CompSpec: ocm.CompSpec{ - Component: c, - Version: v, - }, - })) -} - -var _ = Describe("ref parsing", func() { - Context("file path refs", func() { - t := "ctf" - p := "file/path" - c := "github.com/mandelsoft/ocm" - v := "v1" - - Context("[+][::][./][//[:]", func() { - for _, cm := range []string{"", "+"} { - for _, ut := range []string{"", t} { - for _, uf := range []string{"", "directory", "tar", "tgz"} { - for _, up := range []string{p, "./" + p} { - for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + "+65", v + ".1.2-rc.1", v + ".1.2+65"} { - ref := cm + Type(FileFormat(ut, uf)) + up + "//" + c + Vers(uv) - ut, uf, uv, up := ut, uf, uv, up - - // tests parsing of all permutations of - // [+][::][./]//[:] - It("parses ref "+ref, func() { - if ut != "" || uf != "" { - CheckRef(ref, FileType(ut, uf), "", "", "", c, uv, up, FileFormat(ut, uf)) - } else { - CheckRef(ref, FileType(ut, uf), "", "", "", c, uv, up) - } - }) - } - } - } - } - } - }) - }) - - Context("json repo spec refs", func() { - t := ocireg.Type - s := "mandelsoft/cnudie" - v := "v1" - - h := "ghcr.io" - c := "github.com/mandelsoft/ocm" - - repospec := ocireg.NewRepositorySpec(h, &ocireg.ComponentRepositoryMeta{ - ComponentNameMapping: "", - SubPath: s, - }) - jsonrepospec := string(Must(repospec.MarshalJSON())) - - Context("[::][//][:]", func() { - for _, cm := range []string{"", "+"} { - for _, ut := range []string{t, ""} { - for _, uv := range []string{"", v, v + ".1.1", v + "-rc.1", v + "+65", v + ".1.2-rc.1", v + ".1.2+65"} { - ref := cm + Type(ut) + jsonrepospec + "//" + c + Vers(uv) - ut, uv := ut, uv - - // tests parsing of all permutations of - // [::][//][:] - It("parses ref "+ref, func() { - CheckRef(ref, ut, "", "", "", c, uv, jsonrepospec) - }) - } - } - } - }) - - It("fail if mismatch between type in ref (here, ctf) and type in json repo spec (here, OCIRegistry)", func() { - ctx := ocm.New() - - ref := Must(ocm.ParseRef("ctf::{\"baseUrl\":\"ghcr.io\",\"subPath\":\"mandelsoft/cnudie\",\"type\":\"OCIRegistry\"}//github.com/mandelsoft/ocm:v1")) - spec, err := ctx.MapUniformRepositorySpec(&ref.UniformRepositorySpec) - Expect(spec).To(BeNil()) - Expect(err).ToNot(BeNil()) - }) - }) - - Context("domain refs", func() { - t := ocireg.Type - s := "mandelsoft/cnudie" - v := "v1" - - h := "ghcr.io" - c := "github.com/mandelsoft/ocm" - - Context("[+][::][://][:][/]//[:::][scheme://][:][/]//[:::]://[:][/]//[:::]scheme://[:][/]//[:::][://]:[/]//[:::][scheme://]:[/]//[:= 0 { - next += idx + len(META_SEPARATOR) - } else { - break - } - } - if next == 0 { - return t - } - return t[:next-len(META_SEPARATOR)] + "+" + t[next:] -} - -func (c *componentAccessImpl) IsReadOnly() bool { - return c.repo.IsReadOnly() -} - -func (c *componentAccessImpl) ListVersions() ([]string, error) { - tags, err := c.namespace.ListTags() - if err != nil { - return nil, err - } - result := make([]string, 0, len(tags)) - for _, t := range tags { - // omit reported digests (typically for ctf) - if ok, _ := artdesc.IsDigest(t); !ok { - result = append(result, toVersion(t)) - } - } - return result, err -} - -func (c *componentAccessImpl) HasVersion(vers string) (bool, error) { - tags, err := c.namespace.ListTags() - if err != nil { - return false, err - } - for _, t := range tags { - // omit reported digests (typically for ctf) - if ok, _ := artdesc.IsDigest(t); !ok { - if vers == t { - return true, nil - } - } - } - return false, err -} - -func (c *componentAccessImpl) LookupVersion(version string) (*repocpi.ComponentVersionAccessInfo, error) { - tag, err := toTag(version) - if err != nil { - return nil, err - } - acc, err := c.namespace.GetArtifact(tag) - if err != nil { - if errors.IsErrNotFound(err) { - return nil, cpi.ErrComponentVersionNotFoundWrap(err, c.name, version) - } - return nil, err - } - m := accessobj.ACC_WRITABLE - if c.IsReadOnly() { - m = accessobj.ACC_READONLY - } - return newComponentVersionAccess(m, c, version, acc, true) -} - -func (c *componentAccessImpl) NewVersion(version string, overrides ...bool) (*repocpi.ComponentVersionAccessInfo, error) { - if c.IsReadOnly() { - return nil, accessio.ErrReadOnly - } - override := general.Optional(overrides...) - tag, err := toTag(version) - if err != nil { - return nil, err - } - acc, err := c.namespace.GetArtifact(tag) - if err == nil { - if override { - return newComponentVersionAccess(accessobj.ACC_CREATE, c, version, acc, false) - } - return nil, errors.ErrAlreadyExists(cpi.KIND_COMPONENTVERSION, c.name+"/"+version) - } - if !errors.IsErrNotFoundKind(err, oci.KIND_OCIARTIFACT) { - return nil, err - } - acc, err = c.namespace.NewArtifact() - if err != nil { - return nil, err - } - return newComponentVersionAccess(accessobj.ACC_CREATE, c, version, acc, false) -} diff --git a/pkg/contexts/ocm/repositories/genericocireg/componentversion.go b/pkg/contexts/ocm/repositories/genericocireg/componentversion.go deleted file mode 100644 index 2531589ed..000000000 --- a/pkg/contexts/ocm/repositories/genericocireg/componentversion.go +++ /dev/null @@ -1,316 +0,0 @@ -package genericocireg - -import ( - "fmt" - "path" - "strings" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/set" - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/common" - "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/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localociblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/relativeociref" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/compatattr" - ocihdlr "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/oci" - "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/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/repocpi" - "github.com/open-component-model/ocm/pkg/errkind" - "github.com/open-component-model/ocm/pkg/refmgmt" -) - -// newComponentVersionAccess creates a component access for the artifact access, if this fails the artifact acess is closed. -func newComponentVersionAccess(mode accessobj.AccessMode, comp *componentAccessImpl, version string, access oci.ArtifactAccess, persistent bool) (*repocpi.ComponentVersionAccessInfo, error) { - c, err := newComponentVersionContainer(mode, comp, version, access) - if err != nil { - return nil, err - } - return &repocpi.ComponentVersionAccessInfo{c, true, persistent}, nil -} - -// ////////////////////////////////////////////////////////////////////////////// - -type ComponentVersionContainer struct { - bridge repocpi.ComponentVersionAccessBridge - - comp *componentAccessImpl - version string - access oci.ArtifactAccess - manifest oci.ManifestAccess - state accessobj.State -} - -var _ repocpi.ComponentVersionAccessImpl = (*ComponentVersionContainer)(nil) - -func newComponentVersionContainer(mode accessobj.AccessMode, comp *componentAccessImpl, version string, access oci.ArtifactAccess) (*ComponentVersionContainer, error) { - m := access.ManifestAccess() - if m == nil { - return nil, errors.ErrInvalid("artifact type") - } - state, err := NewState(mode, comp.name, version, m, compatattr.Get(comp.GetContext())) - if err != nil { - access.Close() - return nil, err - } - - return &ComponentVersionContainer{ - comp: comp, - version: version, - access: access, - manifest: m, - state: state, - }, nil -} - -func (c *ComponentVersionContainer) SetBridge(impl repocpi.ComponentVersionAccessBridge) { - c.bridge = impl -} - -func (c *ComponentVersionContainer) GetParentBridge() repocpi.ComponentAccessBridge { - return c.comp.bridge -} - -func (c *ComponentVersionContainer) Close() error { - if c.manifest == nil { - return accessio.ErrClosed - } - c.manifest = nil - return c.access.Close() -} - -func (c *ComponentVersionContainer) SetReadOnly() { - c.state.SetReadOnly() -} - -func (c *ComponentVersionContainer) Check() error { - if c.version != c.GetDescriptor().Version { - return errors.ErrInvalid("component version", c.GetDescriptor().Version) - } - if c.comp.name != c.GetDescriptor().Name { - return errors.ErrInvalid("component name", c.GetDescriptor().Name) - } - return nil -} - -func (c *ComponentVersionContainer) Repository() cpi.Repository { - return c.comp.repo.nonref -} - -func (c *ComponentVersionContainer) GetContext() cpi.Context { - return c.comp.GetContext() -} - -func (c *ComponentVersionContainer) IsReadOnly() bool { - return c.state.IsReadOnly() -} - -func (c *ComponentVersionContainer) IsClosed() bool { - return c.manifest == nil -} - -func (c *ComponentVersionContainer) AccessMethod(a cpi.AccessSpec, cv refmgmt.ExtendedAllocatable) (cpi.AccessMethod, error) { - accessSpec, err := c.comp.GetContext().AccessSpecForSpec(a) - if err != nil { - return nil, err - } - - switch a.GetKind() { - case localblob.Type: - return newLocalBlobAccessMethod(accessSpec.(*localblob.AccessSpec), c.comp.namespace, c.access, cv) - case localociblob.Type: - return newLocalOCIBlobAccessMethod(accessSpec.(*localblob.AccessSpec), c.comp.namespace, c.access, cv) - case relativeociref.Type: - m, err := ociartifact.NewMethod(c.GetContext(), a, accessSpec.(*relativeociref.AccessSpec).Reference, c.comp.repo.ocirepo) - if err == nil { - impl := accspeccpi.GetAccessMethodImplementation(m).(ociartifact.AccessMethodImpl) - cv.BeforeCleanup(refmgmt.CleanupHandlerFunc(impl.Cache)) - } - return m, err - } - - return nil, errors.ErrNotSupported(errkind.KIND_ACCESSMETHOD, a.GetType(), "oci registry") -} - -func (c *ComponentVersionContainer) SetDescriptor(cd *compdesc.ComponentDescriptor) error { - cur := c.GetDescriptor() - *cur = *cd - return c.Update() -} - -func (c *ComponentVersionContainer) Update() error { - logger := Logger(c.GetContext()).WithValues("cv", common.NewNameVersion(c.comp.name, c.version)) - err := c.Check() - if err != nil { - return fmt.Errorf("check failed: %w", err) - } - - if c.state.HasChanged() { - logger.Debug("update component version") - desc := c.GetDescriptor() - layers := set.Set[int]{} - for i := range c.manifest.GetDescriptor().Layers { - layers.Add(i) - } - for i, r := range desc.Resources { - s, l, err := c.evalLayer(r.Access) - if err != nil { - return fmt.Errorf("failed resource layer evaluation: %w", err) - } - if l > 0 { - layers.Delete(l) - } - if s != r.Access { - desc.Resources[i].Access = s - } - } - for i, r := range desc.Sources { - s, l, err := c.evalLayer(r.Access) - if err != nil { - return fmt.Errorf("failed source layer evaluation: %w", err) - } - if l > 0 { - layers.Delete(l) - } - if s != r.Access { - desc.Sources[i].Access = s - } - } - m := c.manifest.GetDescriptor() - i := len(m.Layers) - 1 - - for i > 0 { - if layers.Contains(i) { - logger.Debug("removing unused layer", "layer", i) - m.Layers = append(m.Layers[:i], m.Layers[i+1:]...) - } - i-- - } - if _, err := c.state.Update(); err != nil { - return fmt.Errorf("failed to update state: %w", err) - } - - logger.Debug("add oci artifact") - tag, err := toTag(c.version) - if err != nil { - return err - } - if _, err := c.comp.namespace.AddArtifact(c.manifest, tag); err != nil { - return fmt.Errorf("unable to add artifact: %w", err) - } - } - - return nil -} - -func (c *ComponentVersionContainer) evalLayer(s compdesc.AccessSpec) (compdesc.AccessSpec, int, error) { - var d *artdesc.Descriptor - - spec, err := c.GetContext().AccessSpecForSpec(s) - if err != nil { - return s, 0, err - } - if a, ok := spec.(*localblob.AccessSpec); ok { - if ok, _ := artdesc.IsDigest(a.LocalReference); !ok { - return s, 0, errors.ErrInvalid("digest", a.LocalReference) - } - d = &artdesc.Descriptor{Digest: digest.Digest(a.LocalReference), MediaType: a.GetMimeType()} - } - if d != nil { - // find layer - layers := c.manifest.GetDescriptor().Layers - max := len(layers) - 1 - for i := range layers { - l := layers[len(layers)-1-i] - if i < max && l.Digest == d.Digest && (d.Digest == "" || d.Digest == l.Digest) { - return s, len(layers) - 1 - i, nil - } - } - return s, 0, fmt.Errorf("resource access %s: no layer found for local blob %s[%s]", spec.Describe(c.GetContext()), d.Digest, d.MediaType) - } - return s, 0, nil -} - -func (c *ComponentVersionContainer) GetDescriptor() *compdesc.ComponentDescriptor { - return c.state.GetState().(*compdesc.ComponentDescriptor) -} - -func (c *ComponentVersionContainer) GetBlob(name string) (cpi.DataAccess, error) { - return c.manifest.GetBlob(digest.Digest(name)) -} - -func (c *ComponentVersionContainer) GetStorageContext() cpi.StorageContext { - return ocihdlr.New(c.comp.GetName(), c.Repository(), c.comp.repo.ocirepo.GetSpecification().GetKind(), c.comp.repo.ocirepo, c.comp.namespace, c.manifest) -} - -func (c *ComponentVersionContainer) AddBlob(blob cpi.BlobAccess, refName string, global cpi.AccessSpec) (cpi.AccessSpec, error) { - if blob == nil { - return nil, errors.New("a resource has to be defined") - } - - err := c.manifest.AddBlob(blob) - if err != nil { - return nil, err - } - err = ocihdlr.AssureLayer(c.manifest.GetDescriptor(), blob) - if err != nil { - return nil, err - } - return localblob.New(blob.Digest().String(), refName, blob.MimeType(), global), nil -} - -// AssureGlobalRef provides a global manifest for a local OCI Artifact. -func (c *ComponentVersionContainer) AssureGlobalRef(d digest.Digest, url, name string) (cpi.AccessSpec, error) { - blob, err := c.manifest.GetBlob(d) - if err != nil { - return nil, err - } - var namespace oci.NamespaceAccess - var version string - var tag string - if name == "" { - namespace = c.comp.namespace - } else { - i := strings.LastIndex(name, ":") - if i > 0 { - version = name[i+1:] - name = name[:i] - tag = version - } - namespace, err = c.comp.repo.ocirepo.LookupNamespace(name) - if err != nil { - return nil, err - } - } - set, err := artifactset.OpenFromBlob(accessobj.ACC_READONLY, blob) - if err != nil { - return nil, err - } - defer set.Close() - digest := set.GetMain() - if version == "" { - version = digest.String() - } - art, err := set.GetArtifact(digest.String()) - if err != nil { - return nil, err - } - err = artifactset.TransferArtifact(art, namespace, oci.AsTags(tag)...) - if err != nil { - return nil, err - } - - ref := path.Join(url+namespace.GetNamespace()) + ":" + version - - global := ociartifact.New(ref) - return global, nil -} diff --git a/pkg/contexts/ocm/repositories/genericocireg/cred_test.go b/pkg/contexts/ocm/repositories/genericocireg/cred_test.go deleted file mode 100644 index 1969ab443..000000000 --- a/pkg/contexts/ocm/repositories/genericocireg/cred_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package genericocireg_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/mandelsoft/goutils/finalizer" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - ociidentity "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ocireg" -) - -var _ = Describe("consumer id handling", func() { - It("creates a dummy component", func() { - var finalize finalizer.Finalizer - defer Defer(finalize.Finalize) - - ctx := ocm.New(datacontext.MODE_EXTENDED) - credctx := ctx.CredentialsContext() - - creds := ociidentity.SimpleCredentials("test", "password") - spec := ocireg.NewRepositorySpec("ghcr.io/open-component-model/test") - - id := credentials.GetProvidedConsumerId(spec, credentials.StringUsageContext(COMPONENT)) - Expect(id).To(Equal(credentials.NewConsumerIdentity(ociidentity.CONSUMER_TYPE, ociidentity.ID_HOSTNAME, "ghcr.io", ociidentity.ID_PATHPREFIX, "open-component-model/test/component-descriptors/"+COMPONENT))) - - id = credentials.GetProvidedConsumerId(spec) - Expect(id).To(Equal(credentials.NewConsumerIdentity(ociidentity.CONSUMER_TYPE, ociidentity.ID_HOSTNAME, "ghcr.io", ociidentity.ID_PATHPREFIX, "open-component-model/test"))) - - credctx.SetCredentialsForConsumer(id, creds) - - repo := finalizer.ClosingWith(&finalize, Must(DefaultContext.RepositoryForSpec(spec))) - - id = credentials.GetProvidedConsumerId(repo, credentials.StringUsageContext(COMPONENT)) - Expect(id).To(Equal(credentials.NewConsumerIdentity(ociidentity.CONSUMER_TYPE, ociidentity.ID_HOSTNAME, "ghcr.io", ociidentity.ID_PATHPREFIX, "open-component-model/test/component-descriptors/"+COMPONENT))) - - creds = Must(credentials.CredentialsForConsumer(credctx, id)) - - Expect(creds.Properties()).To(Equal(common.Properties{ - ociidentity.ATTR_USERNAME: "test", - ociidentity.ATTR_PASSWORD: "password", - })) - }) -}) diff --git a/pkg/contexts/ocm/repositories/genericocireg/excludes.go b/pkg/contexts/ocm/repositories/genericocireg/excludes.go deleted file mode 100644 index 8ce480a1d..000000000 --- a/pkg/contexts/ocm/repositories/genericocireg/excludes.go +++ /dev/null @@ -1,13 +0,0 @@ -package genericocireg - -import ( - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/docker" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/empty" -) - -var Excludes = []string{ - docker.Type, - artifactset.Type, - empty.Type, -} diff --git a/pkg/contexts/ocm/repositories/genericocireg/info.go b/pkg/contexts/ocm/repositories/genericocireg/info.go deleted file mode 100644 index d36361d91..000000000 --- a/pkg/contexts/ocm/repositories/genericocireg/info.go +++ /dev/null @@ -1,71 +0,0 @@ -package genericocireg - -import ( - "encoding/json" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/contexts/oci/ociutils" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/genericocireg/componentmapping" - "github.com/open-component-model/ocm/pkg/runtime" -) - -func init() { - ociutils.RegisterInfoHandler(componentmapping.ComponentDescriptorConfigMimeType, &handler{}) -} - -type handler struct{} - -type ComponentVersionInfo struct { - Error string `json:"error,omitempty"` - Description string `json:"description"` - Unparsed string `json:"unparsed,omitempty"` - Descriptor json.RawMessage `json:"descriptor,omitempty"` -} - -func (h handler) Info(m cpi.ManifestAccess, config []byte) interface{} { - info := &ComponentVersionInfo{ - Description: "component version", - } - acc := NewStateAccess(m) - data, err := blobaccess.BlobData(acc.Get()) - if err != nil { - info.Error = "cannot read component descriptor: " + err.Error() - return info - } - var raw interface{} - err = json.Unmarshal(data, &raw) - if err != nil { - info.Unparsed = string(data) - return info - } - info.Descriptor = data - return info -} - -func (h handler) Description(pr common.Printer, m cpi.ManifestAccess, config []byte) { - pr.Printf("component version:\n") - acc := NewStateAccess(m) - data, err := blobaccess.BlobData(acc.Get()) - if err != nil { - pr.Printf(" cannot read component descriptor: %s\n", err.Error()) - return - } - pr.Printf(" descriptor:\n") - var raw interface{} - err = runtime.DefaultYAMLEncoding.Unmarshal(data, &raw) - if err != nil { - pr.Printf(" data: %s\n", string(data)) - pr.Printf(" cannot get unmarshal component descriptor: %s\n", err.Error()) - return - } - - form, err := json.MarshalIndent(raw, " ", " ") - if err != nil { - pr.Printf(" data: %s\n", string(data)) - pr.Printf(" cannot get marshal component descriptor: %s\n", err.Error()) - return - } - pr.Printf("%s\n", string(form)) -} diff --git a/pkg/contexts/ocm/repositories/genericocireg/logging.go b/pkg/contexts/ocm/repositories/genericocireg/logging.go deleted file mode 100644 index 21743870a..000000000 --- a/pkg/contexts/ocm/repositories/genericocireg/logging.go +++ /dev/null @@ -1,15 +0,0 @@ -package genericocireg - -import ( - "github.com/mandelsoft/logging" - - ocmlog "github.com/open-component-model/ocm/pkg/logging" -) - -var REALM = ocmlog.DefineSubRealm("OCM to OCI Registry Mapping", "oci", "mapping") - -var TAG_CDDIFF = logging.DefineTag("cd-diff", "component descriptor modification") - -func Logger(ctx logging.ContextProvider, messageContext ...logging.MessageContext) logging.Logger { - return ctx.LoggingContext().Logger(append([]logging.MessageContext{REALM}, messageContext...)) -} diff --git a/pkg/contexts/ocm/repositories/genericocireg/repo_test.go b/pkg/contexts/ocm/repositories/genericocireg/repo_test.go deleted file mode 100644 index a71c45ae2..000000000 --- a/pkg/contexts/ocm/repositories/genericocireg/repo_test.go +++ /dev/null @@ -1,414 +0,0 @@ -package genericocireg_test - -import ( - "fmt" - "path" - "reflect" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/mandelsoft/goutils/finalizer" - "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/common/accessio" - "github.com/open-component-model/ocm/pkg/common/accessobj" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - ocicpi "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf/testhelper" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localociblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/compatattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/keepblobattr" - storagecontext "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/oci" - handler "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/oci/ocirepo" - "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/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/repocpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/digester/digesters/artifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/genericocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/genericocireg/componentmapping" - ocmreg "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - ocmtesthelper "github.com/open-component-model/ocm/pkg/contexts/ocm/testhelper" - ocmutils "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/signing/hasher/sha256" -) - -var DefaultContext = ocm.New() - -const ( - COMPONENT = "github.com/mandelsoft/ocm" - TESTBASE = "testbase.de" -) - -var _ = Describe("component repository mapping", func() { - var tempfs vfs.FileSystem - - var ocispec oci.RepositorySpec - var spec *genericocireg.RepositorySpec - - BeforeEach(func() { - t, err := osfs.NewTempFileSystem() - Expect(err).To(Succeed()) - tempfs = t - - // ocmlog.Context().AddRule(logging.NewConditionRule(logging.TraceLevel, accessio.ALLOC_REALM)) - - ocispec, err = ctf.NewRepositorySpec(accessobj.ACC_CREATE, "test", accessio.PathFileSystem(tempfs), accessobj.FormatDirectory) - Expect(err).To(Succeed()) - spec = genericocireg.NewRepositorySpec(ocispec, nil) - }) - - AfterEach(func() { - vfs.Cleanup(tempfs) - }) - - It("Don't Panik! When it's not a semver.org conform version. #756", func() { - repo := Must(DefaultContext.RepositoryForSpec(spec)) - comp := Must(repo.LookupComponent(COMPONENT)) - cva, err := comp.NewVersion("v1.two.zeo-2") - Expect(err).To(HaveOccurred()) - Expect(cva).To(BeNil()) - Expect(err.Error()).To(Equal("Invalid Semantic Version")) - }) - - It("creates a dummy component", func() { - var finalize finalizer.Finalizer - defer Defer(finalize.Finalize) - - repo := finalizer.ClosingWith(&finalize, Must(DefaultContext.RepositoryForSpec(spec))) - impl := Must(repocpi.GetRepositoryImplementation(repo)) - Expect(reflect.TypeOf(impl).String()).To(Equal("*genericocireg.RepositoryImpl")) - - comp := finalizer.ClosingWith(&finalize, Must(repo.LookupComponent(COMPONENT))) - vers := finalizer.ClosingWith(&finalize, Must(comp.NewVersion("v1"))) - MustBeSuccessful(comp.AddVersion(vers)) - - noref := vers.Repository() - Expect(noref).NotTo(BeNil()) - Expect(noref.IsClosed()).To(BeFalse()) - Expect(noref.Close()).To(Succeed()) - Expect(noref.IsClosed()).To(BeFalse()) - - MustBeSuccessful(finalize.Finalize()) - - Expect(noref.IsClosed()).To(BeTrue()) - Expect(noref.Close()).To(MatchError("closed")) - ExpectError(noref.LookupComponent("dummy")).To(MatchError("closed")) - - // access it again - repo = finalizer.ClosingWith(&finalize, Must(DefaultContext.RepositoryForSpec(spec))) - - ok := Must(repo.ExistsComponentVersion(COMPONENT, "v1")) - Expect(ok).To(BeTrue()) - - comp = finalizer.ClosingWith(&finalize, Must(repo.LookupComponent(COMPONENT))) - vers = finalizer.ClosingWith(&finalize, Must(comp.LookupVersion("v1"))) - Expect(vers.GetDescriptor()).To(Equal(compdesc.New(COMPONENT, "v1"))) - - MustBeSuccessful(finalize.Finalize()) - }) - - It("handles legacylocalociblob access method", func() { - var finalize finalizer.Finalizer - defer Defer(finalize.Finalize) - - blob := blobaccess.ForString(mime.MIME_OCTET, "anydata") - - // create repository - repo := finalizer.ClosingWith(&finalize, Must(DefaultContext.RepositoryForSpec(spec))) - impl := Must(repocpi.GetRepositoryImplementation(repo)) - Expect(reflect.TypeOf(impl).String()).To(Equal("*genericocireg.RepositoryImpl")) - - comp := finalizer.ClosingWith(&finalize, Must(repo.LookupComponent(COMPONENT))) - vers := finalizer.ClosingWith(&finalize, Must(comp.NewVersion("v1"))) - acc := Must(vers.AddBlob(blob, "", "", nil)) - - MustBeSuccessful(vers.SetResource(compdesc.NewResourceMeta("blob", resourcetypes.PLAIN_TEXT, metav1.LocalRelation), acc)) - MustBeSuccessful(comp.AddVersion(vers)) - - rs := Must(vers.GetResourceByIndex(0)) - acc = Must(rs.Access()) - - // check provided actual access to be local blob - Expect(acc.GetKind()).To(Equal(localblob.Type)) - l, ok := acc.(*localblob.AccessSpec) - Expect(ok).To(BeTrue()) - Expect(l.LocalReference).To(Equal(blob.Digest().String())) - Expect(l.GlobalAccess).To(BeNil()) - - acc = localociblob.New(digest.Digest(l.LocalReference)) - - MustBeSuccessful(vers.SetResource(compdesc.NewResourceMeta("blob", resourcetypes.PLAIN_TEXT, metav1.LocalRelation), acc)) - MustBeSuccessful(comp.AddVersion(vers)) - - rs = Must(vers.GetResourceByIndex(0)) - spec := Must(rs.Access()) - - Expect(spec.GetType()).To(Equal(localociblob.Type)) - - m := Must(rs.AccessMethod()) - finalize.Close(m) - Expect(m.MimeType()).To(Equal("application/octet-stream")) - data := Must(m.Get()) - Expect(string(data)).To(Equal("anydata")) - }) - - It("imports blobs", func() { - var finalize finalizer.Finalizer - defer Defer(finalize.Finalize) - - base := func(ctx *storagecontext.StorageContext) string { - return TESTBASE - } - ctx := ocm.WithBlobHandlers(ocm.DefaultBlobHandlers().Copy().Register(handler.NewBlobHandler(base))).New() - blob := blobaccess.ForString(mime.MIME_OCTET, ocmtesthelper.S_TESTDATA) - - // create repository - repo := finalizer.ClosingWith(&finalize, Must(ctx.RepositoryForSpec(spec))) - impl := Must(repocpi.GetRepositoryImplementation(repo)) - Expect(reflect.TypeOf(impl).String()).To(Equal("*genericocireg.RepositoryImpl")) - - comp := finalizer.ClosingWith(&finalize, Must(repo.LookupComponent(COMPONENT))) - vers := finalizer.ClosingWith(&finalize, Must(comp.NewVersion("v1"))) - acc := Must(vers.AddBlob(blob, "", "", nil)) - MustBeSuccessful(vers.SetResource(compdesc.NewResourceMeta("blob", resourcetypes.PLAIN_TEXT, metav1.LocalRelation), acc)) - MustBeSuccessful(comp.AddVersion(vers)) - - res := Must(vers.GetResourceByIndex(0)) - acc = Must(res.Access()) - - // check provided actual access to be local blob - Expect(acc.GetKind()).To(Equal(localblob.Type)) - l, ok := acc.(*localblob.AccessSpec) - Expect(ok).To(BeTrue()) - Expect(l.LocalReference).To(Equal(blob.Digest().String())) - Expect(l.GlobalAccess).NotTo(BeNil()) - - // check provided global access to be oci blob - g := Must(l.GlobalAccess.Evaluate(DefaultContext)) - o, ok := g.(*ociblob.AccessSpec) - Expect(ok).To(BeTrue()) - Expect(o.Digest).To(Equal(blob.Digest())) - Expect(o.Reference).To(Equal(TESTBASE + "/" + componentmapping.ComponentDescriptorNamespace + "/" + COMPONENT)) - - Expect(res.Meta().Digest).NotTo(BeNil()) - Expect(res.Meta().Digest.Value).To(Equal(ocmtesthelper.D_TESTDATA)) - }) - - It("imports artifact", func() { - var finalize finalizer.Finalizer - defer Defer(finalize.Finalize) - - mime := artdesc.ToContentMediaType(artdesc.MediaTypeImageManifest) + "+tar+gzip" - base := func(ctx *storagecontext.StorageContext) string { - return TESTBASE - } - ctx := ocm.WithBlobHandlers(ocm.DefaultBlobHandlers().Copy().Register(handler.NewArtifactHandler(base), cpi.ForMimeType(mime))).New() - keepblobattr.Set(ctx, true) - - // create artifactset - opts := Must(accessio.AccessOptions(nil, accessio.PathFileSystem(tempfs))) - r := Must(artifactset.FormatTGZ.Create("test.tgz", opts, 0o700)) - testhelper.DefaultManifestFill(r) - r.Annotate(artifactset.MAINARTIFACT_ANNOTATION, "sha256:"+testhelper.DIGEST_MANIFEST) - Expect(r.Close()).To(Succeed()) - - // create repository - repo := finalizer.ClosingWith(&finalize, Must(ctx.RepositoryForSpec(spec))) - impl := Must(repocpi.GetRepositoryImplementation(repo)) - Expect(reflect.TypeOf(impl).String()).To(Equal("*genericocireg.RepositoryImpl")) - ocirepo := genericocireg.GetOCIRepository(repo) - Expect(ocirepo).NotTo(BeNil()) - - nested := finalize.Nested() - comp := finalizer.ClosingWith(nested, Must(repo.LookupComponent(COMPONENT))) - vers := finalizer.ClosingWith(nested, Must(comp.NewVersion("v1"))) - blob := blobaccess.ForFile(mime, "test.tgz", tempfs) - - fmt.Printf("physical digest: %s\n", blob.Digest()) - acc := Must(vers.AddBlob(blob, "", "artifact1", nil)) - MustBeSuccessful(vers.SetResource(cpi.NewResourceMeta("image", resourcetypes.OCI_IMAGE, metav1.LocalRelation), acc)) - MustBeSuccessful(comp.AddVersion(vers)) - - res := Must(vers.GetResourceByIndex(0)) - acc = Must(res.Access()) - Expect(acc.GetKind()).To(Equal(localblob.Type)) - rd := res.Meta().Digest - Expect(rd).NotTo(BeNil()) - Expect(rd.Value).To(Equal(testhelper.DIGEST_MANIFEST)) - Expect(rd.NormalisationAlgorithm).To(Equal(artifact.OciArtifactDigestV1)) - Expect(rd.HashAlgorithm).To(Equal(sha256.Algorithm)) - - acc = acc.GlobalAccessSpec(ctx) - Expect(acc).NotTo(BeNil()) - Expect(acc.GetKind()).To(Equal(ociartifact.Type)) - o := acc.(*ociartifact.AccessSpec) - Expect(o.ImageReference).To(Equal(TESTBASE + "/artifact1@sha256:" + testhelper.DIGEST_MANIFEST)) - - acc = Must(vers.AddBlob(blob, "", "artifact2:v1", nil)) - MustBeSuccessful(vers.SetResource(cpi.NewResourceMeta("image2", resourcetypes.OCI_IMAGE, metav1.LocalRelation), acc, cpi.ModifyResource())) - MustBeSuccessful(comp.AddVersion(vers)) - - res = Must(vers.GetResourceByIndex(1)) - acc = Must(res.Access()) - acc = acc.GlobalAccessSpec(ctx) - Expect(acc).NotTo(BeNil()) - Expect(acc.GetKind()).To(Equal(ociartifact.Type)) - o = acc.(*ociartifact.AccessSpec) - Expect(o.ImageReference).To(Equal(TESTBASE + "/artifact2:v1")) - - MustBeSuccessful(nested.Finalize()) - - ns := finalizer.ClosingWith(nested, Must(ocirepo.LookupNamespace("artifact2"))) - art := finalizer.ClosingWith(nested, Must(ns.GetArtifact("v1"))) - testhelper.CheckArtifact(art) - - MustBeSuccessful(finalize.Finalize()) - }) - - It("removes old unused layers", func() { - var finalize finalizer.Finalizer - defer Defer(finalize.Finalize, "finalize open elements") - - repo := finalizer.ClosingWith(&finalize, Must(DefaultContext.RepositoryForSpec(spec))) - impl := Must(repocpi.GetRepositoryImplementation(repo)) - Expect(reflect.TypeOf(impl).String()).To(Equal("*genericocireg.RepositoryImpl")) - - nested := finalize.Nested() - - comp := finalizer.ClosingWith(nested, Must(repo.LookupComponent(COMPONENT))) - vers := finalizer.ClosingWith(nested, Must(comp.NewVersion("v1"))) - - m1 := compdesc.NewResourceMeta("rsc1", resourcetypes.PLAIN_TEXT, metav1.LocalRelation) - blob := blobaccess.ForString(mime.MIME_TEXT, ocmtesthelper.S_TESTDATA) - - MustBeSuccessful(vers.SetResourceBlob(m1, blob, "", nil)) - MustBeSuccessful(comp.AddVersion(vers)) - - MustBeSuccessful(nested.Finalize()) - - // modify resource in component - vers = finalizer.ClosingWith(nested, Must(repo.LookupComponentVersion(COMPONENT, "v1"))) - blob = blobaccess.ForString(mime.MIME_TEXT, "otherdata") - MustBeSuccessful(vers.SetResourceBlob(m1, blob, "", nil)) - MustBeSuccessful(vers.Update()) - MustBeSuccessful(nested.Finalize()) - - // check content - vers = finalizer.ClosingWith(nested, Must(repo.LookupComponentVersion(COMPONENT, "v1"))) - r := Must(vers.GetResource(metav1.NewIdentity("rsc1"))) - data := Must(ocmutils.GetResourceData(r)) - Expect(string(data)).To(Equal("otherdata")) - MustBeSuccessful(nested.Finalize()) - - MustBeSuccessful(finalize.Finalize()) - - ocirepo := Must(DefaultContext.OCIContext().RepositoryForSpec(ocispec)) - finalize.Close(ocirepo) - - art := Must(ocirepo.LookupArtifact("component-descriptors/"+COMPONENT, "v1")) - finalize.Close(art) - - Expect(art.GetDescriptor().IsManifest()).To(BeTrue()) - m := Must(art.GetDescriptor().Manifest()) - Expect(len(m.Layers)).To(Equal(2)) - }) - - Context("legacy mode", func() { - It("creates a legacy dummy component", func() { - ctx := ocm.New() - compatattr.Set(ctx, true) - - var finalize finalizer.Finalizer - defer Defer(finalize.Finalize) - - repo := finalizer.ClosingWith(&finalize, Must(ctx.RepositoryForSpec(spec))) - comp := finalizer.ClosingWith(&finalize, Must(repo.LookupComponent(COMPONENT))) - vers := finalizer.ClosingWith(&finalize, Must(comp.NewVersion("v1"))) - MustBeSuccessful(comp.AddVersion(vers)) - MustBeSuccessful(finalize.Finalize()) - - // access as OCI repository - - ocirepo := finalizer.ClosingWith(&finalize, Must(oci.DefaultContext().RepositoryForSpec(ocispec))) - ns := finalizer.ClosingWith(&finalize, Must(ocirepo.LookupNamespace(path.Join(componentmapping.ComponentDescriptorNamespace, COMPONENT)))) - art := finalizer.ClosingWith(&finalize, Must(ns.GetArtifact("v1"))) - m := Must(art.GetDescriptor().Manifest()) - Expect(m.Config.MediaType).To(Equal(componentmapping.LegacyComponentDescriptorConfigMimeType)) - Expect(len(m.Layers)).To(Equal(1)) - Expect(m.Layers[0].MediaType).To(Equal(componentmapping.LegacyComponentDescriptorTarMimeType)) - }) - - It("updates a legacy dummy component", func() { - ctx := ocm.New() - compatattr.Set(ctx, true) - - var finalize finalizer.Finalizer - defer Defer(finalize.Finalize) - - repo := finalizer.ClosingWith(&finalize, Must(ctx.RepositoryForSpec(spec))) - comp := finalizer.ClosingWith(&finalize, Must(repo.LookupComponent(COMPONENT))) - vers := finalizer.ClosingWith(&finalize, Must(comp.NewVersion("v1"))) - MustBeSuccessful(comp.AddVersion(vers)) - MustBeSuccessful(finalize.Finalize()) - - // update component in non-legacy context - - repo = finalizer.ClosingWith(&finalize, Must(DefaultContext.RepositoryForSpec(spec))) - comp = finalizer.ClosingWith(&finalize, Must(repo.LookupComponent(COMPONENT))) - vers = finalizer.ClosingWith(&finalize, Must(comp.LookupVersion("v1"))) - vers.GetDescriptor().Provider.Name = "acme.org" - MustBeSuccessful(comp.AddVersion(vers)) - MustBeSuccessful(finalize.Finalize()) - - // access as OCI repository - - ocirepo := finalizer.ClosingWith(&finalize, Must(oci.DefaultContext().RepositoryForSpec(ocispec))) - ns := finalizer.ClosingWith(&finalize, Must(ocirepo.LookupNamespace(path.Join(componentmapping.ComponentDescriptorNamespace, COMPONENT)))) - art := finalizer.ClosingWith(&finalize, Must(ns.GetArtifact("v1"))) - m := Must(art.GetDescriptor().Manifest()) - Expect(m.Config.MediaType).To(Equal(componentmapping.LegacyComponentDescriptorConfigMimeType)) - Expect(len(m.Layers)).To(Equal(1)) - Expect(m.Layers[0].MediaType).To(Equal(componentmapping.LegacyComponentDescriptorTarMimeType)) - MustBeSuccessful(finalize.Finalize()) - - repo = finalizer.ClosingWith(&finalize, Must(DefaultContext.RepositoryForSpec(spec))) - vers = finalizer.ClosingWith(&finalize, Must(repo.LookupComponentVersion(COMPONENT, "v1"))) - Expect(string(vers.GetDescriptor().Provider.Name)).To(Equal("acme.org")) - }) - }) - - Context("repo urls", func() { - It("creates scheme based repo", func() { - ctx := ocm.New() - - spec := ocmreg.NewRepositorySpec("http://127.0.0.1:5000/ocm") - repo := Must(ctx.RepositoryForSpec(spec)) - defer Close(repo, "repo") - - ocirepo := genericocireg.GetOCIRepository(repo) - Expect(ocirepo).NotTo(BeNil()) - impl := Must(ocicpi.GetRepositoryImplementation(ocirepo)) - - Expect(impl).NotTo(BeNil()) - - Expect(impl.(*ocireg.RepositoryImpl).GetBaseURL()).To(Equal("http://127.0.0.1:5000")) - Expect(impl.(*ocireg.RepositoryImpl).GetRef("repo/path", "1.0.0")).To(Equal("127.0.0.1:5000/repo/path:1.0.0")) - }) - }) -}) diff --git a/pkg/contexts/ocm/repositories/genericocireg/repository.go b/pkg/contexts/ocm/repositories/genericocireg/repository.go deleted file mode 100644 index e616521ef..000000000 --- a/pkg/contexts/ocm/repositories/genericocireg/repository.go +++ /dev/null @@ -1,206 +0,0 @@ -package genericocireg - -import ( - "crypto/sha256" - "encoding/hex" - "fmt" - "path" - "strings" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/general" - - "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" - ocicpi "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/repocpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/genericocireg/componentmapping" -) - -type OCIBasedRepository interface { - OCIRepository() ocicpi.Repository -} - -func GetOCIRepository(r cpi.Repository) ocicpi.Repository { - if o, ok := r.(OCIBasedRepository); ok { - return o.OCIRepository() - } - impl, err := repocpi.GetRepositoryImplementation(r) - if err != nil { - return nil - } - if o, ok := impl.(OCIBasedRepository); ok { - return o.OCIRepository() - } - return nil -} - -type RepositoryImpl struct { - bridge repocpi.RepositoryBridge - ctx cpi.Context - meta ComponentRepositoryMeta - nonref cpi.Repository - ocirepo oci.Repository - readonly bool -} - -var ( - _ repocpi.RepositoryImpl = (*RepositoryImpl)(nil) - _ credentials.ConsumerIdentityProvider = (*RepositoryImpl)(nil) -) - -func NewRepository(ctxp cpi.ContextProvider, meta *ComponentRepositoryMeta, ocirepo oci.Repository) cpi.Repository { - ctx := datacontext.InternalContextRef(ctxp.OCMContext()) - impl := &RepositoryImpl{ - ctx: ctx, - meta: *DefaultComponentRepositoryMeta(meta), - ocirepo: ocirepo, - } - return repocpi.NewRepository(impl, "OCM repo[OCI]") -} - -func (r *RepositoryImpl) Close() error { - return r.ocirepo.Close() -} - -func (r *RepositoryImpl) IsReadOnly() bool { - // TODO: extend OCI to query ReadOnly mode - return r.readonly -} - -func (r *RepositoryImpl) SetReadOnly() { - r.readonly = true -} - -func (r *RepositoryImpl) SetBridge(base repocpi.RepositoryBridge) { - r.bridge = base - r.nonref = repocpi.NewNoneRefRepositoryView(base) -} - -func (r *RepositoryImpl) GetContext() cpi.Context { - return r.ctx -} - -func (r *RepositoryImpl) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { - prefix := r.meta.SubPath - if c, ok := general.Optional(uctx...).(credentials.StringUsageContext); ok { - prefix = path.Join(prefix, componentmapping.ComponentDescriptorNamespace, c.String()) - } - if p, ok := r.ocirepo.(credentials.ConsumerIdentityProvider); ok { - return p.GetConsumerId(credentials.StringUsageContext(prefix)) - } - return nil -} - -func (r *RepositoryImpl) GetIdentityMatcher() string { - if p, ok := r.ocirepo.(credentials.ConsumerIdentityProvider); ok { - return p.GetIdentityMatcher() - } - return "" -} - -func (r *RepositoryImpl) OCIRepository() ocicpi.Repository { - return r.ocirepo -} - -func (r *RepositoryImpl) Meta() ComponentRepositoryMeta { - return r.meta -} - -func (r *RepositoryImpl) GetSpecification() cpi.RepositorySpec { - return &RepositorySpec{ - RepositorySpec: r.ocirepo.GetSpecification(), - ComponentRepositoryMeta: r.meta, - } -} - -func (r *RepositoryImpl) ComponentLister() cpi.ComponentLister { - if r.meta.ComponentNameMapping != OCIRegistryURLPathMapping { - return nil - } - lister := r.ocirepo.NamespaceLister() - if lister == nil { - return nil - } - return r -} - -func (r *RepositoryImpl) NumComponents(prefix string) (int, error) { - lister := r.ocirepo.NamespaceLister() - if lister == nil { - return -1, errors.ErrNotSupported("component lister") - } - p := path.Join(r.meta.SubPath, componentmapping.ComponentDescriptorNamespace, prefix) - if strings.HasSuffix(prefix, "/") && !strings.HasSuffix(p, "/") { - p += "/" - } - return lister.NumNamespaces(p) -} - -func (r *RepositoryImpl) GetComponents(prefix string, closure bool) ([]string, error) { - lister := r.ocirepo.NamespaceLister() - if lister == nil { - return nil, errors.ErrNotSupported("component lister") - } - p := path.Join(r.meta.SubPath, componentmapping.ComponentDescriptorNamespace) - compprefix := len(p) + 1 - p = path.Join(p, prefix) - if strings.HasSuffix(prefix, "/") && !strings.HasSuffix(p, "/") { - p += "/" - } - tmp, err := lister.GetNamespaces(p, closure) - if err != nil { - return nil, err - } - result := make([]string, len(tmp)) - for i, r := range tmp { - result[i] = r[compprefix:] - } - return result, nil -} - -func (r *RepositoryImpl) GetOCIRepository() oci.Repository { - return r.ocirepo -} - -func (r *RepositoryImpl) ExistsComponentVersion(name string, version string) (bool, error) { - namespace, err := r.MapComponentNameToNamespace(name) - if err != nil { - return false, err - } - a, err := r.ocirepo.LookupArtifact(namespace, version) - if err != nil { - return false, err - } - defer a.Close() - desc, err := a.Manifest() - if err != nil { - return false, err - } - switch desc.Config.MediaType { - case componentmapping.ComponentDescriptorConfigMimeType, - componentmapping.LegacyComponentDescriptorConfigMimeType, - componentmapping.Legacy2ComponentDescriptorConfigMimeType: - return true, nil - } - return false, nil -} - -func (r *RepositoryImpl) LookupComponent(name string) (*repocpi.ComponentAccessInfo, error) { - return newComponentAccess(r, name, true) -} - -func (r *RepositoryImpl) MapComponentNameToNamespace(name string) (string, error) { - switch r.meta.ComponentNameMapping { - case OCIRegistryURLPathMapping, "": - return path.Join(r.meta.SubPath, componentmapping.ComponentDescriptorNamespace, name), nil - case OCIRegistryDigestMapping: - h := sha256.New() - _, _ = h.Write([]byte(name)) - return path.Join(r.meta.SubPath, hex.EncodeToString(h.Sum(nil))), nil - default: - return "", fmt.Errorf("unknown component name mapping method %s", r.meta.ComponentNameMapping) - } -} diff --git a/pkg/contexts/ocm/repositories/genericocireg/state.go b/pkg/contexts/ocm/repositories/genericocireg/state.go deleted file mode 100644 index 39d4f20ab..000000000 --- a/pkg/contexts/ocm/repositories/genericocireg/state.go +++ /dev/null @@ -1,284 +0,0 @@ -package genericocireg - -import ( - "archive/tar" - "bytes" - "encoding/json" - "fmt" - "io" - "reflect" - "strings" - - "github.com/go-test/deep" - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/logging" - "github.com/opencontainers/go-digest" - ociv1 "github.com/opencontainers/image-spec/specs-go/v1" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common/accessobj" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ctf/format" - "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/repositories/genericocireg/componentmapping" - ocmlog "github.com/open-component-model/ocm/pkg/logging" - "github.com/open-component-model/ocm/pkg/utils" -) - -func NewState(mode accessobj.AccessMode, name, version string, access oci.ManifestAccess, compat ...bool) (accessobj.State, error) { - return accessobj.NewState(mode, NewStateAccess(access, compat...), NewStateHandler(name, version)) -} - -// StateAccess handles the component descriptor persistence in an OCI Manifest. -type StateAccess struct { - access oci.ManifestAccess - layerMedia string - compat bool -} - -var _ accessobj.StateAccess = (*StateAccess)(nil) - -func NewStateAccess(access oci.ManifestAccess, compat ...bool) accessobj.StateAccess { - return &StateAccess{ - compat: utils.Optional(compat...), - access: access, - } -} - -func (s *StateAccess) Get() (blobaccess.BlobAccess, error) { - mediaType := s.access.GetDescriptor().Config.MediaType - switch mediaType { - case componentmapping.ComponentDescriptorConfigMimeType, - componentmapping.LegacyComponentDescriptorConfigMimeType, - componentmapping.Legacy2ComponentDescriptorConfigMimeType: - return s.get() - case "": - return nil, errors.ErrNotFound(cpi.KIND_COMPONENTVERSION) - default: - return nil, errors.Newf("artifact is no component: %s", mediaType) - } -} - -func (s *StateAccess) get() (blobaccess.BlobAccess, error) { - var config ComponentDescriptorConfig - - data, err := blobaccess.BlobData(s.access.GetConfigBlob()) - if err != nil { - return nil, err - } - err = json.Unmarshal(data, &config) - if err != nil { - return nil, err - } - if config.ComponentDescriptorLayer == nil || config.ComponentDescriptorLayer.Digest == "" { - return nil, errors.ErrInvalid("component descriptor config") - } - switch config.ComponentDescriptorLayer.MediaType { - case componentmapping.ComponentDescriptorJSONMimeType, - componentmapping.LegacyComponentDescriptorJSONMimeType, - componentmapping.ComponentDescriptorYAMLMimeType, - componentmapping.LegacyComponentDescriptorYAMLMimeType: - s.layerMedia = "" - return s.access.GetBlob(config.ComponentDescriptorLayer.Digest) - case componentmapping.ComponentDescriptorTarMimeType, - componentmapping.LegacyComponentDescriptorTarMimeType, - componentmapping.Legacy2ComponentDescriptorTarMimeType: - d, err := s.access.GetBlob(config.ComponentDescriptorLayer.Digest) - if err != nil { - return nil, err - } - r, err := d.Reader() - if err != nil { - return nil, err - } - defer r.Close() - data, err := s.readComponentDescriptorFromTar(r) - if err != nil { - return nil, err - } - s.layerMedia = config.ComponentDescriptorLayer.MediaType - return blobaccess.ForData(componentmapping.ComponentDescriptorYAMLMimeType, data), nil - default: - return nil, errors.ErrInvalid("config mediatype", config.ComponentDescriptorLayer.MediaType) - } -} - -// readComponentDescriptorFromTar reads the component descriptor from a tar. -// The component is expected to be inside the tar at "/component-descriptor.yaml". -func (s *StateAccess) readComponentDescriptorFromTar(r io.Reader) ([]byte, error) { - tr := tar.NewReader(r) - for { - header, err := tr.Next() - if err != nil { - if errors.Is(err, io.EOF) { - return nil, errors.New("no component descriptor found in tar") - } - return nil, fmt.Errorf("unable to read tar: %w", err) - } - - if strings.TrimLeft(header.Name, "/") != compdesc.ComponentDescriptorFileName { - continue - } - - var data bytes.Buffer - //nolint:gosec // We don't know what size limit we could set, the tar - // archive can be an image layer and that can even reach the gigabyte range. - // For now, we acknowledge the risk. - // - // We checked other softwares and tried to figure out how they manage this, - // but it's handled the same way. - if _, err := io.Copy(&data, tr); err != nil { - return nil, fmt.Errorf("erro while reading component descriptor file from tar: %w", err) - } - return data.Bytes(), err - } -} - -func (s StateAccess) Digest() digest.Digest { - blob, err := s.access.GetConfigBlob() - if err != nil { - return "" - } - return blob.Digest() -} - -func (s *StateAccess) Put(data []byte) error { - desc := s.access.GetDescriptor() - mediaType := desc.Config.MediaType - if mediaType == "" { - if s.compat { - mediaType = componentmapping.LegacyComponentDescriptorConfigMimeType - } else { - mediaType = componentmapping.ComponentDescriptorConfigMimeType - } - desc.Config.MediaType = mediaType - } - - arch, err := s.writeComponentDescriptorTar(data) - if err != nil { - return err - } - config := ComponentDescriptorConfig{ - ComponentDescriptorLayer: artdesc.DefaultBlobDescriptor(arch), - } - - configdata, err := json.Marshal(&config) - if err != nil { - return err - } - - err = s.access.AddBlob(arch) - if err != nil { - return err - } - s.layerMedia = arch.MimeType() - configblob := blobaccess.ForData(mediaType, configdata) - err = s.access.AddBlob(configblob) - if err != nil { - return err - } - desc.Config = *artdesc.DefaultBlobDescriptor(configblob) - if len(desc.Layers) < 2 { - desc.Layers = []ociv1.Descriptor{*artdesc.DefaultBlobDescriptor(arch)} - } else { - desc.Layers[0] = *artdesc.DefaultBlobDescriptor(arch) - } - return nil -} - -// writeComponentDescriptorTar writes the component descriptor into a tar. -// The component is expected to be inside the tar at "/component-descriptor.yaml". -func (s *StateAccess) writeComponentDescriptorTar(data []byte) (cpi.BlobAccess, error) { - var buf bytes.Buffer - tw := tar.NewWriter(&buf) - err := tw.WriteHeader(&tar.Header{ - Typeflag: tar.TypeReg, - Name: componentmapping.ComponentDescriptorFileName, - Size: int64(len(data)), - ModTime: format.ModTime, - }) - - media := s.layerMedia - if media == "" { - if s.compat { - media = componentmapping.LegacyComponentDescriptorTarMimeType - } else { - media = componentmapping.ComponentDescriptorTarMimeType - } - } - if err != nil { - return nil, errors.Newf("unable to add component descriptor header: %s", err) - } - if _, err := io.Copy(tw, bytes.NewBuffer(data)); err != nil { - return nil, errors.Newf("unable to write component-descriptor to tar: %s", err) - } - if err := tw.Close(); err != nil { - return nil, errors.Newf("unable to close tar writer: %s", err) - } - return blobaccess.ForData(media, buf.Bytes()), nil -} - -// ComponentDescriptorConfig is a Component-Descriptor OCI configuration that is used to store the reference to the -// (pseudo-)layer used to store the Component-Descriptor in. -type ComponentDescriptorConfig struct { - ComponentDescriptorLayer *ociv1.Descriptor `json:"componentDescriptorLayer,omitempty"` -} - -//////////////////////////////////////////////////////////////////////////////// - -// StateHandler handles the encoding of a component descriptor. -type StateHandler struct { - name string - version string -} - -var _ accessobj.StateHandler = (*StateHandler)(nil) - -func NewStateHandler(name, version string) accessobj.StateHandler { - return &StateHandler{ - name: name, - version: version, - } -} - -func (i StateHandler) Initial() interface{} { - return compdesc.New(i.name, i.version) -} - -// Encode always provides a yaml representation. -func (i StateHandler) Encode(d interface{}) ([]byte, error) { - desc, ok := d.(*compdesc.ComponentDescriptor) - if !ok { - return nil, fmt.Errorf("failed to assert type %t to *compdesc.ComponentDescriptor", d) - } - desc.Name = i.name - desc.Version = i.version - return compdesc.Encode(desc) -} - -// Decode always accepts a yaml representation, and therefore json, also. -func (i StateHandler) Decode(data []byte) (interface{}, error) { - return compdesc.Decode(data) -} - -func (i StateHandler) Equivalent(a, b interface{}) bool { - if l := Logger(ocmlog.Context(), TAG_CDDIFF); l.Enabled(logging.DebugLevel) { - diff := deep.Equal(a, b) - if len(diff) > 0 { - l.Debug("component descriptor has been changed", "diff", diff) - return false - } - return true - } - - ea, err := i.Encode(a) - if err == nil { - eb, err := i.Encode(b) - if err == nil { - return bytes.Equal(ea, eb) - } - } - return reflect.DeepEqual(a, b) -} diff --git a/pkg/contexts/ocm/repositories/genericocireg/type.go b/pkg/contexts/ocm/repositories/genericocireg/type.go deleted file mode 100644 index 49c796ee1..000000000 --- a/pkg/contexts/ocm/repositories/genericocireg/type.go +++ /dev/null @@ -1,191 +0,0 @@ -package genericocireg - -import ( - "encoding/json" - "path" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/general" - "github.com/sirupsen/logrus" - - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/genericocireg/componentmapping" - "github.com/open-component-model/ocm/pkg/runtime" -) - -// ComponentNameMapping describes the method that is used to map the "Component Name", "Component Version"-tuples -// to OCI Image References. -type ComponentNameMapping string - -const ( - Type = ocireg.Type - - OCIRegistryURLPathMapping ComponentNameMapping = "urlPath" - OCIRegistryDigestMapping ComponentNameMapping = "sha256-digest" -) - -func init() { - cpi.DefaultDelegationRegistry().Register("OCI", New(10)) -} - -// delegation tries to resolve an ocm repository specification -// with an OCI repository specification supported by the OCI context -// of the OCM context. -type delegation struct { - prio int -} - -func New(prio int) cpi.RepositoryPriorityDecoder { - return &delegation{prio} -} - -var _ cpi.RepositoryPriorityDecoder = (*delegation)(nil) - -func (d *delegation) Decode(ctx cpi.Context, data []byte, unmarshal runtime.Unmarshaler) (cpi.RepositorySpec, error) { - if unmarshal == nil { - unmarshal = runtime.DefaultYAMLEncoding.Unmarshaler - } - - ospec, err := ctx.OCIContext().RepositoryTypes().Decode(data, unmarshal) - if err != nil { - return nil, err - } - if oci.IsUnknown(ospec) { - return nil, nil - } - - meta := &ComponentRepositoryMeta{} - err = unmarshal.Unmarshal(data, meta) - if err != nil { - return nil, errors.Wrapf(err, "cannot unmarshal component repository meta information") - } - return normalizers.Normalize(NewRepositorySpec(ospec, meta)), nil -} - -func (d *delegation) Priority() int { - return d.prio -} - -// ComponentRepositoryMeta describes config special for a mapping of -// a component repository to an oci registry. -// It is parsed in addition to an OCI based specification. -type ComponentRepositoryMeta struct { - // ComponentNameMapping describes the method that is used to map the "Component Name", "Component Version"-tuples - // to OCI Image References. - ComponentNameMapping ComponentNameMapping `json:"componentNameMapping,omitempty"` - SubPath string `json:"subPath,omitempty"` -} - -func NewComponentRepositoryMeta(subPath string, mapping ComponentNameMapping) *ComponentRepositoryMeta { - return DefaultComponentRepositoryMeta(&ComponentRepositoryMeta{ - ComponentNameMapping: mapping, - SubPath: subPath, - }) -} - -//////////////////////////////////////////////////////////////////////////////// - -type RepositorySpec struct { - oci.RepositorySpec - ComponentRepositoryMeta -} - -var ( - _ cpi.RepositorySpec = (*RepositorySpec)(nil) - _ cpi.PrefixProvider = (*RepositorySpec)(nil) - _ cpi.IntermediateRepositorySpecAspect = (*RepositorySpec)(nil) - _ json.Marshaler = (*RepositorySpec)(nil) - _ credentials.ConsumerIdentityProvider = (*RepositorySpec)(nil) -) - -func NewRepositorySpec(spec oci.RepositorySpec, meta *ComponentRepositoryMeta) *RepositorySpec { - s := &RepositorySpec{ - RepositorySpec: spec, - ComponentRepositoryMeta: *DefaultComponentRepositoryMeta(meta), - } - return normalizers.Normalize(s) -} - -func (a *RepositorySpec) PathPrefix() string { - return a.SubPath -} - -func (a *RepositorySpec) IsIntermediate() bool { - if s, ok := a.RepositorySpec.(oci.IntermediateRepositorySpecAspect); ok { - return s.IsIntermediate() - } - return false -} - -// TODO: host etc is missing - -func (a *RepositorySpec) AsUniformSpec(cpi.Context) *cpi.UniformRepositorySpec { - return &cpi.UniformRepositorySpec{Type: a.GetKind(), SubPath: a.SubPath} -} - -func (u *RepositorySpec) UnmarshalJSON(data []byte) error { - logrus.Debugf("unmarshal generic ocireg spec %s\n", string(data)) - ocispec := &oci.GenericRepositorySpec{} - if err := json.Unmarshal(data, ocispec); err != nil { - return err - } - compmeta := &ComponentRepositoryMeta{} - if err := json.Unmarshal(data, ocispec); err != nil { - return err - } - - u.RepositorySpec = ocispec - u.ComponentRepositoryMeta = *compmeta - - normalizers.Normalize(u) - return nil -} - -// MarshalJSON implements a custom json unmarshal method for an unstructured type. -// The oci.RepositorySpec object might already implement json.Marshaler, -// which would be inherited and omit marshaling the addend attributes of a -// cpi.RepositorySpec. -func (u RepositorySpec) MarshalJSON() ([]byte, error) { - ocispec, err := runtime.ToUnstructuredTypedObject(u.RepositorySpec) - if err != nil { - return nil, err - } - compmeta, err := runtime.ToUnstructuredObject(u.ComponentRepositoryMeta) - if err != nil { - return nil, err - } - return json.Marshal(compmeta.FlatMerge(ocispec.Object)) -} - -func (s *RepositorySpec) Repository(ctx cpi.Context, creds credentials.Credentials) (cpi.Repository, error) { - r, err := s.RepositorySpec.Repository(ctx.OCIContext(), creds) - if err != nil { - return nil, err - } - return NewRepository(ctx, &s.ComponentRepositoryMeta, r), nil -} - -func (s *RepositorySpec) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { - prefix := s.SubPath - if c, ok := general.Optional(uctx...).(credentials.StringUsageContext); ok { - prefix = path.Join(prefix, componentmapping.ComponentDescriptorNamespace, c.String()) - } - return credentials.GetProvidedConsumerId(s.RepositorySpec, credentials.StringUsageContext(prefix)) -} - -func (s *RepositorySpec) GetIdentityMatcher() string { - return credentials.GetProvidedIdentityMatcher(s.RepositorySpec) -} - -func DefaultComponentRepositoryMeta(meta *ComponentRepositoryMeta) *ComponentRepositoryMeta { - if meta == nil { - meta = &ComponentRepositoryMeta{} - } - if meta.ComponentNameMapping == "" { - meta.ComponentNameMapping = OCIRegistryURLPathMapping - } - return meta -} diff --git a/pkg/contexts/ocm/repositories/genericocireg/uniform.go b/pkg/contexts/ocm/repositories/genericocireg/uniform.go deleted file mode 100644 index 72aa019e7..000000000 --- a/pkg/contexts/ocm/repositories/genericocireg/uniform.go +++ /dev/null @@ -1,86 +0,0 @@ -package genericocireg - -import ( - "fmt" - "strings" - - "github.com/open-component-model/ocm/pkg/contexts/oci/grammar" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/compatattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" -) - -func init() { - cpi.RegisterRepositorySpecHandler(&repospechandler{}, "*") - cpi.RegisterRefParseHandler(Type, HandleRef) - cpi.RegisterRefParseHandler(ocireg.ShortType, HandleRef) -} - -type repospechandler struct{} - -func (h *repospechandler) MapReference(ctx cpi.Context, u *cpi.UniformRepositorySpec) (cpi.RepositorySpec, error) { - var meta *ComponentRepositoryMeta - host := u.Host - subp := u.SubPath - - // This is checked because it can lead to confusion with the ocm notation. - if strings.Contains(subp, "//") { - return nil, fmt.Errorf("subpath %q cannot contain double slash (//)", subp) - } - if u.Type == Type { - if u.Info != "" && u.SubPath == "" { - idx := strings.Index(u.Info, grammar.RepositorySeparator) - if idx > 0 { - host = u.Info[:idx] - subp = u.Info[idx+1:] - } else { - host = u.Info - } - } else if u.Host == "" { - return nil, fmt.Errorf("host required for OCI based OCM reference") - } - } else { - if u.Type != "" || u.Info != "" || u.Host == "" { - return nil, nil - } - host = u.Host - } - if u.Scheme != "" { - host = u.Scheme + "://" + host - } - if subp != "" { - meta = NewComponentRepositoryMeta(subp, "") - } - if compatattr.Get(ctx) { - return NewRepositorySpec(ocireg.NewLegacyRepositorySpec(host), meta), nil - } - return NewRepositorySpec(ocireg.NewRepositorySpec(host), meta), nil -} - -func HandleRef(u *cpi.UniformRepositorySpec) error { - if u.Host == "" && u.Info != "" && u.SubPath == "" { - info := u.Info - scheme := "" - match := grammar.AnchoredSchemedRegexp.FindStringSubmatch(info) - if match != nil { - scheme = match[1] - info = match[2] - } - host := "" - subp := "" - idx := strings.Index(info, grammar.RepositorySeparator) - if idx > 0 { - host = info[:idx] - subp = info[idx+1:] - } else { - host = info - } - if grammar.HostPortRegexp.MatchString(host) || grammar.DomainPortRegexp.MatchString(host) { - u.Scheme = scheme - u.Host = host - u.SubPath = subp - u.Info = "" - } - } - return nil -} diff --git a/pkg/contexts/ocm/repositories/init.go b/pkg/contexts/ocm/repositories/init.go deleted file mode 100644 index f813c1ee7..000000000 --- a/pkg/contexts/ocm/repositories/init.go +++ /dev/null @@ -1,7 +0,0 @@ -package repositories - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/comparch" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/genericocireg" -) diff --git a/pkg/contexts/ocm/repositories/ocireg/type.go b/pkg/contexts/ocm/repositories/ocireg/type.go deleted file mode 100644 index 7a24c59ea..000000000 --- a/pkg/contexts/ocm/repositories/ocireg/type.go +++ /dev/null @@ -1,43 +0,0 @@ -package ocireg - -import ( - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/ocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/genericocireg" - "github.com/open-component-model/ocm/pkg/utils" -) - -// ComponentNameMapping describes the method that is used to map the "Component Name", "Component Version"-tuples -// to OCI Image References. -type ComponentNameMapping = genericocireg.ComponentNameMapping - -const ( - Type = ocireg.Type - TypeV1 = ocireg.TypeV1 - - OCIRegistryURLPathMapping ComponentNameMapping = "urlPath" - OCIRegistryDigestMapping ComponentNameMapping = "sha256-digest" -) - -// ComponentRepositoryMeta describes config special for a mapping of -// a component repository to an oci registry. -type ComponentRepositoryMeta = genericocireg.ComponentRepositoryMeta - -// RepositorySpec describes a component repository backed by a oci registry. -type RepositorySpec = genericocireg.RepositorySpec - -// NewRepositorySpec creates a new RepositorySpec. -// If no ocm meta is given, the subPath part is extracted from the base URL. -// Otherwise, the given URL is used as OCI registry URL as it is. -func NewRepositorySpec(baseURL string, metas ...*ComponentRepositoryMeta) *RepositorySpec { - return genericocireg.NewRepositorySpec(ocireg.NewRepositorySpec(baseURL), utils.Optional(metas...)) -} - -func NewComponentRepositoryMeta(subPath string, mapping ...ComponentNameMapping) *ComponentRepositoryMeta { - return genericocireg.NewComponentRepositoryMeta(subPath, utils.OptionalDefaulted(OCIRegistryURLPathMapping, mapping...)) -} - -func NewRepository(ctx cpi.ContextProvider, baseURL string, metas ...*ComponentRepositoryMeta) (cpi.Repository, error) { - spec := NewRepositorySpec(baseURL, metas...) - return ctx.OCMContext().RepositoryForSpec(spec) -} diff --git a/pkg/contexts/ocm/repositories/virtual/access.go b/pkg/contexts/ocm/repositories/virtual/access.go deleted file mode 100644 index 70d66de20..000000000 --- a/pkg/contexts/ocm/repositories/virtual/access.go +++ /dev/null @@ -1,34 +0,0 @@ -package virtual - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" -) - -type VersionAccess interface { - GetDescriptor() *compdesc.ComponentDescriptor - GetBlob(name string) (cpi.DataAccess, error) - AddBlob(blob cpi.BlobAccess) (string, error) - Update() error - Close() error - - IsReadOnly() bool - SetReadOnly() -} - -type Access interface { - ComponentLister() cpi.ComponentLister - - ExistsComponentVersion(name string, version string) (bool, error) - ListVersions(comp string) ([]string, error) - - GetComponentVersion(comp, version string) (VersionAccess, error) - - IsReadOnly() bool - SetReadOnly() - Close() error -} - -type RepositorySpecProvider interface { - GetSpecification() cpi.RepositorySpec -} diff --git a/pkg/contexts/ocm/repositories/virtual/accessmethod_localblob.go b/pkg/contexts/ocm/repositories/virtual/accessmethod_localblob.go deleted file mode 100644 index f7fd081b1..000000000 --- a/pkg/contexts/ocm/repositories/virtual/accessmethod_localblob.go +++ /dev/null @@ -1,64 +0,0 @@ -package virtual - -import ( - "io" - "sync" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" -) - -type localBlobAccessMethod struct { - lock sync.Mutex - data blobaccess.DataAccess - spec *localblob.AccessSpec -} - -var _ accspeccpi.AccessMethodImpl = (*localBlobAccessMethod)(nil) - -func newLocalBlobAccessMethod(a *localblob.AccessSpec, data blobaccess.DataAccess) (*localBlobAccessMethod, error) { - return &localBlobAccessMethod{ - spec: a, - data: data, - }, nil -} - -func (_ *localBlobAccessMethod) IsLocal() bool { - return true -} - -func (m *localBlobAccessMethod) GetKind() string { - return m.spec.GetKind() -} - -func (m *localBlobAccessMethod) AccessSpec() accspeccpi.AccessSpec { - return m.spec -} - -func (m *localBlobAccessMethod) Close() error { - m.lock.Lock() - defer m.lock.Unlock() - - if m.data == nil { - return blobaccess.ErrClosed - } - list := errors.ErrorList{} - list.Add(m.data.Close()) - m.data = nil - return list.Result() -} - -func (m *localBlobAccessMethod) Reader() (io.ReadCloser, error) { - return m.data.Reader() -} - -func (m *localBlobAccessMethod) Get() (data []byte, ferr error) { - return blobaccess.BlobData(m.data) -} - -func (m *localBlobAccessMethod) MimeType() string { - return m.spec.MediaType -} diff --git a/pkg/contexts/ocm/repositories/virtual/component.go b/pkg/contexts/ocm/repositories/virtual/component.go deleted file mode 100644 index 634c248c0..000000000 --- a/pkg/contexts/ocm/repositories/virtual/component.go +++ /dev/null @@ -1,84 +0,0 @@ -package virtual - -import ( - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/general" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/repocpi" -) - -type componentAccessImpl struct { - bridge repocpi.ComponentAccessBridge - - repo *RepositoryImpl - name string -} - -var _ repocpi.ComponentAccessImpl = (*componentAccessImpl)(nil) - -func newComponentAccess(repo *RepositoryImpl, name string, main bool) (*repocpi.ComponentAccessInfo, error) { - impl := &componentAccessImpl{ - repo: repo, - name: name, - } - return &repocpi.ComponentAccessInfo{impl, "OCM component[Simple]", main}, nil -} - -func (c *componentAccessImpl) Close() error { - return nil -} - -func (c *componentAccessImpl) SetBridge(base repocpi.ComponentAccessBridge) { - c.bridge = base -} - -func (c *componentAccessImpl) GetParentBridge() repocpi.RepositoryViewManager { - return c.repo.bridge -} - -func (c *componentAccessImpl) GetContext() cpi.Context { - return c.repo.GetContext() -} - -func (c *componentAccessImpl) GetName() string { - return c.name -} - -func (c *componentAccessImpl) ListVersions() ([]string, error) { - return c.repo.access.ListVersions(c.name) -} - -func (c *componentAccessImpl) HasVersion(vers string) (bool, error) { - return c.repo.ExistsComponentVersion(c.name, vers) -} - -func (c *componentAccessImpl) IsReadOnly() bool { - return c.repo.access.IsReadOnly() -} - -func (c *componentAccessImpl) LookupVersion(version string) (*repocpi.ComponentVersionAccessInfo, error) { - ok, err := c.HasVersion(version) - if err != nil { - return nil, err - } - if !ok { - return nil, cpi.ErrComponentVersionNotFoundWrap(err, c.name, version) - } - return newComponentVersionAccess(c, version, true) -} - -func (c *componentAccessImpl) NewVersion(version string, overrides ...bool) (*repocpi.ComponentVersionAccessInfo, error) { - override := general.Optional(overrides...) - ok, err := c.HasVersion(version) - if err == nil && ok { - if override { - return newComponentVersionAccess(c, version, false) - } - return nil, errors.ErrAlreadyExists(cpi.KIND_COMPONENTVERSION, c.name+"/"+version) - } - if err != nil && !errors.IsErrNotFoundKind(err, cpi.KIND_COMPONENTVERSION) { - return nil, err - } - return newComponentVersionAccess(c, version, false) -} diff --git a/pkg/contexts/ocm/repositories/virtual/componentversion.go b/pkg/contexts/ocm/repositories/virtual/componentversion.go deleted file mode 100644 index 6b287d6f5..000000000 --- a/pkg/contexts/ocm/repositories/virtual/componentversion.go +++ /dev/null @@ -1,154 +0,0 @@ -package virtual - -import ( - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localfsblob" - ocmhdlr "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/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/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/repocpi" - "github.com/open-component-model/ocm/pkg/errkind" - "github.com/open-component-model/ocm/pkg/refmgmt" -) - -// newComponentVersionAccess creates a component access for the artifact access, if this fails the artifact acess is closed. -func newComponentVersionAccess(comp *componentAccessImpl, version string, persistent bool) (*repocpi.ComponentVersionAccessInfo, error) { - access, err := comp.repo.access.GetComponentVersion(comp.GetName(), version) - if err != nil { - return nil, err - } - c, err := newComponentVersionContainer(comp, version, access) - if err != nil { - return nil, err - } - return &repocpi.ComponentVersionAccessInfo{c, true, persistent}, nil -} - -// ////////////////////////////////////////////////////////////////////////////// - -type ComponentVersionContainer struct { - bridge repocpi.ComponentVersionAccessBridge - - comp *componentAccessImpl - version string - access VersionAccess -} - -var _ repocpi.ComponentVersionAccessImpl = (*ComponentVersionContainer)(nil) - -func newComponentVersionContainer(comp *componentAccessImpl, version string, access VersionAccess) (*ComponentVersionContainer, error) { - return &ComponentVersionContainer{ - comp: comp, - version: version, - access: access, - }, nil -} - -func (c *ComponentVersionContainer) SetBridge(base repocpi.ComponentVersionAccessBridge) { - c.bridge = base -} - -func (c *ComponentVersionContainer) GetParentBridge() repocpi.ComponentAccessBridge { - return c.comp.bridge -} - -func (c *ComponentVersionContainer) Close() error { - if c.access == nil { - return accessio.ErrClosed - } - a := c.access - c.access = nil - return a.Close() -} - -func (c *ComponentVersionContainer) Check() error { - if c.version != c.GetDescriptor().Version { - return errors.ErrInvalid("component version", c.GetDescriptor().Version) - } - if c.comp.name != c.GetDescriptor().Name { - return errors.ErrInvalid("component name", c.GetDescriptor().Name) - } - return nil -} - -func (c *ComponentVersionContainer) Repository() cpi.Repository { - return c.comp.repo.nonref -} - -func (c *ComponentVersionContainer) GetContext() cpi.Context { - return c.comp.GetContext() -} - -func (c *ComponentVersionContainer) IsReadOnly() bool { - return c.access.IsReadOnly() -} - -func (c *ComponentVersionContainer) SetReadOnly() { - c.access.SetReadOnly() -} - -func (c *ComponentVersionContainer) IsClosed() bool { - return c.access == nil -} - -func (c *ComponentVersionContainer) AccessMethod(a cpi.AccessSpec, cv refmgmt.ExtendedAllocatable) (cpi.AccessMethod, error) { - accessSpec, err := c.comp.GetContext().AccessSpecForSpec(a) - if err != nil { - return nil, err - } - - switch a.GetKind() { // to be extended - case localfsblob.Type: - fallthrough - case localblob.Type: - blob, err := c.access.GetBlob(accessSpec.(*localblob.AccessSpec).LocalReference) - if err != nil { - return nil, err - } - - return accspeccpi.AccessMethodForImplementation(newLocalBlobAccessMethod(accessSpec.(*localblob.AccessSpec), blob)) - } - - return nil, errors.ErrNotSupported(errkind.KIND_ACCESSMETHOD, a.GetType(), "virtual registry") -} - -func (c *ComponentVersionContainer) Update() error { - return c.access.Update() -} - -func (c *ComponentVersionContainer) SetDescriptor(cd *compdesc.ComponentDescriptor) error { - cur := c.access.GetDescriptor() - *cur = *cd - return c.access.Update() -} - -func (c *ComponentVersionContainer) GetDescriptor() *compdesc.ComponentDescriptor { - return c.access.GetDescriptor() -} - -func (c *ComponentVersionContainer) GetBlob(name string) (cpi.DataAccess, error) { - return c.access.GetBlob(name) -} - -func (c *ComponentVersionContainer) GetStorageContext() cpi.StorageContext { - return ocmhdlr.New(c.Repository(), c.comp.GetName(), c.access, Type, c.access) -} - -func (c *ComponentVersionContainer) AddBlob(blob cpi.BlobAccess, refName string, global cpi.AccessSpec) (cpi.AccessSpec, error) { - if c.IsReadOnly() { - return nil, accessio.ErrReadOnly - } - if blob == nil { - return nil, errors.New("a resource has to be defined") - } - - ref, err := c.access.AddBlob(blob) - if err != nil { - return nil, err - } - return localblob.New(ref, refName, blob.MimeType(), global), nil -} diff --git a/pkg/contexts/ocm/repositories/virtual/index.go b/pkg/contexts/ocm/repositories/virtual/index.go deleted file mode 100644 index 264d4b6b2..000000000 --- a/pkg/contexts/ocm/repositories/virtual/index.go +++ /dev/null @@ -1,108 +0,0 @@ -package virtual - -import ( - "sync" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/common" - ocicpi "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" - "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/utils" -) - -type IndexEntry[I interface{}] struct { - cd *compdesc.ComponentDescriptor - info I -} - -func (i *IndexEntry[I]) CD() *compdesc.ComponentDescriptor { - if i == nil { - return nil - } - return i.cd -} - -func (i *IndexEntry[I]) Info() I { - var zero I - if i == nil { - return zero - } - return i.info -} - -type Index[I interface{}] struct { - lock sync.Mutex - descriptors map[string]map[string]*IndexEntry[I] -} - -func NewIndex[I interface{}]() *Index[I] { - return &Index[I]{descriptors: map[string]map[string]*IndexEntry[I]{}} -} - -func (i *Index[I]) NumComponents(prefix string) (int, error) { - i.lock.Lock() - defer i.lock.Unlock() - - list := ocicpi.FilterByNamespacePrefix(prefix, utils.StringMapKeys(i.descriptors)) - return len(list), nil -} - -func (i *Index[I]) GetComponents(prefix string, closure bool) ([]string, error) { - i.lock.Lock() - defer i.lock.Unlock() - - return ocicpi.FilterChildren(closure, prefix, utils.StringMapKeys(i.descriptors)), nil -} - -func (i *Index[I]) GetVersions(comp string) []string { - i.lock.Lock() - defer i.lock.Unlock() - - vers := i.descriptors[comp] - if len(vers) == 0 { - return []string{} - } - return utils.StringMapKeys(vers) -} - -func (i *Index[I]) Get(comp, vers string) *IndexEntry[I] { - i.lock.Lock() - defer i.lock.Unlock() - - var e *IndexEntry[I] - set := i.descriptors[comp] - if len(vers) != 0 { - e = set[vers] - } - return e -} - -func (i *Index[I]) Add(cd *compdesc.ComponentDescriptor, info I) error { - i.lock.Lock() - defer i.lock.Unlock() - - set := i.descriptors[cd.Name] - if set == nil { - set = map[string]*IndexEntry[I]{} - i.descriptors[cd.Name] = set - } - if set[cd.Version] != nil { - return errors.ErrAlreadyExists(cpi.KIND_COMPONENTVERSION, common.VersionedElementKey(cd).String()) - } - set[cd.Version] = &IndexEntry[I]{cd, info} - return nil -} - -func (i *Index[I]) Set(cd *compdesc.ComponentDescriptor, info I) { - i.lock.Lock() - defer i.lock.Unlock() - - set := i.descriptors[cd.Name] - if set == nil { - set = map[string]*IndexEntry[I]{} - i.descriptors[cd.Name] = set - } - set[cd.Version] = &IndexEntry[I]{cd, info} -} diff --git a/pkg/contexts/ocm/repositories/virtual/repo_test.go b/pkg/contexts/ocm/repositories/virtual/repo_test.go deleted file mode 100644 index d6a730ce6..000000000 --- a/pkg/contexts/ocm/repositories/virtual/repo_test.go +++ /dev/null @@ -1,130 +0,0 @@ -package virtual_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/env" - . "github.com/open-component-model/ocm/pkg/env/builder" - - "github.com/mandelsoft/goutils/finalizer" - "github.com/mandelsoft/vfs/pkg/layerfs" - "github.com/mandelsoft/vfs/pkg/memoryfs" - "github.com/mandelsoft/vfs/pkg/projectionfs" - - "github.com/open-component-model/ocm/pkg/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/compose" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/compositionmodeattr" - "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/contexts/ocm/repositories/virtual" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/virtual/example" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - ocmutils "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" - "github.com/open-component-model/ocm/pkg/mime" -) - -var _ = Describe("virtual repo", func() { - var env *Builder - var repo ocm.Repository - var access *example.Access - - // ocmlog.Context().AddRule(logging.NewConditionRule(logging.TraceLevel, accessio.ALLOC_REALM)) - - AfterEach(func() { - MustBeSuccessful(repo.Close()) - env.Cleanup() - }) - - Context("readonly", func() { - BeforeEach(func() { - env = NewBuilder(TestData()) - access = Must(example.NewAccess(Must(projectionfs.New(env, "testdata")), true)) - repo = virtual.NewRepository(env.OCMContext(), access) - }) - - It("handles list", func() { - lister := repo.ComponentLister() - Expect(lister).NotTo(BeNil()) - names := Must(lister.GetComponents("", true)) - Expect(names).To(ConsistOf([]string{"acme.org/component", "acme.org/component/ref"})) - }) - - It("handles get", func() { - comp := Must(repo.LookupComponent("acme.org/component")) - defer Close(comp, "component") - Expect(comp.ListVersions()).To(ConsistOf([]string{"v1.0.0"})) - Expect(comp.HasVersion("v1.0.0")).To(BeTrue()) - Expect(comp.HasVersion("v1.0.1")).To(BeFalse()) - vers := Must(comp.LookupVersion("v1.0.0")) - defer Close(vers, "version") - r := Must(vers.GetResourceByIndex(0)) - data := Must(ocmutils.GetResourceData(r)) - Expect(string(data)).To(Equal("my test data\n")) - - a := Must(r.Access()) - Expect(a.GetVersion()).To(Equal("v1")) - }) - }) - - Context("modifiable", func() { - BeforeEach(func() { - env = NewBuilder(TestData()) - - fs := Must(projectionfs.New(env, "testdata")) - fs = layerfs.New(memoryfs.New(), fs) - access = Must(example.NewAccess(fs, false)) - repo = virtual.NewRepository(env.OCMContext(), access) - }) - - DescribeTable("handles put", func(mode bool, typ string) { - var finalize finalizer.Finalizer - defer Defer(finalize.Finalize) - - compositionmodeattr.Set(env.OCMContext(), mode) - comp := Must(repo.LookupComponent("acme.org/component/new")) - finalize.Close(comp, "component") - Expect(comp.ListVersions()).To(ConsistOf([]string{})) - vers := Must(comp.NewVersion("v1.0.0", false)) - finalize.Close(vers, "version") - - blob := blobaccess.ForString(mime.MIME_TEXT, "new test data") - MustBeSuccessful(vers.SetResourceBlob(compdesc.NewResourceMeta("new", resourcetypes.PLAIN_TEXT, metav1.LocalRelation), blob, "", nil)) - - r := Must(vers.GetResourceByIndex(0)) - a := Must(r.Access()) - Expect(a.GetKind()).To(Equal(typ)) - - comp.AddVersion(vers) - r = Must(vers.GetResourceByIndex(0)) // re-read resource from component descriptor. - a = Must(r.Access()) - Expect(a.GetKind()).To(Equal(localblob.Type)) - - dig := "fe81d80611e39a10f1d7d12f98ce0bc6fe745d08fef007d8eebddc0a21d17827" - Expect(a.(*localblob.AccessSpec).LocalReference).To(Equal(dig)) - - MustBeSuccessful(finalize.Finalize()) - - MustBeSuccessful(access.Reset()) - - comp = Must(repo.LookupComponent("acme.org/component/new")) - finalize.Close(comp, "component") - Expect(comp.ListVersions()).To(ConsistOf([]string{"v1.0.0"})) - Expect(comp.HasVersion("v1.0.0")).To(BeTrue()) - Expect(comp.HasVersion("v1.0.1")).To(BeFalse()) - vers = Must(comp.LookupVersion("v1.0.0")) - finalize.Close(vers, "version") - r = Must(vers.GetResourceByIndex(0)) - data := Must(ocmutils.GetResourceData(r)) - Expect(string(data)).To(Equal("new test data")) - - a = Must(r.Access()) - Expect(a.GetVersion()).To(Equal("v1")) - }, - Entry("with direct mode", false, localblob.Type), - Entry("with composition mode", true, compose.Type), - ) - }) -}) diff --git a/pkg/contexts/ocm/repositories/virtual/repository.go b/pkg/contexts/ocm/repositories/virtual/repository.go deleted file mode 100644 index 980abf9d0..000000000 --- a/pkg/contexts/ocm/repositories/virtual/repository.go +++ /dev/null @@ -1,64 +0,0 @@ -package virtual - -import ( - "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/cpi/repocpi" -) - -type RepositoryImpl struct { - bridge repocpi.RepositoryBridge - ctx cpi.Context - access Access - nonref cpi.Repository -} - -var _ repocpi.RepositoryImpl = (*RepositoryImpl)(nil) - -func NewRepository(ctxp cpi.ContextProvider, acc Access) cpi.Repository { - impl := &RepositoryImpl{ - ctx: datacontext.InternalContextRef(ctxp.OCMContext()), - access: acc, - } - return repocpi.NewRepository(impl, "OCM repo[Simple]") -} - -func (r *RepositoryImpl) Close() error { - return r.access.Close() -} - -func (r *RepositoryImpl) IsReadOnly() bool { - return r.access.IsReadOnly() -} - -func (r *RepositoryImpl) SetReadOnly() { - r.access.SetReadOnly() -} - -func (r *RepositoryImpl) SetBridge(base repocpi.RepositoryBridge) { - r.bridge = base - r.nonref = repocpi.NewNoneRefRepositoryView(base) -} - -func (r *RepositoryImpl) GetContext() cpi.Context { - return r.ctx -} - -func (r *RepositoryImpl) GetSpecification() cpi.RepositorySpec { - if p, ok := r.access.(RepositorySpecProvider); ok { - return p.GetSpecification() - } - return NewRepositorySpec(r.access) -} - -func (r *RepositoryImpl) ComponentLister() cpi.ComponentLister { - return r.access.ComponentLister() -} - -func (r *RepositoryImpl) ExistsComponentVersion(name string, version string) (bool, error) { - return r.access.ExistsComponentVersion(name, version) -} - -func (r *RepositoryImpl) LookupComponent(name string) (*repocpi.ComponentAccessInfo, error) { - return newComponentAccess(r, name, true) -} diff --git a/pkg/contexts/ocm/repositories/virtual/type.go b/pkg/contexts/ocm/repositories/virtual/type.go deleted file mode 100644 index a802471b4..000000000 --- a/pkg/contexts/ocm/repositories/virtual/type.go +++ /dev/null @@ -1,35 +0,0 @@ -package virtual - -import ( - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - Type = "Virtual" - TypeV1 = Type + runtime.VersionSeparator + "v1" -) - -type RepositorySpec struct { - runtime.ObjectVersionedTypedObject - Access Access `json:"-"` -} - -func NewRepositorySpec(acc Access) *RepositorySpec { - return &RepositorySpec{ - ObjectVersionedTypedObject: runtime.NewVersionedTypedObject(Type), - Access: acc, - } -} - -func (r RepositorySpec) AsUniformSpec(context internal.Context) *cpi.UniformRepositorySpec { - return nil -} - -func (r *RepositorySpec) Repository(ctx cpi.Context, credentials credentials.Credentials) (internal.Repository, error) { - return NewRepository(ctx, r.Access), nil -} - -var _ cpi.RepositorySpec = (*RepositorySpec)(nil) diff --git a/pkg/contexts/ocm/resolver.go b/pkg/contexts/ocm/resolver.go deleted file mode 100644 index 29fd65e3c..000000000 --- a/pkg/contexts/ocm/resolver.go +++ /dev/null @@ -1,88 +0,0 @@ -package ocm - -import ( - "sync" - - "github.com/mandelsoft/goutils/errors" - "golang.org/x/exp/slices" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" -) - -type DedicatedResolver []ComponentVersionAccess - -var _ ComponentVersionResolver = (*DedicatedResolver)(nil) - -func NewDedicatedResolver(cv ...ComponentVersionAccess) ComponentVersionResolver { - return DedicatedResolver(slices.Clone(cv)) -} - -func (d DedicatedResolver) LookupComponentVersion(name string, version string) (ComponentVersionAccess, error) { - for _, cv := range d { - if cv.GetName() == name && cv.GetVersion() == version { - return cv.Dup() - } - } - return nil, nil -} - -//////////////////////////////////////////////////////////////////////////////// - -type CompoundResolver struct { - lock sync.RWMutex - resolvers []ComponentVersionResolver -} - -var _ ComponentVersionResolver = (*CompoundResolver)(nil) - -func NewCompoundResolver(res ...ComponentVersionResolver) ComponentVersionResolver { - for i := 0; i < len(res); i++ { - if res[i] == nil { - res = append(res[:i], res[i+1:]...) - i-- - } - } - if len(res) == 1 { - return res[0] - } - return &CompoundResolver{resolvers: res} -} - -func (c *CompoundResolver) LookupComponentVersion(name string, version string) (internal.ComponentVersionAccess, error) { - c.lock.RLock() - defer c.lock.RUnlock() - for _, r := range c.resolvers { - if r == nil { - continue - } - cv, err := r.LookupComponentVersion(name, version) - if err == nil && cv != nil { - return cv, nil - } - if !errors.IsErrNotFoundKind(err, KIND_COMPONENTVERSION) && !errors.IsErrNotFoundKind(err, KIND_COMPONENT) { - return nil, err - } - } - return nil, errors.ErrNotFound(KIND_OCM_REFERENCE, common.NewNameVersion(name, version).String()) -} - -func (c *CompoundResolver) AddResolver(r ComponentVersionResolver) { - c.lock.Lock() - defer c.lock.Unlock() - c.resolvers = append(c.resolvers, r) -} - -//////////////////////////////////////////////////////////////////////////////// - -type MatchingResolver interface { - ComponentVersionResolver - ContextProvider - - AddRule(prefix string, spec RepositorySpec, prio ...int) - Finalize() error -} - -func NewMatchingResolver(ctx ContextProvider) MatchingResolver { - return internal.NewMatchingResolver(ctx.OCMContext()) -} diff --git a/pkg/contexts/ocm/selectors/accessors/accessors.go b/pkg/contexts/ocm/selectors/accessors/accessors.go deleted file mode 100644 index 1559f89d2..000000000 --- a/pkg/contexts/ocm/selectors/accessors/accessors.go +++ /dev/null @@ -1,56 +0,0 @@ -package accessors - -import ( - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/runtime" -) - -// ElementListAccessor provides generic access to list of elements. -type ElementListAccessor interface { - Len() int - Get(i int) ElementMetaAccessor -} - -// ElementMeta describes the access to common element meta data attributes. -type ElementMeta interface { - GetName() string - GetVersion() string - GetExtraIdentity() v1.Identity - GetLabels() v1.Labels - GetIdentityForContext(accessor ElementListAccessor) v1.Identity -} - -// ElementMetaAccessor provides generic access an elements meta information. -type ElementMetaAccessor interface { - GetMeta() ElementMeta -} - -// AccessSpec is the minimal interface for access spec attributes. -type AccessSpec interface { - runtime.VersionedTypedObject -} - -// ArtifactAccessor provides access to generic artifact information of an element. -type ArtifactAccessor interface { - ElementMetaAccessor - GetType() string - GetAccess() AccessSpec -} - -// ResourceAccessor provides access to resource attribute. -type ResourceAccessor interface { - ArtifactAccessor - GetRelation() v1.ResourceRelation - GetDigest() *v1.DigestSpec -} - -// SourceAccessor provides access to source attribute. -type SourceAccessor interface { - ArtifactAccessor -} - -// ReferenceAccessor provides access to source attribute. -type ReferenceAccessor interface { - ElementMetaAccessor - GetComponentName() string -} diff --git a/pkg/contexts/ocm/selectors/identity.go b/pkg/contexts/ocm/selectors/identity.go deleted file mode 100644 index 0ba701b84..000000000 --- a/pkg/contexts/ocm/selectors/identity.go +++ /dev/null @@ -1,195 +0,0 @@ -package selectors - -import ( - "regexp" - - "github.com/Masterminds/semver/v3" - - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/accessors" -) - -type IdentitySelector interface { - MatchIdentity(identity v1.Identity) bool -} - -type IdentitySelectorImpl struct { - IdentitySelector -} - -func (i *IdentitySelectorImpl) MatchResource(list accessors.ElementListAccessor, a accessors.ResourceAccessor) bool { - return i.MatchIdentity(a.GetMeta().GetIdentityForContext(list)) -} - -func (i *IdentitySelectorImpl) MatchSource(list accessors.ElementListAccessor, a accessors.SourceAccessor) bool { - return i.MatchIdentity(a.GetMeta().GetIdentityForContext(list)) -} - -func (i *IdentitySelectorImpl) MatchReference(list accessors.ElementListAccessor, a accessors.ReferenceAccessor) bool { - return i.MatchIdentity(a.GetMeta().GetIdentityForContext(list)) -} - -type IdentityErrorSelectorImpl struct { - ErrorSelectorBase - IdentitySelectorImpl -} - -func NewIdentityErrorSelectorImpl(s IdentitySelector, err error) *IdentityErrorSelectorImpl { - return &IdentityErrorSelectorImpl{NewErrorSelectorBase(err), IdentitySelectorImpl{s}} -} - -//////////////////////////////////////////////////////////////////////////////// - -type idattrs struct { - v1.Identity -} - -func (i *idattrs) MatchIdentity(identity v1.Identity) bool { - for n, v := range i.Identity { - if identity[n] != v { - return false - } - } - return true -} - -func IdentityAttributesByKeyPairs(extra ...string) *IdentitySelectorImpl { - return &IdentitySelectorImpl{&idattrs{v1.NewExtraIdentity(extra...)}} -} - -func IdentityAttributes(identity v1.Identity) *IdentitySelectorImpl { - return &IdentitySelectorImpl{&idattrs{identity}} -} - -//////////////////////////////////////////////////////////////////////////////// - -type id struct { - v1.Identity -} - -func (i *id) MatchIdentity(identity v1.Identity) bool { - if len(i.Identity) != len(identity) { - return false - } - for n, v := range i.Identity { - if identity[n] != v { - return false - } - } - return true -} - -func IdentityByKeyPairs(extra ...string) *IdentitySelectorImpl { - return &IdentitySelectorImpl{&id{v1.NewExtraIdentity(extra...)}} -} - -func Identity(identity v1.Identity) *IdentitySelectorImpl { - return &IdentitySelectorImpl{&id{identity}} -} - -//////////////////////////////////////////////////////////////////////////////// - -type num int - -func (i num) MatchIdentity(identity v1.Identity) bool { - return len(identity) == int(i) -} - -func NumberOfIdentityAttributes(n int) *IdentitySelectorImpl { - return &IdentitySelectorImpl{num(n)} -} - -//////////////////////////////////////////////////////////////////////////////// - -type idRegEx struct { - attr string - *regexp.Regexp -} - -func (c *idRegEx) MatchIdentity(identity v1.Identity) bool { - v, ok := identity[c.attr] - if !ok { - return false - } - return c.Regexp.MatchString(v) -} - -func IdentityAttrRegex(name, ex string) *IdentitySelectorImpl { - c, _ := regexp.Compile(ex) - return &IdentitySelectorImpl{&idRegEx{name, c}} -} - -//////////////////////////////////////////////////////////////////////////////// - -type Name string - -func (n Name) MatchIdentity(identity v1.Identity) bool { - return string(n) == identity[v1.SystemIdentityName] -} - -func (n Name) MatchResource(list accessors.ElementListAccessor, r accessors.ResourceAccessor) bool { - return string(n) == r.GetMeta().GetName() -} - -func (n Name) MatchSource(list accessors.ElementListAccessor, r accessors.SourceAccessor) bool { - return string(n) == r.GetMeta().GetName() -} - -func (n Name) MatchReference(list accessors.ElementListAccessor, r accessors.ReferenceAccessor) bool { - return string(n) == r.GetMeta().GetName() -} - -//////////////////////////////////////////////////////////////////////////////// - -type Version string - -func (v Version) MatchIdentity(identity v1.Identity) bool { - return string(v) == identity[v1.SystemIdentityVersion] -} - -func (v Version) MatchResource(list accessors.ElementListAccessor, r accessors.ResourceAccessor) bool { - return string(v) == r.GetMeta().GetVersion() -} - -func (v Version) MatchSource(list accessors.ElementListAccessor, r accessors.SourceAccessor) bool { - return string(v) == r.GetMeta().GetVersion() -} - -func (v Version) MatchReference(list accessors.ElementListAccessor, r accessors.ReferenceAccessor) bool { - return string(v) == r.GetMeta().GetVersion() -} - -//////////////////////////////////////////////////////////////////////////////// - -type semverConstraint struct { - *semver.Constraints -} - -func VersionConstraint(expr string) *semverConstraint { - c, _ := semver.NewConstraint(expr) - return &semverConstraint{c} -} - -func (v *semverConstraint) check(vers string) bool { - sv, _ := semver.NewVersion(vers) - if sv == nil { - return false - } - return v.Constraints.Check(sv) -} - -func (v *semverConstraint) MatchIdentity(identity v1.Identity) bool { - return v.check(identity[v1.SystemIdentityVersion]) -} - -func (v *semverConstraint) MatchResource(list accessors.ElementListAccessor, r accessors.ResourceAccessor) bool { - return v.check(r.GetMeta().GetVersion()) -} - -func (v *semverConstraint) MatchSource(list accessors.ElementListAccessor, r accessors.SourceAccessor) bool { - return v.check(r.GetMeta().GetVersion()) -} - -func (v *semverConstraint) MatchReference(list accessors.ElementListAccessor, r accessors.ReferenceAccessor) bool { - return v.check(r.GetMeta().GetVersion()) -} diff --git a/pkg/contexts/ocm/selectors/label.go b/pkg/contexts/ocm/selectors/label.go deleted file mode 100644 index dd3a7cbd5..000000000 --- a/pkg/contexts/ocm/selectors/label.go +++ /dev/null @@ -1,86 +0,0 @@ -package selectors - -import ( - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/accessors" -) - -func SelectLabels(labels v1.Labels, sel ...LabelSelector) ([]v1.Label, error) { - err := ValidateSelectors(sel...) - if err != nil { - return nil, err - } - return GetLabels(labels, sel...), nil -} - -func GetLabels(labels v1.Labels, sel ...LabelSelector) []v1.Label { - var result []v1.Label - for _, l := range labels { - for _, s := range sel { - if !s.MatchLabel(&l) { - continue - } - } - result = append(result, l) - } - return result -} - -//////////////////////////////////////////////////////////////////////////////// - -type LabelSelectorImpl struct { - LabelSelector -} - -func (i *LabelSelectorImpl) MatchResource(list accessors.ElementListAccessor, a accessors.ResourceAccessor) bool { - return len(GetLabels(a.GetMeta().GetLabels(), i)) > 0 -} - -func (i *LabelSelectorImpl) MatchSource(list accessors.ElementListAccessor, a accessors.SourceAccessor) bool { - return len(GetLabels(a.GetMeta().GetLabels(), i)) > 0 -} - -func (i *LabelSelectorImpl) MatchReference(list accessors.ElementListAccessor, a accessors.ReferenceAccessor) bool { - return len(GetLabels(a.GetMeta().GetLabels(), i)) > 0 -} - -type LabelErrPropSelectorImpl struct { - LabelSelectorImpl -} - -func (l *LabelErrPropSelectorImpl) GetError() error { - if e, ok := l.LabelSelector.(ErrorProvider); ok { - return e.GetError() - } - return nil -} - -type LabelErrorSelectorImpl struct { - ErrorSelectorBase - LabelSelectorImpl -} - -func NewLabelErrorSelectorImpl(s LabelSelector, err error) *LabelErrorSelectorImpl { - return &LabelErrorSelectorImpl{NewErrorSelectorBase(err), LabelSelectorImpl{s}} -} - -//////////////////////////////////////////////////////////////////////////////// - -type label []LabelSelector - -func (s label) MatchLabel(l *v1.Label) bool { - for _, n := range s { - if !n.MatchLabel(l) { - return false - } - } - return true -} - -func (s label) GetError() error { - return ValidateSubSelectors("and", []LabelSelector(s)...) -} - -func Label(sel ...LabelSelector) *LabelErrPropSelectorImpl { - return &LabelErrPropSelectorImpl{LabelSelectorImpl{label(sel)}} -} diff --git a/pkg/contexts/ocm/selectors/labelsel/interface.go b/pkg/contexts/ocm/selectors/labelsel/interface.go deleted file mode 100644 index fe1b4103f..000000000 --- a/pkg/contexts/ocm/selectors/labelsel/interface.go +++ /dev/null @@ -1,178 +0,0 @@ -package labelsel - -import ( - "bytes" - "container/list" - "encoding/json" - "reflect" - - "github.com/mandelsoft/goutils/errors" - "github.com/mikefarah/yq/v4/pkg/yqlib" - "gopkg.in/op/go-logging.v1" - - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -func init() { - logging.SetLevel(logging.ERROR, "yq-lib") - yqlib.InitExpressionParser() -} - -type Selector = selectors.LabelSelector - -func Select(labels v1.Labels, sel ...Selector) ([]v1.Label, error) { - return selectors.SelectLabels(labels, sel...) -} - -func Get(labels v1.Labels, sel ...Selector) []v1.Label { - return selectors.GetLabels(labels, sel...) -} - -//////////////////////////////////////////////////////////////////////////////// - -type name string - -func (n name) MatchLabel(l *v1.Label) bool { - return string(n) == l.Name -} - -func Name(n string) *selectors.LabelSelectorImpl { - return &selectors.LabelSelectorImpl{name(n)} -} - -//////////////////////////////////////////////////////////////////////////////// - -type version string - -func (n version) MatchLabel(l *v1.Label) bool { - return string(n) == l.Version -} - -func Version(n string) *selectors.LabelSelectorImpl { - return &selectors.LabelSelectorImpl{version(n)} -} - -/////////////////////////////////////////////////////////////////////////////// - -type signed bool - -func (n signed) MatchLabel(l *v1.Label) bool { - return bool(n) == l.Signing -} - -func Signed(b ...bool) *selectors.LabelSelectorImpl { - return &selectors.LabelSelectorImpl{signed(utils.OptionalDefaultedBool(true, b...))} -} - -/////////////////////////////////////////////////////////////////////////////// - -type mergealgo string - -func (n mergealgo) MatchLabel(l *v1.Label) bool { - a := string(n) - if l.Merge == nil { - return a == "" - } - return a == l.Merge.Algorithm -} - -func MergeAlgo(algo string) *selectors.LabelSelectorImpl { - return &selectors.LabelSelectorImpl{mergealgo(algo)} -} - -//////////////////////////////////////////////////////////////////////////////// - -func AsStructure(value interface{}) (interface{}, error) { - var err error - - data, ok := value.([]byte) - if !ok { - data, err = json.Marshal(value) - if err != nil { - return nil, err - } - } - - var v interface{} - err = runtime.DefaultYAMLEncoding.Unmarshal(data, &v) - if err != nil { - return nil, err - } - return v, nil -} - -// Value matches a label by a label value. -// This selector should typically be combined with Name. -func Value(value interface{}) *selectors.LabelErrorSelectorImpl { - data, err := AsStructure(value) - return selectors.NewLabelErrorSelectorImpl(selectors.LabelSelectorFunc(func(l *v1.Label) bool { - var value interface{} - err := json.Unmarshal(l.Value, &value) - if err != nil { - return false - } - return reflect.DeepEqual(value, data) - }), err) -} - -//////////////////////////////////////////////////////////////////////////////// - -func YQParse(data []byte) (*yqlib.CandidateNode, error) { - decoder := yqlib.NewYamlDecoder(yqlib.YamlPreferences{}) - err := decoder.Init(bytes.NewReader(data)) - if err != nil { - return nil, err - } - return decoder.Decode() -} - -type yqeval struct { - expr *yqlib.ExpressionNode - value interface{} -} - -func (v *yqeval) MatchLabel(l *v1.Label) bool { - if v.expr == nil { - return false - } - in, err := YQParse(l.Value) - if err != nil { - return false - } - t := yqlib.NewDataTreeNavigator() - docs := list.New() - docs.PushBack(in) - context, err := t.GetMatchingNodes(yqlib.Context{MatchingNodes: docs}, v.expr) - if err != nil { - return false - } - if context.MatchingNodes.Len() != 1 { - return false - } - data, err := context.MatchingNodes.Front().Value.(*yqlib.CandidateNode).MarshalJSON() - if err != nil { - return false - } - var out interface{} - err = json.Unmarshal(data, &out) - if err != nil { - return false - } - return reflect.DeepEqual(v.value, out) -} - -// YQExpression matches a part of a label values described by a YQ expression. -// If value is a []byte, it is interpreted as JSON data, otherwise the value -// marshalled as JSON. -func YQExpression(expr string, value interface{}) *selectors.LabelErrorSelectorImpl { - var data interface{} - - node, err := yqlib.ExpressionParser.ParseExpression(expr) - if err == nil { - data, err = AsStructure(value) - } - return selectors.NewLabelErrorSelectorImpl(&yqeval{node, data}, errors.Wrapf(err, "YQExpression selector")) -} diff --git a/pkg/contexts/ocm/selectors/labelsel/operators.go b/pkg/contexts/ocm/selectors/labelsel/operators.go deleted file mode 100644 index 7b628ca38..000000000 --- a/pkg/contexts/ocm/selectors/labelsel/operators.go +++ /dev/null @@ -1,56 +0,0 @@ -package labelsel - -import ( - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors" -) - -var ( - _ selectors.ErrorProvider = (or)(nil) - _ selectors.ErrorProvider = (*not)(nil) -) - -//////////////////////////////////////////////////////////////////////////////// - -func And(sel ...Selector) *selectors.LabelErrPropSelectorImpl { - return selectors.Label(sel...) -} - -//////////////////////////////////////////////////////////////////////////////// - -type or []Selector - -func (a or) MatchLabel(l *v1.Label) bool { - for _, o := range a { - if o.MatchLabel(l) { - return true - } - } - return false -} - -func (a or) GetError() error { - return selectors.ValidateSubSelectors("or", []Selector(a)...) -} - -func Or(operands ...Selector) Selector { - return or(operands) -} - -//////////////////////////////////////////////////////////////////////////////// - -type not struct { - sel Selector -} - -func (a *not) MatchLabel(l *v1.Label) bool { - return !a.sel.MatchLabel(l) -} - -func (a *not) GetError() error { - return selectors.ValidateSubSelectors("not", a.sel) -} - -func Not(operand Selector) Selector { - return ¬{operand} -} diff --git a/pkg/contexts/ocm/selectors/refsel/element.go b/pkg/contexts/ocm/selectors/refsel/element.go deleted file mode 100644 index 1aa996474..000000000 --- a/pkg/contexts/ocm/selectors/refsel/element.go +++ /dev/null @@ -1,35 +0,0 @@ -package refsel - -import ( - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/labelsel" -) - -// Identity selectors - -func IdentityByKeyPairs(extras ...string) Selector { - return selectors.IdentityByKeyPairs(extras...) -} - -func Identity(id v1.Identity) Selector { - return selectors.Identity(id) -} - -func Name(n string) Selector { - return selectors.Name(n) -} - -func Version(n string) Selector { - return selectors.Version(n) -} - -// Label selectors - -func Label(sel ...selectors.LabelSelector) Selector { - return selectors.Label(sel...) -} - -func LabelName(n string) Selector { - return labelsel.Name(n) -} diff --git a/pkg/contexts/ocm/selectors/refsel/interface.go b/pkg/contexts/ocm/selectors/refsel/interface.go deleted file mode 100644 index 872222c5f..000000000 --- a/pkg/contexts/ocm/selectors/refsel/interface.go +++ /dev/null @@ -1,59 +0,0 @@ -package refsel - -import ( - "regexp" - - "github.com/gobwas/glob" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/accessors" -) - -type ( - Selector = selectors.ReferenceSelector - SelectorFunc = selectors.ReferenceSelectorFunc -) - -//////////////////////////////////////////////////////////////////////////////// - -type Component string - -func (c Component) MatchReference(list accessors.ElementListAccessor, ref accessors.ReferenceAccessor) bool { - return string(c) == ref.GetComponentName() -} - -//////////////////////////////////////////////////////////////////////////////// - -type compGlob struct { - glob.Glob -} - -func (c *compGlob) MatchReference(list accessors.ElementListAccessor, ref accessors.ReferenceAccessor) bool { - if c.Glob == nil { - return false - } - return c.Glob.Match(ref.GetComponentName()) -} - -func ComponentGlob(g string) Selector { - c, err := glob.Compile(g, '/') - return selectors.NewReferenceErrorSelectorImpl(&compGlob{c}, err) -} - -//////////////////////////////////////////////////////////////////////////////// - -type compRegEx struct { - *regexp.Regexp -} - -func (c *compRegEx) MatchReference(list accessors.ElementListAccessor, ref accessors.ReferenceAccessor) bool { - if c.Regexp == nil { - return false - } - return c.Regexp.MatchString(ref.GetComponentName()) -} - -func ComponentRegex(g string) selectors.ReferenceSelector { - c, err := regexp.Compile(g) - return selectors.NewReferenceErrorSelectorImpl(&compRegEx{c}, err) -} diff --git a/pkg/contexts/ocm/selectors/refsel/operators.go b/pkg/contexts/ocm/selectors/refsel/operators.go deleted file mode 100644 index 08ce91320..000000000 --- a/pkg/contexts/ocm/selectors/refsel/operators.go +++ /dev/null @@ -1,72 +0,0 @@ -package refsel - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/accessors" -) - -var ( - _ selectors.ErrorProvider = (and)(nil) - _ selectors.ErrorProvider = (or)(nil) - _ selectors.ErrorProvider = (*not)(nil) -) - -//////////////////////////////////////////////////////////////////////////////// - -type and []Selector - -func (a and) MatchReference(list accessors.ElementListAccessor, ref accessors.ReferenceAccessor) bool { - for _, o := range a { - if !o.MatchReference(list, ref) { - return false - } - } - return true -} - -func (a and) GetError() error { - return selectors.ValidateSubSelectors("and", []Selector(a)...) -} - -func And(operands ...Selector) Selector { - return and(operands) -} - -//////////////////////////////////////////////////////////////////////////////// - -type or []Selector - -func (a or) MatchReference(list accessors.ElementListAccessor, ref accessors.ReferenceAccessor) bool { - for _, o := range a { - if o.MatchReference(list, ref) { - return true - } - } - return false -} - -func (a or) GetError() error { - return selectors.ValidateSubSelectors("or", []Selector(a)...) -} - -func Or(operands ...Selector) Selector { - return or(operands) -} - -//////////////////////////////////////////////////////////////////////////////// - -type not struct { - Selector -} - -func (a *not) MatchReference(list accessors.ElementListAccessor, ref accessors.ReferenceAccessor) bool { - return !a.Selector.MatchReference(list, ref) -} - -func (a *not) GetError() error { - return selectors.ValidateSubSelectors("not", a.Selector) -} - -func Not(operand Selector) Selector { - return ¬{operand} -} diff --git a/pkg/contexts/ocm/selectors/rscsel/artifact.go b/pkg/contexts/ocm/selectors/rscsel/artifact.go deleted file mode 100644 index 68107f4ea..000000000 --- a/pkg/contexts/ocm/selectors/rscsel/artifact.go +++ /dev/null @@ -1,15 +0,0 @@ -package rscsel - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors" -) - -// Artifact selectors - -func ArtifactType(n string) Selector { - return selectors.ArtifactType(n) -} - -func AccessKind(n string) Selector { - return selectors.AccessKind(n) -} diff --git a/pkg/contexts/ocm/selectors/rscsel/element.go b/pkg/contexts/ocm/selectors/rscsel/element.go deleted file mode 100644 index b2383ced8..000000000 --- a/pkg/contexts/ocm/selectors/rscsel/element.go +++ /dev/null @@ -1,39 +0,0 @@ -package rscsel - -import ( - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/labelsel" -) - -// Identity selectors - -func IdentityByKeyPairs(extras ...string) Selector { - return selectors.IdentityByKeyPairs(extras...) -} - -func Identity(id v1.Identity) Selector { - return selectors.Identity(id) -} - -func Name(n string) Selector { - return selectors.Name(n) -} - -func Version(n string) Selector { - return selectors.Version(n) -} - -func VersionConstraint(expr string) Selector { - return selectors.VersionConstraint(expr) -} - -// Label selectors - -func Label(sel ...selectors.LabelSelector) Selector { - return selectors.Label(sel...) -} - -func LabelName(n string) Selector { - return labelsel.Name(n) -} diff --git a/pkg/contexts/ocm/selectors/rscsel/interface.go b/pkg/contexts/ocm/selectors/rscsel/interface.go deleted file mode 100644 index c783de7d1..000000000 --- a/pkg/contexts/ocm/selectors/rscsel/interface.go +++ /dev/null @@ -1,40 +0,0 @@ -package rscsel - -import ( - "runtime" - - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/extraid" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/accessors" -) - -type ( - Selector = selectors.ResourceSelector - SelectorFunc = selectors.ResourceSelectorFunc -) - -//////////////////////////////////////////////////////////////////////////////// - -type Relation v1.ResourceRelation - -func (r Relation) MatchResource(list accessors.ElementListAccessor, res accessors.ResourceAccessor) bool { - return v1.ResourceRelation(r) == res.GetRelation() -} - -var ( - Local = Relation(v1.LocalRelation) - External = Relation(v1.ExternalRelation) -) - -//////////////////////////////////////////////////////////////////////////////// - -func Executable(name string) Selector { - return SelectorFunc(func(list accessors.ElementListAccessor, a accessors.ResourceAccessor) bool { - extra := a.GetMeta().GetExtraIdentity() - return a.GetMeta().GetName() == name && a.GetType() == resourcetypes.EXECUTABLE && extra != nil && - extra[extraid.ExecutableOperatingSystem] == runtime.GOOS && - extra[extraid.ExecutableArchitecture] == runtime.GOARCH - }) -} diff --git a/pkg/contexts/ocm/selectors/rscsel/operators.go b/pkg/contexts/ocm/selectors/rscsel/operators.go deleted file mode 100644 index c69120170..000000000 --- a/pkg/contexts/ocm/selectors/rscsel/operators.go +++ /dev/null @@ -1,72 +0,0 @@ -package rscsel - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/accessors" -) - -var ( - _ selectors.ErrorProvider = (and)(nil) - _ selectors.ErrorProvider = (or)(nil) - _ selectors.ErrorProvider = (*not)(nil) -) - -//////////////////////////////////////////////////////////////////////////////// - -type and []Selector - -func (a and) MatchResource(list accessors.ElementListAccessor, e accessors.ResourceAccessor) bool { - for _, o := range a { - if !o.MatchResource(list, e) { - return false - } - } - return true -} - -func (a and) GetError() error { - return selectors.ValidateSubSelectors("and", []Selector(a)...) -} - -func And(operands ...Selector) Selector { - return and(operands) -} - -//////////////////////////////////////////////////////////////////////////////// - -type or []Selector - -func (a or) MatchResource(list accessors.ElementListAccessor, e accessors.ResourceAccessor) bool { - for _, o := range a { - if o.MatchResource(list, e) { - return true - } - } - return false -} - -func (a or) GetError() error { - return selectors.ValidateSubSelectors("or", []Selector(a)...) -} - -func Or(operands ...Selector) Selector { - return or(operands) -} - -//////////////////////////////////////////////////////////////////////////////// - -type not struct { - Selector -} - -func (a *not) MatchResource(list accessors.ElementListAccessor, e accessors.ResourceAccessor) bool { - return !a.Selector.MatchResource(list, e) -} - -func (a *not) GetError() error { - return selectors.ValidateSubSelectors("not", a.Selector) -} - -func Not(operand Selector) Selector { - return ¬{operand} -} diff --git a/pkg/contexts/ocm/selectors/srcsel/artifact.go b/pkg/contexts/ocm/selectors/srcsel/artifact.go deleted file mode 100644 index ebb2d7023..000000000 --- a/pkg/contexts/ocm/selectors/srcsel/artifact.go +++ /dev/null @@ -1,15 +0,0 @@ -package srcsel - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors" -) - -// Artifact selectors - -func ArtifactType(n string) Selector { - return selectors.ArtifactType(n) -} - -func AccessKind(n string) Selector { - return selectors.AccessKind(n) -} diff --git a/pkg/contexts/ocm/selectors/srcsel/element.go b/pkg/contexts/ocm/selectors/srcsel/element.go deleted file mode 100644 index e0e26a799..000000000 --- a/pkg/contexts/ocm/selectors/srcsel/element.go +++ /dev/null @@ -1,39 +0,0 @@ -package srcsel - -import ( - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/labelsel" -) - -// Identity selectors - -func IdentityByKeyPairs(extras ...string) Selector { - return selectors.IdentityByKeyPairs(extras...) -} - -func Identity(id v1.Identity) Selector { - return selectors.Identity(id) -} - -func Name(n string) Selector { - return selectors.Name(n) -} - -func Version(n string) Selector { - return selectors.Version(n) -} - -func VersionConstraint(expr string) Selector { - return selectors.VersionConstraint(expr) -} - -// Label selectors - -func Label(sel ...selectors.LabelSelector) Selector { - return selectors.Label(sel...) -} - -func LabelName(n string) Selector { - return labelsel.Name(n) -} diff --git a/pkg/contexts/ocm/selectors/srcsel/interface.go b/pkg/contexts/ocm/selectors/srcsel/interface.go deleted file mode 100644 index 063d72383..000000000 --- a/pkg/contexts/ocm/selectors/srcsel/interface.go +++ /dev/null @@ -1,10 +0,0 @@ -package srcsel - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors" -) - -type ( - Selector = selectors.SourceSelector - SelectorFunc = selectors.SourceSelectorFunc -) diff --git a/pkg/contexts/ocm/selectors/srcsel/operators.go b/pkg/contexts/ocm/selectors/srcsel/operators.go deleted file mode 100644 index 5adc21390..000000000 --- a/pkg/contexts/ocm/selectors/srcsel/operators.go +++ /dev/null @@ -1,72 +0,0 @@ -package srcsel - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors" - "github.com/open-component-model/ocm/pkg/contexts/ocm/selectors/accessors" -) - -var ( - _ selectors.ErrorProvider = (and)(nil) - _ selectors.ErrorProvider = (or)(nil) - _ selectors.ErrorProvider = (*not)(nil) -) - -//////////////////////////////////////////////////////////////////////////////// - -type and []Selector - -func (a and) MatchSource(list accessors.ElementListAccessor, e accessors.SourceAccessor) bool { - for _, o := range a { - if !o.MatchSource(list, e) { - return false - } - } - return true -} - -func (a and) GetError() error { - return selectors.ValidateSubSelectors("and", []Selector(a)...) -} - -func And(operands ...Selector) Selector { - return and(operands) -} - -//////////////////////////////////////////////////////////////////////////////// - -type or []Selector - -func (a or) MatchSource(list accessors.ElementListAccessor, e accessors.SourceAccessor) bool { - for _, o := range a { - if o.MatchSource(list, e) { - return true - } - } - return false -} - -func (a or) GetError() error { - return selectors.ValidateSubSelectors("or", []Selector(a)...) -} - -func Or(operands ...Selector) Selector { - return or(operands) -} - -//////////////////////////////////////////////////////////////////////////////// - -type not struct { - Selector -} - -func (a *not) MatchReference(list accessors.ElementListAccessor, e accessors.SourceAccessor) bool { - return !a.Selector.MatchSource(list, e) -} - -func (a *not) GetError() error { - return selectors.ValidateSubSelectors("not", a.Selector) -} - -func Not(operand Selector) Selector { - return ¬{operand} -} diff --git a/pkg/contexts/ocm/session.go b/pkg/contexts/ocm/session.go deleted file mode 100644 index d9a1cd8d7..000000000 --- a/pkg/contexts/ocm/session.go +++ /dev/null @@ -1,283 +0,0 @@ -package ocm - -import ( - "fmt" - "reflect" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" -) - -type ComponentContainer interface { - LookupComponent(name string) (ComponentAccess, error) -} -type ComponentVersionContainer interface { - LookupVersion(version string) (ComponentVersionAccess, error) -} - -type EvaluationResult struct { - Ref RefSpec - Repository Repository - Component ComponentAccess - Version ComponentVersionAccess -} - -type Session interface { - datacontext.Session - - Finalize(Finalizer) - LookupRepository(Context, RepositorySpec) (Repository, error) - LookupComponent(ComponentContainer, string) (ComponentAccess, error) - LookupComponentVersion(r ComponentVersionResolver, comp, vers string) (ComponentVersionAccess, error) - GetComponentVersion(ComponentVersionContainer, string) (ComponentVersionAccess, error) - EvaluateRef(ctx Context, ref string) (*EvaluationResult, error) - EvaluateComponentRef(ctx Context, ref string) (*EvaluationResult, error) - EvaluateVersionRef(ctx Context, ref string) (*EvaluationResult, error) - DetermineRepository(ctx Context, ref string) (Repository, UniformRepositorySpec, error) - DetermineRepositoryBySpec(ctx Context, spec *UniformRepositorySpec) (Repository, error) -} - -type session struct { - datacontext.Session - base datacontext.SessionBase - repositories *internal.RepositoryCache - components map[datacontext.ObjectKey]ComponentAccess - versions map[datacontext.ObjectKey]ComponentVersionAccess -} - -var _ Session = (*session)(nil) - -var key = reflect.TypeOf(session{}) - -func NewSession(s datacontext.Session) Session { - return datacontext.GetOrCreateSubSession(s, key, newSession).(Session) -} - -func newSession(s datacontext.SessionBase) datacontext.Session { - return &session{ - Session: s.Session(), - base: s, - repositories: internal.NewRepositoryCache(), - components: map[datacontext.ObjectKey]ComponentAccess{}, - versions: map[datacontext.ObjectKey]ComponentVersionAccess{}, - } -} - -type Finalizer interface { - Finalize() error -} - -type finalizer struct { - finalizer Finalizer -} - -func (f *finalizer) Close() error { - return f.finalizer.Finalize() -} - -func (s *session) Finalize(f Finalizer) { - s.Session.AddCloser(&finalizer{f}) -} - -func (s *session) Close() error { - return s.Session.Close() - // TODO: cleanup cache -} - -func (s *session) LookupRepository(ctx Context, spec RepositorySpec) (Repository, error) { - s.base.Lock() - defer s.base.Unlock() - if s.base.IsClosed() { - return nil, errors.ErrClosed("session") - } - - repo, cached, err := s.repositories.LookupRepository(ctx, spec) - if err != nil { - return nil, err - } - - // The repo's closer function should only be added once with add closer. Otherwise, it would be attempted to close - // an already closed object. Thus, we only want to add the repo's closer function, if it was not already cached - // (and thus, consequently already added to the sessions close). - // Session has to take over responsibility for open repositories for the Repository Cache because the objects - // opened during a session have to be closed in the reverse order they were opened (e.g. components opened based - // on a previously opened repository have to be closed first). - if !cached { - s.base.AddCloser(repo) - } - - return repo, nil -} - -func (s *session) LookupComponent(c ComponentContainer, name string) (ComponentAccess, error) { - key := datacontext.ObjectKey{ - Object: c, - Name: name, - } - s.base.Lock() - defer s.base.Unlock() - if s.base.IsClosed() { - return nil, errors.ErrClosed("session") - } - if ns := s.components[key]; ns != nil { - return ns, nil - } - ns, err := c.LookupComponent(name) - if err != nil { - return nil, err - } - s.components[key] = ns - s.base.AddCloser(ns) - return ns, err -} - -func (s *session) LookupComponentVersion(r ComponentVersionResolver, comp, vers string) (ComponentVersionAccess, error) { - if repo, ok := r.(Repository); ok { - component, err := s.LookupComponent(repo, comp) - if err != nil { - return nil, err - } - return s.GetComponentVersion(component, vers) - } - - key := datacontext.ObjectKey{ - Object: r, - Name: common.NewNameVersion(comp, vers).String(), - } - s.base.Lock() - defer s.base.Unlock() - if s.base.IsClosed() { - return nil, errors.ErrClosed("session") - } - if obj := s.versions[key]; obj != nil { - return obj, nil - } - - obj, err := r.LookupComponentVersion(comp, vers) - if err != nil { - return nil, err - } - - s.versions[key] = obj - s.base.AddCloser(obj) - return obj, err -} - -func (s *session) GetComponentVersion(c ComponentVersionContainer, version string) (ComponentVersionAccess, error) { - if c == nil { - return nil, fmt.Errorf("no container given") - } - key := datacontext.ObjectKey{ - Object: c, - Name: version, - } - s.base.Lock() - defer s.base.Unlock() - if s.base.IsClosed() { - return nil, errors.ErrClosed("session") - } - if obj := s.versions[key]; obj != nil { - return obj, nil - } - obj, err := c.LookupVersion(version) - if err != nil { - return nil, err - } - s.versions[key] = obj - s.base.AddCloser(obj) - return obj, err -} - -func (s *session) EvaluateVersionRef(ctx Context, ref string) (*EvaluationResult, error) { - evaluated, err := s.EvaluateComponentRef(ctx, ref) - if err != nil { - return nil, err - } - versions, err := evaluated.Component.ListVersions() - if err != nil { - return evaluated, errors.Wrapf(err, "%s[%s]: listing versions", ref, evaluated.Ref.Component) - } - if len(versions) != 1 { - return evaluated, errors.Wrapf(err, "%s {%s]: found %d components", ref, evaluated.Ref.Component, len(versions)) - } - evaluated.Version, err = s.GetComponentVersion(evaluated.Component, versions[0]) - if err != nil { - return evaluated, errors.Wrapf(err, "%s {%s:%s]: listing components", ref, evaluated.Ref.Component, versions[0]) - } - evaluated.Ref.Version = &versions[0] - return evaluated, nil -} - -func (s *session) EvaluateComponentRef(ctx Context, ref string) (*EvaluationResult, error) { - evaluated, err := s.EvaluateRef(ctx, ref) - if err != nil { - return evaluated, err - } - if evaluated.Component == nil { - lister := evaluated.Repository.ComponentLister() - if lister == nil { - return evaluated, errors.Newf("%s: no component specified", ref) - } - if n, err := lister.NumComponents(""); n != 1 { - if err != nil { - return evaluated, errors.Wrapf(err, "%s: listing components", ref) - } - // return evaluated, errors.Newf("%s: found %d components", ref, n) - return evaluated, nil // return repo ref - } - list, err := lister.GetComponents("", true) - if err != nil { - return evaluated, errors.Wrapf(err, "%s: listing components", ref) - } - evaluated.Ref.Component = list[0] - evaluated.Component, err = s.LookupComponent(evaluated.Repository, list[0]) - if err != nil { - return evaluated, errors.Wrapf(err, "%s: listing components", ref) - } - } - return evaluated, nil -} - -func (s *session) EvaluateRef(ctx Context, ref string) (*EvaluationResult, error) { - var err error - result := &EvaluationResult{} - result.Ref, err = ParseRef(ref) - if err != nil { - return nil, err - } - - result.Repository, err = s.DetermineRepositoryBySpec(ctx, &result.Ref.UniformRepositorySpec) - if err != nil { - return result, err - } - if result.Ref.Component != "" { - result.Component, err = s.LookupComponent(result.Repository, result.Ref.Component) - if err != nil { - return nil, err - } - if result.Ref.IsVersion() { - result.Version, err = s.GetComponentVersion(result.Component, *result.Ref.Version) - } - } - return result, err -} - -func (s *session) DetermineRepository(ctx Context, ref string) (Repository, UniformRepositorySpec, error) { - spec, err := ParseRepo(ref) - if err != nil { - return nil, spec, err - } - r, err := s.DetermineRepositoryBySpec(ctx, &spec) - return r, spec, err -} - -func (s *session) DetermineRepositoryBySpec(ctx Context, spec *UniformRepositorySpec) (Repository, error) { - rspec, err := ctx.MapUniformRepositorySpec(spec) - if err != nil { - return nil, err - } - return s.LookupRepository(ctx, rspec) -} diff --git a/pkg/contexts/ocm/signing/convenience.go b/pkg/contexts/ocm/signing/convenience.go deleted file mode 100644 index 542eb8c52..000000000 --- a/pkg/contexts/ocm/signing/convenience.go +++ /dev/null @@ -1,57 +0,0 @@ -package signing - -import ( - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" -) - -func SignComponentVersion(cv ocm.ComponentVersionAccess, name string, optlist ...Option) (*metav1.DigestSpec, error) { - var opts Options - - opts.Eval( - SignatureName(name), - Update(), - Recursive(), - VerifyDigests(), - ) - opts.Eval(optlist...) - - if opts.VerifySignature { - return nil, errors.Newf("impossible verification option set for signing") - } - if opts.Signer == nil { - opts.Signer = signingattr.Get(cv.GetContext()).GetSigner(rsa.Algorithm) - } - err := opts.Complete(cv.GetContext()) - if err != nil { - return nil, errors.Wrapf(err, "inconsistent options for signing") - } - return Apply(nil, nil, cv, &opts) -} - -func VerifyComponentVersion(cv ocm.ComponentVersionAccess, name string, optlist ...Option) (*metav1.DigestSpec, error) { - var opts Options - if len(cv.GetDescriptor().Signatures) == 1 && name == "" { - name = cv.GetDescriptor().Signatures[0].Name - } - - opts.Eval( - VerifyDigests(), - VerifySignature(name), - Recursive(), - ) - opts.Eval(optlist...) - - if opts.Signer != nil { - return nil, errors.Newf("impossible signer option set for verification") - } - err := opts.Complete(cv.GetContext()) - if err != nil { - return nil, errors.Wrapf(err, "inconsistent options for verification") - } - return Apply(nil, nil, cv, &opts) -} diff --git a/pkg/contexts/ocm/signing/handler_test.go b/pkg/contexts/ocm/signing/handler_test.go deleted file mode 100644 index eee10f6eb..000000000 --- a/pkg/contexts/ocm/signing/handler_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package signing_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "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/contexts/ocm/signing" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" - rsa_pss "github.com/open-component-model/ocm/pkg/signing/handlers/rsa-pss" - "github.com/open-component-model/ocm/pkg/signing/signutils" -) - -var _ = Describe("Simple signing handlers", func() { - ctx := ocm.DefaultContext() - - var cv ocm.ComponentVersionAccess - var pub signutils.GenericPublicKey - var priv signutils.GenericPrivateKey - - BeforeEach(func() { - priv, pub = Must2(rsa.CreateKeyPair()) - }) - - Context("", func() { - BeforeEach(func() { - cv = composition.NewComponentVersion(ctx, COMPONENTA, VERSION) - MustBeSuccessful(cv.SetResourceBlob(ocm.NewResourceMeta("blob", resourcetypes.PLAIN_TEXT, v1.LocalRelation), blobaccess.ForString(mime.MIME_TEXT, "test data"), "", nil)) - }) - - DescribeTable("rsa handlers", func(kind string) { - Must(signing.SignComponentVersion(cv, "signature", signing.PrivateKey("signature", priv))) - Must(signing.VerifyComponentVersion(cv, "signature", signing.PublicKey("signature", pub))) - }, - Entry("rsa", rsa.Algorithm), - Entry("rsapss", rsa_pss.Algorithm), - ) - }) - - Context("non-unique resources", func() { - BeforeEach(func() { - cv = composition.NewComponentVersion(ctx, COMPONENTA, VERSION) - - meta := ocm.NewResourceMeta("blob", resourcetypes.PLAIN_TEXT, v1.LocalRelation) - meta.Version = "v1" - meta.ExtraIdentity = map[string]string{} - MustBeSuccessful(cv.SetResourceBlob(meta, blobaccess.ForString(mime.MIME_TEXT, "test data"), "", nil)) - meta.Version = "v2" - MustBeSuccessful(cv.SetResourceBlob(meta, blobaccess.ForString(mime.MIME_TEXT, "other test data"), "", nil, ocm.TargetIndex(-1))) - }) - - It("signs without modification", func() { - Must(signing.SignComponentVersion(cv, "signature", signing.PrivateKey("signature", priv))) - cd := cv.GetDescriptor() - Expect(len(cd.Resources)).To(Equal(2)) - Expect(len(cd.Resources[0].ExtraIdentity)).To(Equal(0)) - Expect(len(cd.Resources[1].ExtraIdentity)).To(Equal(0)) - }) - }) -}) diff --git a/pkg/contexts/ocm/signing/options.go b/pkg/contexts/ocm/signing/options.go deleted file mode 100644 index 3dce22044..000000000 --- a/pkg/contexts/ocm/signing/options.go +++ /dev/null @@ -1,753 +0,0 @@ -package signing - -import ( - "crypto/x509/pkix" - "fmt" - "strings" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/generics" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/rootcertsattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/hasher/sha256" - "github.com/open-component-model/ocm/pkg/signing/signutils" - "github.com/open-component-model/ocm/pkg/utils" -) - -type Option interface { - ApplySigningOption(o *Options) -} - -//////////////////////////////////////////////////////////////////////////////// - -type printer struct { - printer common.Printer -} - -// Printer provides an option configuring a printer for a signing/verification -// operation. -func Printer(p common.Printer) Option { - return &printer{p} -} - -func (o *printer) ApplySigningOption(opts *Options) { - opts.Printer = o.printer -} - -//////////////////////////////////////////////////////////////////////////////// - -const ( - DIGESTMODE_LOCAL = "local" // (default) store nested digests locally in component descriptor - DIGESTMODE_TOP = "top" // store aggregated nested digests in signed component version -) - -type digestmode struct { - mode string -} - -// DigestMode provides an option configuring the digest mode for a signing/verification -// operation. Possible values are -// - DIGESTMODE_LOCAL(default) all digest information is store along with a component version -// - DIGESTMODE_TOP (experimental) all digest information is gathered for referenced component versions in the initially signed component version. -func DigestMode(name string) Option { - return &digestmode{name} -} - -func (o *digestmode) ApplySigningOption(opts *Options) { - opts.DigestMode = o.mode -} - -//////////////////////////////////////////////////////////////////////////////// - -type recursive struct { - flag bool -} - -// Recursive provides an option configuring recursion for a signing/verification -// operation. If enabled the operation will be done for all component versions -// in the reference graph. -func Recursive(flags ...bool) Option { - return &recursive{utils.GetOptionFlag(flags...)} -} - -func (o *recursive) ApplySigningOption(opts *Options) { - opts.Recursively = o.flag -} - -//////////////////////////////////////////////////////////////////////////////// - -type update struct { - flag bool -} - -// Update provides an option configuring the update mode for a signing/verification -// operation. Only if enabled, state changes will be persisted. -func Update(flags ...bool) Option { - return &update{utils.GetOptionFlag(flags...)} -} - -func (o *update) ApplySigningOption(opts *Options) { - opts.Update = o.flag -} - -//////////////////////////////////////////////////////////////////////////////// - -type verify struct { - flag bool -} - -// VerifyDigests provides an option requesting signature verification for a -// signing/verification operation. -func VerifyDigests(flags ...bool) Option { - return &verify{utils.GetOptionFlag(flags...)} -} - -func (o *verify) ApplySigningOption(opts *Options) { - opts.Verify = o.flag -} - -//////////////////////////////////////////////////////////////////////////////// - -type signer struct { - algo string - signer signing.Signer - name string -} - -// Sign provides an option requesting signing for a dedicated name and signer for a -// signing operation. -func Sign(h signing.Signer, name string) Option { - return &signer{"", h, name} -} - -// Signer provides an option requesting to use a dedicated signer for a -// signing/verification operation. -func Signer(h signing.Signer) Option { - return &signer{"", h, ""} -} - -// SignByAlgo provides an option requesting signing with a signing algorithm -// for a signing operation. The effective signer is taken from -// the signer registry provided by the OCM context. -func SignByAlgo(algo string, name string) Option { - return &signer{algo, nil, name} -} - -// SignerByAlgo provides an option requesting to use a dedicated signer by -// algorithm for a signing operation. The effective signer is taken from -// the signer registry provided by the OCM context. -func SignerByAlgo(algo string) Option { - return &signer{algo, nil, ""} -} - -func (o *signer) ApplySigningOption(opts *Options) { - n := strings.TrimSpace(o.name) - if n != "" { - opts.SignatureNames = append([]string{n}, opts.SignatureNames...) - } - opts.SignAlgo = o.algo - opts.Signer = o.signer -} - -//////////////////////////////////////////////////////////////////////////////// - -type hasher struct { - algo string - hasher signing.Hasher -} - -// Hash provides an option requesting hashing with a dedicated hasher for a -// signing/hash operation. -func Hash(h signing.Hasher) Option { - return &hasher{"", h} -} - -// HashByAlgo provides an option requesting to use a dedicated hasher by name -// for a signing/hash operation. The effective hasher is taken from -// the hasher registry provided by the OCM context. -func HashByAlgo(algo string) Option { - return &hasher{algo, nil} -} - -func (o *hasher) ApplySigningOption(opts *Options) { - opts.HashAlgo = o.algo - opts.Hasher = o.hasher -} - -//////////////////////////////////////////////////////////////////////////////// - -type verifier struct { - name string -} - -// VerifySignature provides an option requesting verification for dedicated -// signature names for a signing/verification operation. If no name is specified -// the names are taken from the component version. -func VerifySignature(names ...string) Option { - name := "" - for _, n := range names { - n = strings.TrimSpace(n) - if n != "" { - name = n - break - } - } - return &verifier{name} -} - -func (o *verifier) ApplySigningOption(opts *Options) { - opts.VerifySignature = true - if o.name != "" { - opts.SignatureNames = append(opts.SignatureNames, o.name) - } -} - -//////////////////////////////////////////////////////////////////////////////// - -type resolver struct { - resolver []ocm.ComponentVersionResolver -} - -// Resolver provides an option requesting to use a dedicated component version -// resolver for a signing/verification operation. It is used to resolve -// references in component versions. -func Resolver(h ...ocm.ComponentVersionResolver) Option { - return &resolver{h} -} - -func (o *resolver) ApplySigningOption(opts *Options) { - opts.Resolver = ocm.NewCompoundResolver(append([]ocm.ComponentVersionResolver{opts.Resolver}, o.resolver...)...) -} - -//////////////////////////////////////////////////////////////////////////////// - -type skip struct { - skip map[string]bool -} - -// SkipAccessTypes provides an option to declare dedicated resource types -// which should be excluded from digesting. This is a legacy options, -// required only for the handling of older component version not yet -// completely configured with resource digests. The content of resources with -// the given types will be marked as not signature relevant. -func SkipAccessTypes(names ...string) Option { - m := map[string]bool{} - for _, n := range names { - m[n] = true - } - return &skip{m} -} - -func (o *skip) ApplySigningOption(opts *Options) { - if len(o.skip) > 0 { - if opts.SkipAccessTypes == nil { - opts.SkipAccessTypes = map[string]bool{} - } - for k, v := range o.skip { - opts.SkipAccessTypes[k] = v - } - } -} - -//////////////////////////////////////////////////////////////////////////////// - -type registry struct { - registry signing.Registry -} - -// Registry provides an option requesting to use a dedicated signing registry -// for a signing/verification operation. It is used to lookup -// signers, verifiers, hashers and signing public/private keys by name. -func Registry(h signing.Registry) Option { - return ®istry{h} -} - -func (o *registry) ApplySigningOption(opts *Options) { - opts.Registry = o.registry -} - -//////////////////////////////////////////////////////////////////////////////// - -type signame struct { - name string - reset bool -} - -// SignatureName provides an option requesting to use dedicated signature names -// for a signing/verification operation. -func SignatureName(name string, reset ...bool) Option { - return &signame{name, utils.Optional(reset...)} -} - -func (o *signame) ApplySigningOption(opts *Options) { - if o.reset { - opts.SignatureNames = nil - } - if o.name != "" { - opts.SignatureNames = append(opts.SignatureNames, o.name) - } -} - -//////////////////////////////////////////////////////////////////////////////// - -type issuer struct { - issuer pkix.Name - name string - err error -} - -// Issuer provides an option requesting to use a dedicated issuer name -// for a signing operation. -func Issuer(is string) Option { - dn, err := signutils.ParseDN(is) - if err != nil { - return &issuer{err: err} - } - return &issuer{issuer: *dn} -} - -func IssuerFor(name string, is string) Option { - dn, err := signutils.ParseDN(is) - if err != nil { - return &issuer{err: err} - } - return PKIXIssuerFor(name, *dn) -} - -// PKIXIssuer provides an option requesting to use a dedicated issuer name -// for a signing operation. -func PKIXIssuer(is pkix.Name) Option { - return &issuer{issuer: is} -} - -func PKIXIssuerFor(name string, is pkix.Name) Option { - return &issuer{issuer: is, name: name} -} - -func (o *issuer) ApplySigningOption(opts *Options) { - if o.name != "" { - if opts.Keys == nil { - opts.Keys = signing.NewKeyRegistry() - } - opts.Keys.RegisterIssuer(o.name, generics.Pointer(o.issuer)) - } else { - opts.Issuer = generics.Pointer(o.issuer) - } -} - -//////////////////////////////////////////////////////////////////////////////// - -type rootcerts struct { - pool signutils.GenericCertificatePool -} - -// RootCertificates provides an option requesting to dedicated root certificates -// for a signing/verification operation using certificates. -func RootCertificates(pool signutils.GenericCertificatePool) Option { - return &rootcerts{pool} -} - -func (o *rootcerts) ApplySigningOption(opts *Options) { - opts.RootCerts = o.pool -} - -//////////////////////////////////////////////////////////////////////////////// - -type privkey struct { - name string - key interface{} -} - -// PrivateKey provides an option requesting to use a dedicated private key -// for a dedicated signature name for a signing operation. -func PrivateKey(name string, key interface{}) Option { - return &privkey{name, key} -} - -func (o *privkey) ApplySigningOption(opts *Options) { - if o.key == nil { - return - } - if opts.Keys == nil { - opts.Keys = signing.NewKeyRegistry() - } - opts.Keys.RegisterPrivateKey(o.name, o.key) -} - -//////////////////////////////////////////////////////////////////////////////// - -type pubkey struct { - name string - key interface{} -} - -// PublicKey provides an option requesting to use a dedicated public key -// for a dedicated signature name for a verification operation. -func PublicKey(name string, key interface{}) Option { - return &pubkey{name, key} -} - -func (o *pubkey) ApplySigningOption(opts *Options) { - if o.key == nil { - return - } - if opts.Keys == nil { - opts.Keys = signing.NewKeyRegistry() - } - opts.Keys.RegisterPublicKey(o.name, o.key) -} - -//////////////////////////////////////////////////////////////////////////////// - -type tsaOpt struct { - url string - use *bool -} - -// UseTSA enables the usage of a timestamp server authority. -func UseTSA(flag ...bool) Option { - return &tsaOpt{use: utils.BoolP(utils.GetOptionFlag(flag...))} -} - -// TSAUrl selects the TSA server URL to use, if TSA mode is enabled. -func TSAUrl(url string) Option { - return &tsaOpt{url: url} -} - -func (o *tsaOpt) ApplySigningOption(opts *Options) { - if o.url != "" { - opts.TSAUrl = o.url - } - if o.use != nil { - opts.UseTSA = *o.use - } -} - -//////////////////////////////////////////////////////////////////////////////// - -type Options struct { - Printer common.Printer - Update bool - Recursively bool - DigestMode string - Verify bool - SignAlgo string - Signer signing.Signer - Issuer *pkix.Name - VerifySignature bool - RootCerts signutils.GenericCertificatePool - HashAlgo string - Hasher signing.Hasher - Keys signing.KeyRegistry - Registry signing.Registry - Resolver ocm.ComponentVersionResolver - SkipAccessTypes map[string]bool - SignatureNames []string - NormalizationAlgo string - Keyless bool - TSAUrl string - UseTSA bool - - effectiveRegistry signing.Registry -} - -var _ Option = (*Options)(nil) - -func NewOptions(list ...Option) *Options { - return (&Options{}).Eval(list...) -} - -func (opts *Options) Eval(list ...Option) *Options { - for _, o := range list { - o.ApplySigningOption(opts) - } - return opts -} - -func (o *Options) ApplySigningOption(opts *Options) { - if o.Printer != nil { - opts.Printer = o.Printer - } - if o.Keys != nil { - opts.Keys = o.Keys - } - if o.Signer != nil { - opts.Signer = o.Signer - } - if o.DigestMode != "" { - opts.DigestMode = o.DigestMode - } - if o.VerifySignature { - opts.VerifySignature = o.VerifySignature - } - if o.Hasher != nil { - opts.Hasher = o.Hasher - } - if o.Registry != nil { - opts.Registry = o.Registry - } - if o.Resolver != nil { - opts.Resolver = o.Resolver - } - if len(o.SignatureNames) != 0 { - opts.SignatureNames = o.SignatureNames - } - if o.SkipAccessTypes != nil { - if opts.SkipAccessTypes == nil { - opts.SkipAccessTypes = map[string]bool{} - } - for k, v := range o.SkipAccessTypes { - opts.SkipAccessTypes[k] = v - } - } - if o.Issuer != nil { - opts.Issuer = o.Issuer - } - opts.Recursively = o.Recursively - opts.Update = o.Update - opts.Verify = o.Verify - opts.Keyless = o.Keyless - if o.NormalizationAlgo != "" { - opts.NormalizationAlgo = o.NormalizationAlgo - } - if o.TSAUrl != "" { - opts.TSAUrl = o.TSAUrl - } - if o.UseTSA { - opts.UseTSA = o.UseTSA - } -} - -// Complete takes either nil, an ocm.ContextProvider or a signing.Registry. -// To be compatible with an older version the type has been changed to interface -// to support multiple variants. -func (o *Options) Complete(ctx interface{}) error { - var reg signing.Registry - - if ctx == nil { - ctx = ocm.DefaultContext() - } - - var ocmctx ocm.Context - - switch t := ctx.(type) { - case ocm.ContextProvider: - ocmctx = t.OCMContext() - reg = signingattr.Get(ocmctx) - case signing.Registry: - reg = t - ocmctx = ocm.DefaultContext() - default: - return fmt.Errorf("context argument (%T) is invalid", ctx) - } - - o.Printer = common.AssurePrinter(o.Printer) - - if o.Registry == nil { - o.Registry = reg - } - - o.effectiveRegistry = o.Registry - if o.Keys != nil && (o.Keys.HasKeys() || o.Keys.HasIssuers()) { - o.effectiveRegistry = signing.RegistryWithPreferredKeys(o.Registry, o.Keys) - } - - certs := rootcertsattr.Get(ocmctx) - if o.RootCerts == nil && certs.HasRootCertificates() { - o.RootCerts = certs.GetRootCertPool(true) - } - - if o.RootCerts != nil { - // check root certificates - pool, err := signutils.GetCertPool(o.RootCerts, false) - if err != nil { - return err - } - o.RootCerts = pool - } - - if o.SkipAccessTypes == nil { - o.SkipAccessTypes = map[string]bool{} - } - - if o.Signer == nil && o.SignAlgo != "" { - o.Signer = o.Registry.GetSigner(o.SignAlgo) - if o.Signer == nil { - return errors.ErrUnknown(compdesc.KIND_SIGN_ALGORITHM, o.SignAlgo) - } - } - if o.Signer != nil { - if len(o.SignatureNames) == 0 { - return errors.Newf("signature name required for signing") - } - priv, err := o.PrivateKey() - if err != nil { - return err - } - if priv == nil && !o.Keyless { - return errors.ErrNotFound(compdesc.KIND_PRIVATE_KEY, o.SignatureNames[0]) - } - if o.DigestMode == "" { - o.DigestMode = DIGESTMODE_LOCAL - } - } - if !o.Keyless { - if o.Signer != nil && !o.VerifySignature { - if pub := o.PublicKey(o.SignatureName()); pub != nil { - o.VerifySignature = true - if err := o.checkCert(pub, o.IssuerFor(o.SignatureName())); err != nil { - return fmt.Errorf("public key not valid: %w", err) - } - } - } else if o.VerifySignature { - for _, n := range o.SignatureNames { - pub := o.PublicKey(n) - // don't check for public key here, anymore, - // because the key might be provided via certificate together with - // the signature. An early failure is therefore not possible anymore. - if pub != nil { - if err := o.checkCert(pub, o.IssuerFor(n)); err != nil { - return fmt.Errorf("public key not valid: %w", err) - } - } - } - } - } - if o.NormalizationAlgo == "" { - o.NormalizationAlgo = compdesc.JsonNormalisationV1 - } - - if o.Hasher == nil && o.HashAlgo != "" { - o.Hasher = o.Registry.GetHasher(o.HashAlgo) - if o.Hasher == nil { - return errors.ErrUnknown(compdesc.KIND_HASH_ALGORITHM, o.HashAlgo) - } - } - if o.Hasher == nil { - o.Hasher = o.Registry.GetHasher(sha256.Algorithm) - } - return nil -} - -func (o *Options) checkCert(data interface{}, name *pkix.Name) error { - cert, pool, err := signutils.GetCertificate(data, false) - if err != nil { - return nil - } - err = signing.VerifyCertDN(pool, o.RootCerts, name, cert) - if err != nil { - if name != nil { - return errors.Wrapf(err, "issuer [%s]", name) - } - return err - } - return nil -} - -func (o *Options) DoUpdate() bool { - return o.Update || o.DoSign() -} - -func (o *Options) DoSign() bool { - return o.Signer != nil && len(o.SignatureNames) > 0 -} - -func (o *Options) StoreLocally() bool { - return o.DigestMode == DIGESTMODE_LOCAL -} - -func (o *Options) DoVerify() bool { - return o.VerifySignature -} - -func (o *Options) SignatureName() string { - if len(o.SignatureNames) > 0 { - return o.SignatureNames[0] - } - return "" -} - -func (o *Options) GetIssuer() *pkix.Name { - if o.Issuer != nil { - return o.Issuer - } - if o.effectiveRegistry != nil { - return o.effectiveRegistry.GetIssuer(o.SignatureName()) - } - return nil -} - -func (o *Options) IssuerFor(name string) *pkix.Name { - if o.Issuer != nil && name == o.SignatureName() { - return o.Issuer - } - if o.effectiveRegistry != nil { - return o.effectiveRegistry.GetIssuer(name) - } - return nil -} - -func (o *Options) SignatureConfigured(name string) bool { - for _, n := range o.SignatureNames { - if n == name { - return true - } - } - return false -} - -func (o *Options) PublicKey(sig string) signutils.GenericPublicKey { - return o.effectiveRegistry.GetPublicKey(sig) -} - -func (o *Options) PrivateKey() (signutils.GenericPrivateKey, error) { - return signing.ResolvePrivateKey(o.effectiveRegistry, o.SignatureName()) -} - -func (o *Options) EffectiveTSAUrl() string { - if o.UseTSA { - if o.TSAUrl != "" { - return o.TSAUrl - } - return o.effectiveRegistry.TSAUrl() - } - return "" -} - -func (o *Options) Dup() *Options { - opts := *o - return &opts -} - -func (o *Options) Nested() *Options { - opts := o.Dup() - opts.VerifySignature = false // TODO: may be we want a mode to verify signature if present - if !opts.Recursively { - opts.Update = opts.DoUpdate() && opts.DigestMode == DIGESTMODE_LOCAL - opts.Signer = nil - } - opts.Printer = opts.Printer.AddGap(" ") - return opts -} - -func (o *Options) StopRecursion() *Options { - opts := *o - opts.Recursively = false - opts.Signer = nil - opts.Update = false - return &opts -} - -func (o *Options) WithDigestMode(mode string) *Options { - if mode == "" || o.DigestMode == mode { - return o - } - opts := *o - opts.DigestMode = mode - return &opts -} diff --git a/pkg/contexts/ocm/signing/options_test.go b/pkg/contexts/ocm/signing/options_test.go deleted file mode 100644 index e4459887e..000000000 --- a/pkg/contexts/ocm/signing/options_test.go +++ /dev/null @@ -1,98 +0,0 @@ -package signing_test - -import ( - "crypto/x509" - "crypto/x509/pkix" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/signing" - - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" - "github.com/open-component-model/ocm/pkg/signing/signutils" -) - -const NAME = "mandelsoft" - -var _ = Describe("options", func() { - defer GinkgoRecover() - - capriv, capub, err := rsa.Handler{}.CreateKeyPair() - Expect(err).To(Succeed()) - - spec := &signutils.Specification{ - RootCAs: nil, - IsCA: true, - PublicKey: capub, - CAPrivateKey: capriv, - CAChain: nil, - Subject: pkix.Name{ - CommonName: "ca-authority", - }, - Usages: signutils.Usages{x509.ExtKeyUsageCodeSigning}, - Validity: 10 * time.Hour, - NotBefore: nil, - } - - ca, _, err := signutils.CreateCertificate(spec) - Expect(err).To(Succeed()) - - priv, pub, err := rsa.Handler{}.CreateKeyPair() - Expect(err).To(Succeed()) - - spec.Subject = pkix.Name{ - CommonName: NAME, - StreetAddress: []string{"some street 21"}, - } - spec.RootCAs = ca - spec.CAChain = ca - spec.PublicKey = pub - spec.IsCA = false - - cert, _, err := signutils.CreateCertificate(spec) - Expect(err).To(Succeed()) - - pool := x509.NewCertPool() - pool.AddCert(ca) - - It("verifies options for verification", func() { - opts := NewOptions( - RootCertificates(pool), - VerifySignature(NAME), - PrivateKey(NAME, priv), - PublicKey(NAME, cert), - ) - Expect(opts.Complete(ocm.DefaultContext())).To(Succeed()) - }) - - It("fails for options for verification without root cert", func() { - opts := NewOptions( - VerifySignature(NAME), - PrivateKey(NAME, priv), - PublicKey(NAME, cert), - ) - Expect(opts.Complete(ocm.DefaultContext())).To(HaveOccurred()) - }) - - It("succeeds for options for signing with verification with root cert", func() { - opts := NewOptions( - RootCertificates(pool), - Sign(signing.DefaultRegistry().GetSigner(rsa.Algorithm), NAME), - PrivateKey(NAME, priv), - PublicKey(NAME, cert), - ) - Expect(opts.Complete(ocm.DefaultContext())).To(Succeed()) - }) - - It("fails for options for signing with verification without root cert", func() { - opts := NewOptions( - Sign(signing.DefaultRegistry().GetSigner(rsa.Algorithm), NAME), - PrivateKey(NAME, priv), - PublicKey(NAME, cert), - ) - Expect(opts.Complete(ocm.DefaultContext())).To(HaveOccurred()) - }) -}) diff --git a/pkg/contexts/ocm/signing/signing_test.go b/pkg/contexts/ocm/signing/signing_test.go deleted file mode 100644 index 343d20659..000000000 --- a/pkg/contexts/ocm/signing/signing_test.go +++ /dev/null @@ -1,1430 +0,0 @@ -package signing_test - -import ( - "crypto/x509/pkix" - "fmt" - "time" - - . "github.com/mandelsoft/goutils/finalizer" - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/oci/testhelper" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/signing" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/testhelper" - . "github.com/open-component-model/ocm/pkg/env/builder" - - "github.com/open-component-model/ocm/pkg/common" - "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/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/none" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/compositionmodeattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" - "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/contexts/ocm/repositories/composition" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/contexts/ocm/signing/signingtest" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" - "github.com/open-component-model/ocm/pkg/signing/hasher/sha256" - "github.com/open-component-model/ocm/pkg/signing/hasher/sha512" - "github.com/open-component-model/ocm/pkg/signing/signutils" -) - -var DefaultContext = ocm.New() - -const ( - ARCH = "/tmp/ctf" - PROVIDER = "mandelsoft" - VERSION = "v1" - COMPONENTA = "github.com/mandelsoft/test" - COMPONENTB = "github.com/mandelsoft/ref" - COMPONENTC = "github.com/mandelsoft/ref2" - COMPONENTD = "github.com/mandelsoft/top" - OUT = "/tmp/res" - OCIPATH = "/tmp/oci" - OCIHOST = "alias" -) - -const ( - SIGNATURE = "test" - SIGNATURE2 = "second" - SIGN_ALGO = rsa.Algorithm -) - -var _ = Describe("access method", func() { - var env *Builder - - BeforeEach(func() { - env = NewBuilder(signingtest.ModifiableTestData()) - env.RSAKeyPair(SIGNATURE, SIGNATURE2) - }) - - AfterEach(func() { - env.Cleanup() - }) - - /* TODO: add complex example from component cli - Context("compatibility", func() { - It("verifies older hash types (sha256[digest type] instead of SHA-256(crypto type))", func() { - session := datacontext.NewSession() - defer session.Close() - - env.ReadRSAKeyPair(SIGNATURE, "/testdata/compat") - cv, err := comparch.Open(env.OCMContext(), accessobj.ACC_READONLY, "/testdata/compat/component-archive", 0, env) - Expect(err).To(Succeed()) - session.AddCloser(cv) - opts := NewOptions( - VerifySignature(SIGNATURE), - VerifyDigests(), - ) - Expect(opts.Complete(signingattr.Get(DefaultContext))).To(Succeed()) - - dig, err := Apply(nil, nil, cv, opts) - Expect(err).To(Succeed()) - Expect(dig.Value).To(Equal("b06e4c1a68274b876661f9fbf1f100526d289745f6ee847bfef702007b5b14cf")) - Expect(dig.HashAlgorithm).To(Equal(sha256.Algorithm)) - }) - It("resigns with older hash types", func() { - session := datacontext.NewSession() - defer session.Close() - - env.ReadRSAKeyPair(SIGNATURE, "/testdata/compat") - cv, err := comparch.Open(env.OCMContext(), accessobj.ACC_READONLY, "/testdata/compat/component-archive", 0, env) - Expect(err).To(Succeed()) - session.AddCloser(cv) - opts := NewOptions( - Sign(signing.DefaultHandlerRegistry().GetSigner(SIGN_ALGO), SIGNATURE), - VerifySignature(SIGNATURE), - VerifyDigests(), - ) - Expect(opts.Complete(signingattr.Get(DefaultContext))).To(Succeed()) - - dig, err := Apply(nil, nil, cv, opts) - Expect(err).To(Succeed()) - Expect(dig.Value).To(Equal("b06e4c1a68274b876661f9fbf1f100526d289745f6ee847bfef702007b5b14cf")) - Expect(dig.HashAlgorithm).To(Equal(sha256.Algorithm)) - }) - }) - */ - - Context("special cases", func() { - DescribeTable("handles none access", func(mode string) { - env.ModificationOptions(ocm.SkipDigest()) - env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { - env.Component(COMPONENTA, func() { - env.Version(VERSION, func() { - env.Provider(PROVIDER) - TestDataResource(env) - env.Resource("value", "", resourcetypes.OCI_IMAGE, metav1.LocalRelation, func() { - env.Access( - none.New(), - ) - }) - }) - }) - }) - - session := datacontext.NewSession() - defer session.Close() - - src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) - archcloser := session.AddCloser(src) - resolver := ocm.NewCompoundResolver(src) - - cv := Must(resolver.LookupComponentVersion(COMPONENTA, VERSION)) - closer := session.AddCloser(cv) - - digest := "123d48879559d16965a54eba9a3e845709770f4f0be984ec8db2f507aa78f338" - - pr, buf := common.NewBufferedPrinter() - // key taken from signing attr - dig := Must(SignComponentVersion(cv, SIGNATURE, SignerByAlgo(SIGN_ALGO), Resolver(resolver), DigestMode(mode), Printer(pr))) - Expect(closer.Close()).To(Succeed()) - Expect(archcloser.Close()).To(Succeed()) - Expect(dig.Value).To(StringEqualWithContext(digest)) - - src = Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) - session.AddCloser(src) - cv = Must(src.LookupComponentVersion(COMPONENTA, VERSION)) - session.AddCloser(cv) - Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(digest)) - - Expect(buf.String()).To(StringEqualTrimmedWithContext(` -applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v1]... - resource 0: "name"="testdata": digest SHA-256:${D_TESTDATA}[genericBlobDigest/v1] -`, Digests)) - - CheckResourceDigests(cv.GetDescriptor(), map[string]*metav1.DigestSpec{ - "testdata": DS_TESTDATA, - }) - //////// - - dig = Must(VerifyComponentVersion(cv, SIGNATURE, Resolver(resolver), Printer(pr))) - Expect(dig.Value).To(Equal(digest)) - }, - Entry(DIGESTMODE_TOP, DIGESTMODE_TOP), - Entry(DIGESTMODE_LOCAL, DIGESTMODE_LOCAL), - ) - }) - - Context("valid", func() { - digestA := "01de99400030e8336020059a435cea4e7fe8f21aad4faf619da882134b85569d" - digestB := "5f416ec59629d6af91287e2ba13c6360339b6a0acf624af2abd2a810ce4aefce" - - localDigests := Substitutions{ - "D_COMPA": digestA, - "D_COMPB": digestB, - } - BeforeEach(func() { - FakeOCIRepo(env, OCIPATH, OCIHOST) - - env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { - OCIManifest1(env) - OCIManifest2(env) - }) - - env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { - env.Component(COMPONENTA, func() { - env.Version(VERSION, func() { - env.Provider(PROVIDER) - TestDataResource(env) - env.Resource("value", "", resourcetypes.OCI_IMAGE, metav1.LocalRelation, func() { - env.Access( - ociartifact.New(oci.StandardOCIRef(OCIHOST+".alias", OCINAMESPACE, OCIVERSION)), - ) - env.Label("transportByValue", true) - }) - env.Resource("ref", "", resourcetypes.OCI_IMAGE, metav1.LocalRelation, func() { - env.Access( - ociartifact.New(oci.StandardOCIRef(OCIHOST+".alias", OCINAMESPACE2, OCIVERSION)), - ) - }) - }) - }) - env.Component(COMPONENTB, func() { - env.Version(VERSION, func() { - env.Provider(PROVIDER) - OtherDataResource(env) - env.Reference("ref", COMPONENTA, VERSION) - }) - }) - }) - }) - - DescribeTable("sign flat version", func(mode string) { - session := datacontext.NewSession() - defer session.Close() - - src, err := ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env) - Expect(err).To(Succeed()) - archcloser := session.AddCloser(src) - resolver := ocm.NewCompoundResolver(src) - - cv, err := resolver.LookupComponentVersion(COMPONENTA, VERSION) - Expect(err).To(Succeed()) - closer := session.AddCloser(cv) - - opts := NewOptions( - Sign(signingattr.Get(env.OCMContext()).GetSigner(SIGN_ALGO), SIGNATURE), - Resolver(resolver), - Update(), VerifyDigests(), - ) - Expect(opts.Complete(env)).To(Succeed()) - - pr, buf := common.NewBufferedPrinter() - dig, err := Apply(pr, nil, cv, opts) - Expect(err).To(Succeed()) - Expect(closer.Close()).To(Succeed()) - Expect(archcloser.Close()).To(Succeed()) - Expect(dig.Value).To(StringEqualWithContext(digestA)) - - src, err = ctf.Open(env, accessobj.ACC_READONLY, ARCH, 0, env) - Expect(err).To(Succeed()) - session.AddCloser(src) - cv, err = src.LookupComponentVersion(COMPONENTA, VERSION) - Expect(err).To(Succeed()) - session.AddCloser(cv) - Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(digestA)) - - Expect(buf.String()).To(StringEqualTrimmedWithContext(` -applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v1]... - resource 0: "name"="testdata": digest SHA-256:${D_TESTDATA}[genericBlobDigest/v1] - resource 1: "name"="value": digest SHA-256:${D_OCIMANIFEST1}[ociArtifactDigest/v1] - resource 2: "name"="ref": digest SHA-256:${D_OCIMANIFEST2}[ociArtifactDigest/v1] -`, Digests, OCIDigests)) - //////// - - opts = NewOptions( - DigestMode(mode), - VerifySignature(SIGNATURE), - Resolver(resolver), - Update(), VerifyDigests(), - ) - Expect(opts.Complete(env)).To(Succeed()) - - dig, err = Apply(nil, nil, cv, opts) - Expect(err).To(Succeed()) - Expect(dig.Value).To(Equal(digestA)) - - CheckResourceDigests(cv.GetDescriptor(), map[string]*metav1.DigestSpec{ - "testdata": DS_TESTDATA, - "value": DS_OCIMANIFEST1, - "ref": DS_OCIMANIFEST2, - }) - - cv.GetDescriptor().Resources[0].Digest.Value = "010ff2fb242a5dee4220f2cb0e6a519891fb67f2f828a6cab4ef8894633b1f50" // some wrong value - _, err = Apply(nil, nil, cv, opts) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(StringEqualWithContext("github.com/mandelsoft/test:v1: calculated resource digest (SHA-256:" + D_TESTDATA + "[genericBlobDigest/v1]) mismatches existing digest (SHA-256:010ff2fb242a5dee4220f2cb0e6a519891fb67f2f828a6cab4ef8894633b1f50[genericBlobDigest/v1]) for testdata:v1 (Local blob sha256:810ff2fb242a5dee4220f2cb0e6a519891fb67f2f828a6cab4ef8894633b1f50[])")) - // Reset to original to avoid write back in readonly mode - cv.GetDescriptor().Resources[0].Digest.Value = D_TESTDATA - - cv.GetDescriptor().Signatures[0].Digest.Value = "0ae7ab0c1578d1292922b2a3884833c380a57df2cc7dfab7213ee051b092edc3" - _, err = Apply(nil, nil, cv, opts) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(StringEqualTrimmedWithContext("github.com/mandelsoft/test:v1: signature digest (0ae7ab0c1578d1292922b2a3884833c380a57df2cc7dfab7213ee051b092edc3) does not match found digest (" + digestA + ")")) - // Reset to original to avoid write back in readonly mode - cv.GetDescriptor().Signatures[0].Digest.Value = digestA - }, - Entry(DIGESTMODE_TOP, DIGESTMODE_TOP), - Entry(DIGESTMODE_LOCAL, DIGESTMODE_LOCAL), - ) - - DescribeTable("sign flat version with generic verification", func(mode string) { - session := datacontext.NewSession() - defer session.Close() - - src, err := ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env) - Expect(err).To(Succeed()) - archcloser := session.AddCloser(src) - resolver := ocm.NewCompoundResolver(src) - - cv, err := resolver.LookupComponentVersion(COMPONENTA, VERSION) - Expect(err).To(Succeed()) - closer := session.AddCloser(cv) - - opts := NewOptions( - DigestMode(mode), - Sign(signingattr.Get(env.OCMContext()).GetSigner(SIGN_ALGO), SIGNATURE), - Resolver(resolver), - Update(), VerifyDigests(), - ) - Expect(opts.Complete(env)).To(Succeed()) - dig, err := Apply(nil, nil, cv, opts) - Expect(err).To(Succeed()) - closer.Close() - archcloser.Close() - Expect(dig.Value).To(StringEqualWithContext(digestA)) - - src, err = ctf.Open(env, accessobj.ACC_READONLY, ARCH, 0, env) - Expect(err).To(Succeed()) - session.AddCloser(src) - cv, err = src.LookupComponentVersion(COMPONENTA, VERSION) - Expect(err).To(Succeed()) - session.AddCloser(cv) - Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(digestA)) - - //////// - - opts = NewOptions( - VerifySignature(), - Resolver(resolver), - Update(), VerifyDigests(), - ) - Expect(opts.Complete(env)).To(Succeed()) - - dig, err = Apply(nil, nil, cv, opts) - Expect(err).To(Succeed()) - Expect(dig.Value).To(Equal(digestA)) - }, - Entry(DIGESTMODE_TOP, DIGESTMODE_TOP), - Entry(DIGESTMODE_LOCAL, DIGESTMODE_LOCAL), - ) - - DescribeTable("sign deep version", func(mode string) { - session := datacontext.NewSession() - defer session.Close() - - src, err := ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env) - Expect(err).To(Succeed()) - archcloser := session.AddCloser(src) - resolver := ocm.NewCompoundResolver(src) - - cv, err := resolver.LookupComponentVersion(COMPONENTB, VERSION) - Expect(err).To(Succeed()) - closer := session.AddCloser(cv) - - opts := NewOptions( - DigestMode(mode), - Sign(signingattr.Get(env.OCMContext()).GetSigner(SIGN_ALGO), SIGNATURE), - Resolver(resolver), - Update(), VerifyDigests(), - ) - Expect(opts.Complete(env)).To(Succeed()) - - pr, buf := common.NewBufferedPrinter() - dig, err := Apply(pr, nil, cv, opts) - Expect(err).To(Succeed()) - closer.Close() - archcloser.Close() - Expect(dig.Value).To(StringEqualWithContext(digestB)) - - src, err = ctf.Open(env, accessobj.ACC_READONLY, ARCH, 0, env) - Expect(err).To(Succeed()) - session.AddCloser(src) - cv, err = src.LookupComponentVersion(COMPONENTB, VERSION) - Expect(err).To(Succeed()) - session.AddCloser(cv) - Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(digestB)) - - if mode == DIGESTMODE_TOP { - Expect(cv.GetDescriptor().NestedDigests.String()).To(StringEqualTrimmedWithContext(` -github.com/mandelsoft/test:v1: SHA-256:${D_COMPA}[jsonNormalisation/v1] - testdata:v1[]: SHA-256:${D_TESTDATA}[genericBlobDigest/v1] - value:v1[]: SHA-256:${D_OCIMANIFEST1}[ociArtifactDigest/v1] - ref:v1[]: SHA-256:${D_OCIMANIFEST2}[ociArtifactDigest/v1] -`, localDigests, Digests, OCIDigests)) - } else { - Expect(cv.GetDescriptor().NestedDigests).To(BeNil()) - } - - cva, err := src.LookupComponentVersion(COMPONENTA, VERSION) - Expect(err).To(Succeed()) - session.AddCloser(cva) - Expect(len(cva.GetDescriptor().Signatures)).To(Equal(0)) - - Expect(buf.String()).To(StringEqualTrimmedWithContext(` -applying to version "github.com/mandelsoft/ref:v1"[github.com/mandelsoft/ref:v1]... - no digest found for "github.com/mandelsoft/test:v1" - applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/ref:v1]... - resource 0: "name"="testdata": digest SHA-256:${D_TESTDATA}[genericBlobDigest/v1] - resource 1: "name"="value": digest SHA-256:${D_OCIMANIFEST1}[ociArtifactDigest/v1] - resource 2: "name"="ref": digest SHA-256:${D_OCIMANIFEST2}[ociArtifactDigest/v1] - reference 0: github.com/mandelsoft/test:v1: digest SHA-256:${D_COMPA}[jsonNormalisation/v1] - resource 0: "name"="otherdata": digest SHA-256:${D_OTHERDATA}[genericBlobDigest/v1] -`, localDigests, Digests, OCIDigests)) - //////// - - opts = NewOptions( - VerifySignature(SIGNATURE), - Resolver(src), - VerifyDigests(), - ) - Expect(opts.Complete(env)).To(Succeed()) - - dig, err = Apply(nil, nil, cv, opts) - Expect(err).To(Succeed()) - Expect(dig.Value).To(Equal(digestB)) - }, - Entry(DIGESTMODE_TOP, DIGESTMODE_TOP), - Entry(DIGESTMODE_LOCAL, DIGESTMODE_LOCAL), - ) - - DescribeTable("fails generic verification", func(mode string) { - session := datacontext.NewSession() - defer session.Close() - - src, err := ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env) - Expect(err).To(Succeed()) - session.AddCloser(src) - resolver := ocm.NewCompoundResolver(src) - - cv, err := resolver.LookupComponentVersion(COMPONENTA, VERSION) - Expect(err).To(Succeed()) - session.AddCloser(cv) - - opts := NewOptions( - DigestMode(mode), - VerifySignature(), - Resolver(resolver), - Update(), VerifyDigests(), - ) - Expect(opts.Complete(env)).To(Succeed()) - - _, err = Apply(nil, nil, cv, opts) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(StringEqualWithContext("github.com/mandelsoft/test:v1: failed to determine signature info: no signature found")) - }, - Entry(DIGESTMODE_TOP, DIGESTMODE_TOP), - Entry(DIGESTMODE_LOCAL, DIGESTMODE_LOCAL), - ) - }) - - Context("invalid", func() { - BeforeEach(func() { - env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { - env.Component(COMPONENTB, func() { - env.Version(VERSION, func() { - env.Provider(PROVIDER) - OtherDataResource(env) - env.Reference("ref", COMPONENTA, VERSION) - }) - }) - }) - }) - - DescribeTable("fails signing version with unknown ref", func(mode string) { - session := datacontext.NewSession() - defer session.Close() - - src, err := ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env) - Expect(err).To(Succeed()) - session.AddCloser(src) - - opts := NewOptions( - DigestMode(mode), - Sign(signingattr.Get(env.OCMContext()).GetSigner(SIGN_ALGO), SIGNATURE), - Resolver(src), - Update(), VerifyDigests(), - ) - Expect(opts.Complete(env)).To(Succeed()) - - cv, err := src.LookupComponentVersion(COMPONENTB, VERSION) - Expect(err).To(Succeed()) - session.AddCloser(cv) - - _, err = Apply(nil, nil, cv, opts) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(StringEqualWithContext("github.com/mandelsoft/ref:v1: failed resolving component reference ref[github.com/mandelsoft/test:v1]: component version \"github.com/mandelsoft/test:v1\" not found: oci artifact \"v1\" not found in component-descriptors/github.com/mandelsoft/test")) - }, - Entry(DIGESTMODE_TOP, DIGESTMODE_TOP), - Entry(DIGESTMODE_LOCAL, DIGESTMODE_LOCAL), - ) - }) - - Context("legacy rhombus", func() { - D_DATAA := "8a835d52867572bdaf7da7fb35ee59ad45c3db2dacdeeca62178edd5d07ef08c" - D_DATAA512 := "47e63fa783ec370d83b84ce3de37a3de3fdd5cdc64d4fb21a530dce00fa1011e8dbc85f7509694a5875bf82e710ce00ac6bcd8e716741a7fc4c51a181b741920" - D_DATAB := "5f103fcedc97b81bfc1841447d164781ed0f6244ce20b26d7a8a7d5880156c33" - D_DATAB512 := "a9469fc2e9787c8496cf1526508ae86d4e855715ef6b8f7031bdc55759683762f1c330b94a4516dff23e32f19fb170cbcb53015f1ffc0d77624ee5c9a288a030" - D_DATAC := "90e06e32c46338db42d78d49fee035063d4b10e83cfbf0d1831e14527245da12" - D_DATAD := "5a5c3f681c2af10d682926a635a1dc9dfe7087d4fa3daf329bf0acad540911a9" - - DS_DATAA := TextResourceDigestSpec(D_DATAA) - DS_DATAB := TextResourceDigestSpec(D_DATAB) - DS_DATAC := TextResourceDigestSpec(D_DATAC) - DS_DATAD := TextResourceDigestSpec(D_DATAD) - - D_COMPA := "bdb62ce8299f10e230b91bc9a9bfdbf2d33147f205fcf736d802c7e1cec7b5e8" - D_COMPA512 := "7aa760f27b494814e56c44413afd7bc9d932df28918d63bea222be4bd2b6abd921225cca2140d6eb549418a75b8db2a32be1852012d77474657505f0ea57b34d" - // D_COMPA_HASHED := "0bf5d019bab058a392b6bcb2ae50c93a02f623da0a439b1bbbfd4b1f795fbd3aafe271e3b757fad06e9118f74b18c2b83c7443f86e0c04c4539196bad79c6380" - D_COMPB := "d1def1b60cc8b241451b0e3fccb705a9d99db188b72ec4548519017921700857" - D_COMPB512 := "08366761127c791e550d2082e34e68c8836739c68f018f969a46a17a6c13b529390303335ee0ae3cd938af9e0f31665427a1b45360622d864a5dbe053917a75d" - // D_COMPBR := "e47deeca35bc34116770a50a88954a0b028eb4e236d089b84e419c6d7ce15d97" - D_COMPC := "b376a7b440c0b1e506e54a790966119a8e229cf9226980b84c628d77ef06fc58" - D_COMPD := "64674d3e2843d36c603f44477e4cd66ee85fe1a91227bbcd271202429024ed61" - - localDigests := Substitutions{ - "D_DATAA": D_DATAA, - "D_DATAB": D_DATAB, - "D_DATAB512": D_DATAB512, - "D_DATAC": D_DATAC, - "D_DATAD": D_DATAD, - - "D_COMPA": D_COMPA, - "D_COMPB": D_COMPB, - // "D_COMPBR": D_COMPB, - "D_COMPC": D_COMPC, - "D_COMPD": D_COMPD, - "D_COMPB512": D_COMPB512, - } - - _, _, _, _ = DS_DATAA, DS_DATAB, DS_DATAC, DS_DATAD - - setup := func(opts ...ocm.ModificationOption) { - env.ModificationOptions(opts...) - env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { - env.Component(COMPONENTA, func() { - env.Version(VERSION, func() { - env.Provider(PROVIDER) - env.Resource("data_a", "", "PlainText", metav1.LocalRelation, func() { - env.BlobStringData(mime.MIME_TEXT, "testdata A") - }) - }) - }) - env.Component(COMPONENTB, func() { - env.Version(VERSION, func() { - env.Provider(PROVIDER) - env.Resource("data_b", "", "PlainText", metav1.LocalRelation, func() { - env.BlobStringData(mime.MIME_TEXT, "testdata B") - }) - env.Reference("ref", COMPONENTA, VERSION) - }) - }) - env.Component(COMPONENTC, func() { - env.Version(VERSION, func() { - env.Provider(PROVIDER) - env.Resource("data_c", "", "PlainText", metav1.LocalRelation, func() { - env.BlobStringData(mime.MIME_TEXT, "testdata C") - }) - env.Reference("ref", COMPONENTA, VERSION) - }) - }) - env.Component(COMPONENTD, func() { - env.Version(VERSION, func() { - env.Provider(PROVIDER) - env.Resource("data_d", "", "PlainText", metav1.LocalRelation, func() { - env.BlobStringData(mime.MIME_TEXT, "testdata D") - }) - env.Reference("refb", COMPONENTB, VERSION) - env.Reference("refc", COMPONENTC, VERSION) - }) - }) - }) - } - - DescribeTable("hashes unsigned", func(mode bool, c EntryCheck, mopts ...ocm.ModificationOption) { - var finalizer Finalizer - defer Defer(finalizer.Finalize) - - { - compositionmodeattr.Set(env.OCMContext(), mode) - setup(mopts...) - arch := finalizer.Nested() - src, err := ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env) - Expect(err).To(Succeed()) - arch.Close(src) - - resolver := ocm.NewCompoundResolver(src) - - log := HashComponent(resolver, COMPONENTD, D_COMPD, DigestMode(c.Mode())) - - Expect(log).To(StringEqualTrimmedWithContext(` -applying to version "github.com/mandelsoft/top:v1"[github.com/mandelsoft/top:v1]... - no digest found for "github.com/mandelsoft/ref:v1" - applying to version "github.com/mandelsoft/ref:v1"[github.com/mandelsoft/top:v1]... - no digest found for "github.com/mandelsoft/test:v1" - applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/top:v1]... - resource 0: "name"="data_a": digest SHA-256:${D_DATAA}[genericBlobDigest/v1] - reference 0: github.com/mandelsoft/test:v1: digest SHA-256:${D_COMPA}[jsonNormalisation/v1] - resource 0: "name"="data_b": digest SHA-256:${D_DATAB}[genericBlobDigest/v1] - reference 0: github.com/mandelsoft/ref:v1: digest SHA-256:${D_COMPB}[jsonNormalisation/v1] - no digest found for "github.com/mandelsoft/ref2:v1" - applying to version "github.com/mandelsoft/ref2:v1"[github.com/mandelsoft/top:v1]... - reference 0: github.com/mandelsoft/test:v1: digest SHA-256:${D_COMPA}[jsonNormalisation/v1] - resource 0: "name"="data_c": digest SHA-256:${D_DATAC}[genericBlobDigest/v1] - reference 1: github.com/mandelsoft/ref2:v1: digest SHA-256:${D_COMPC}[jsonNormalisation/v1] - resource 0: "name"="data_d": digest SHA-256:${D_DATAD}[genericBlobDigest/v1] -`, localDigests)) - MustBeSuccessful(arch.Finalize()) - } - - src, err := ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env) - Expect(err).To(Succeed()) - finalizer.Close(src) - cv, err := src.LookupComponentVersion(COMPONENTD, VERSION) - Expect(err).To(Succeed()) - finalizer.Close(cv) - Expect(len(cv.GetDescriptor().Signatures)).To(Equal(0)) - - c.Check1CheckD(cv, localDigests) - - Expect(cv.GetDescriptor().Resources[0].Digest).NotTo(BeNil()) - Expect(cv.GetDescriptor().Resources[0].Digest.String()).To(Equal("SHA-256:" + D_DATAD + "[genericBlobDigest/v1]")) - - cva, err := src.LookupComponentVersion(COMPONENTA, VERSION) - Expect(err).To(Succeed()) - sub := finalizer.Nested() - sub.Close(cva) - Expect(len(cva.GetDescriptor().Signatures)).To(Equal(0)) - - c.Check1CheckA(cva, DS_DATAA, mopts...) - - //////// - - VerifyHashes(src, COMPONENTD, D_COMPD) - - c.Check1Corrupt(cva, sub, cv) - - opts := NewOptions( - Resolver(src), - VerifyDigests(), - ) - Expect(opts.Complete(env)).To(Succeed()) - _, err = Apply(nil, nil, cv, opts) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("github.com/mandelsoft/top:v1: failed applying to component reference refb[github.com/mandelsoft/ref:v1]: github.com/mandelsoft/top:v1->github.com/mandelsoft/ref:v1: failed applying to component reference ref[github.com/mandelsoft/test:v1]: github.com/mandelsoft/top:v1->github.com/mandelsoft/ref:v1->github.com/mandelsoft/test:v1: calculated resource digest (SHA-256:" + D_DATAA + "[genericBlobDigest/v1]) mismatches existing digest (SHA-256:" + wrongDigest + "[genericBlobDigest/v1]) for data_a:v1 (Local blob sha256:" + D_DATAA + "[])")) - }, - Entry(DIGESTMODE_TOP, false, &EntryTop{}), - Entry(DIGESTMODE_LOCAL, false, &EntryLocal{}), - - Entry("legacy "+DIGESTMODE_TOP, false, &EntryTop{}, ocm.SkipDigest()), - Entry("legacy "+DIGESTMODE_LOCAL, false, &EntryLocal{}, ocm.SkipDigest()), - - Entry(DIGESTMODE_TOP+" with composition mode", true, &EntryTop{}), - Entry(DIGESTMODE_LOCAL+" with composition mode", true, &EntryLocal{}), - - Entry("legacy "+DIGESTMODE_TOP+" with composition mode", true, &EntryTop{}, ocm.SkipDigest()), - Entry("legacy "+DIGESTMODE_LOCAL+" with composition mode", true, &EntryLocal{}, ocm.SkipDigest()), - ) - - DescribeTable("signs unsigned", func(c EntryCheck, mopts ...ocm.ModificationOption) { - var finalizer Finalizer - defer Defer(finalizer.Finalize) - - { - setup(mopts...) - arch := finalizer.Nested() - src, err := ctf.Open(env, accessobj.ACC_WRITABLE, ARCH, 0, env) - Expect(err).To(Succeed()) - arch.Close(src) - - resolver := ocm.NewCompoundResolver(src) - - log := SignComponent(resolver, SIGNATURE, COMPONENTD, D_COMPD, DigestMode(c.Mode())) - - Expect(log).To(StringEqualTrimmedWithContext(` -applying to version "github.com/mandelsoft/top:v1"[github.com/mandelsoft/top:v1]... - no digest found for "github.com/mandelsoft/ref:v1" - applying to version "github.com/mandelsoft/ref:v1"[github.com/mandelsoft/top:v1]... - no digest found for "github.com/mandelsoft/test:v1" - applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/top:v1]... - resource 0: "name"="data_a": digest SHA-256:${D_DATAA}[genericBlobDigest/v1] - reference 0: github.com/mandelsoft/test:v1: digest SHA-256:${D_COMPA}[jsonNormalisation/v1] - resource 0: "name"="data_b": digest SHA-256:${D_DATAB}[genericBlobDigest/v1] - reference 0: github.com/mandelsoft/ref:v1: digest SHA-256:${D_COMPB}[jsonNormalisation/v1] - no digest found for "github.com/mandelsoft/ref2:v1" - applying to version "github.com/mandelsoft/ref2:v1"[github.com/mandelsoft/top:v1]... - reference 0: github.com/mandelsoft/test:v1: digest SHA-256:${D_COMPA}[jsonNormalisation/v1] - resource 0: "name"="data_c": digest SHA-256:${D_DATAC}[genericBlobDigest/v1] - reference 1: github.com/mandelsoft/ref2:v1: digest SHA-256:${D_COMPC}[jsonNormalisation/v1] - resource 0: "name"="data_d": digest SHA-256:${D_DATAD}[genericBlobDigest/v1] -`, localDigests)) - MustBeSuccessful(arch.Finalize()) - } - src, err := ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env) - Expect(err).To(Succeed()) - finalizer.Close(src) - cv, err := src.LookupComponentVersion(COMPONENTD, VERSION) - Expect(err).To(Succeed()) - finalizer.Close(cv) - Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(D_COMPD)) - - c.Check1CheckD(cv, localDigests) - c.Check2Ref(cv, "refb", D_COMPB) - c.Check2Ref(cv, "refc", D_COMPC) - - cva, err := src.LookupComponentVersion(COMPONENTA, VERSION) - Expect(err).To(Succeed()) - finalizer.Close(cva) - Expect(len(cva.GetDescriptor().Signatures)).To(Equal(0)) - - c.Check1CheckA(cva, DS_DATAA, mopts...) - //////// - - cvb := Must(src.LookupComponentVersion(COMPONENTB, VERSION)) - finalizer.Close(cvb) - Expect(len(cvb.GetDescriptor().Signatures)).To(Equal(0)) - c.Check2Ref(cvb, "ref", D_COMPA) - - cvc := Must(src.LookupComponentVersion(COMPONENTB, VERSION)) - finalizer.Close(cvc) - Expect(len(cvb.GetDescriptor().Signatures)).To(Equal(0)) - c.Check2Ref(cvb, "ref", D_COMPA) - - VerifyComponent(src, SIGNATURE, COMPONENTD, D_COMPD) - }, - Entry(DIGESTMODE_TOP, &EntryTop{}), - Entry(DIGESTMODE_LOCAL, &EntryLocal{}), - - Entry("legacy "+DIGESTMODE_TOP, &EntryTop{}, ocm.SkipDigest()), - Entry("legacy "+DIGESTMODE_LOCAL, &EntryLocal{}, ocm.SkipDigest()), - ) - - // D_COMPD_LEGACY := "342d30317bee13ec30d815122f23b19d9ee54a15ff8be1ec550c8072d5a6dba6" - // D_COMPB_HASHED := "af8a8324b7848fc5887c63e632402df99c729669889b4d2ae7efceb9f1c2341b81d8a18b82e994564d854422a544c3dffc7d64d8389c90ab7fad19a50bb75e31" - D_COMPB_HASHED := "6ef2fa650b73302f2f23543adf4588e18ec419c5604eab43dcbb7d4ef12a7e6ad0f5d872d34a9839d428861e22770973e0ca7316891f8b246cb0942d4fede3fc" - // DigestDFor512 := "64674d3e2843d36c603f44477e4cd66ee85fe1a91227bbcd271202429024ed61" - // DigestBFor512 := "af8a8324b7848fc5887c63e632402df99c729669889b4d2ae7efceb9f1c2341b81d8a18b82e994564d854422a544c3dffc7d64d8389c90ab7fad19a50bb75e31" - - DescribeTable("signs and rehashes presigned in top mode", (func(subst Substitutions, mopts ...ocm.ModificationOption) { - var finalizer Finalizer - defer Defer(finalizer.Finalize) - - setup(mopts...) - { - arch := finalizer.Nested() - src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) - arch.Close(src) - - resolver := ocm.NewCompoundResolver(src) - - log := SignComponent(resolver, SIGNATURE, COMPONENTB, subst["D_COMPB_X"], DigestMode(DIGESTMODE_TOP), HashByAlgo(sha512.Algorithm)) - Expect(log).To(StringEqualTrimmedWithContext(` -applying to version "github.com/mandelsoft/ref:v1"[github.com/mandelsoft/ref:v1]... - no digest found for "github.com/mandelsoft/test:v1" - applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/ref:v1]... - resource 0: "name"="data_a": digest SHA-512:${D_DATAA_X}[genericBlobDigest/v1] - reference 0: github.com/mandelsoft/test:v1: digest SHA-512:${D_COMPA_X}[jsonNormalisation/v1] - resource 0: "name"="data_b": digest ${HASH}:${D_DATAB_X}[genericBlobDigest/v1] - -`, MergeSubst(localDigests, subst))) - VerifyComponent(resolver, SIGNATURE, COMPONENTB, subst["D_COMPB_X"]) - log = SignComponent(resolver, SIGNATURE, COMPONENTD, subst["D_COMPD_X"], DigestMode(DIGESTMODE_TOP)) - - Expect(log).To(StringEqualTrimmedWithContext(` -applying to version "github.com/mandelsoft/top:v1"[github.com/mandelsoft/top:v1]... - no digest found for "github.com/mandelsoft/ref:v1" - applying to version "github.com/mandelsoft/ref:v1"[github.com/mandelsoft/top:v1]... - no digest found for "github.com/mandelsoft/test:v1" - applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/top:v1]... - resource 0: "name"="data_a": digest SHA-256:${D_DATAA}[genericBlobDigest/v1] - reference 0: github.com/mandelsoft/test:v1: digest SHA-256:${D_COMPA}[jsonNormalisation/v1] - resource 0: "name"="data_b": digest SHA-256:${D_DATAB}[genericBlobDigest/v1] - reference 0: github.com/mandelsoft/ref:v1: digest SHA-256:${D_COMPB}[jsonNormalisation/v1] - no digest found for "github.com/mandelsoft/ref2:v1" - applying to version "github.com/mandelsoft/ref2:v1"[github.com/mandelsoft/top:v1]... - reference 0: github.com/mandelsoft/test:v1: digest SHA-256:${D_COMPA}[jsonNormalisation/v1] - resource 0: "name"="data_c": digest SHA-256:${D_DATAC}[genericBlobDigest/v1] - reference 1: github.com/mandelsoft/ref2:v1: digest SHA-256:${D_COMPC}[jsonNormalisation/v1] - resource 0: "name"="data_d": digest SHA-256:${D_DATAD}[genericBlobDigest/v1] -`, MergeSubst(localDigests, subst))) - Defer(arch.Finalize) - } - - src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) - finalizer.Close(src) - cv := Must(src.LookupComponentVersion(COMPONENTD, VERSION)) - finalizer.Close(cv) - Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(subst["D_COMPD_X"])) - - Expect(cv.GetDescriptor().NestedDigests).NotTo(BeNil()) - Expect(cv.GetDescriptor().NestedDigests.String()).To(StringEqualTrimmedWithContext(` -github.com/mandelsoft/ref:v1: SHA-256:${D_COMPB}[jsonNormalisation/v1] - data_b:v1[]: SHA-256:${D_DATAB}[genericBlobDigest/v1] -github.com/mandelsoft/ref2:v1: SHA-256:${D_COMPC}[jsonNormalisation/v1] - data_c:v1[]: SHA-256:${D_DATAC}[genericBlobDigest/v1] -github.com/mandelsoft/test:v1: SHA-256:${D_COMPA}[jsonNormalisation/v1] - data_a:v1[]: SHA-256:${D_DATAA}[genericBlobDigest/v1] -`, MergeSubst(localDigests, subst))) - - cva, err := src.LookupComponentVersion(COMPONENTA, VERSION) - Expect(err).To(Succeed()) - finalizer.Close(cva) - Expect(len(cva.GetDescriptor().Signatures)).To(Equal(0)) - - //////// - - VerifyComponent(src, SIGNATURE, COMPONENTD, subst["D_COMPD_X"]) - }), - Entry("legacy", Substitutions{ - "HASH": "SHA-512", - "D_DATAA_X": D_DATAA512, - "D_DATAB_X": D_DATAB512, - "D_DATAB": D_DATAB, - "D_COMPA_X": D_COMPA512, - "D_COMPB_X": D_COMPB512, - "D_COMPD_X": D_COMPD, - "D_COMPB": D_COMPB, - }, ocm.SkipDigest()), - Entry("hashed", Substitutions{ - "HASH": "SHA-256", - "D_DATAA_X": D_DATAA512, - "D_DATAB_X": D_DATAB, - "D_COMPA_X": D_COMPA512, - "D_COMPB_X": D_COMPB_HASHED, - "D_COMPD_X": D_COMPD, - }), - ) - - DescribeTable("verifies after sub level signing", func(subst Substitutions, mopts ...ocm.ModificationOption) { - var finalizer Finalizer - defer Defer(finalizer.Finalize) - - setup(mopts...) - - digestD := D_COMPD - { - arch := finalizer.Nested() - src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) - arch.Close(src) - - resolver := ocm.NewCompoundResolver(src) - - fmt.Printf("SIGN D\n") - log := SignComponent(resolver, SIGNATURE, COMPONENTD, digestD, DigestMode(DIGESTMODE_TOP)) - - Expect(log).To(StringEqualTrimmedWithContext(` -applying to version "github.com/mandelsoft/top:v1"[github.com/mandelsoft/top:v1]... - no digest found for "github.com/mandelsoft/ref:v1" - applying to version "github.com/mandelsoft/ref:v1"[github.com/mandelsoft/top:v1]... - no digest found for "github.com/mandelsoft/test:v1" - applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/top:v1]... - resource 0: "name"="data_a": digest SHA-256:${D_DATAA}[genericBlobDigest/v1] - reference 0: github.com/mandelsoft/test:v1: digest SHA-256:${D_COMPA}[jsonNormalisation/v1] - resource 0: "name"="data_b": digest SHA-256:${D_DATAB}[genericBlobDigest/v1] - reference 0: github.com/mandelsoft/ref:v1: digest SHA-256:${D_COMPB}[jsonNormalisation/v1] - no digest found for "github.com/mandelsoft/ref2:v1" - applying to version "github.com/mandelsoft/ref2:v1"[github.com/mandelsoft/top:v1]... - reference 0: github.com/mandelsoft/test:v1: digest SHA-256:${D_COMPA}[jsonNormalisation/v1] - resource 0: "name"="data_c": digest SHA-256:${D_DATAC}[genericBlobDigest/v1] - reference 1: github.com/mandelsoft/ref2:v1: digest SHA-256:${D_COMPC}[jsonNormalisation/v1] - resource 0: "name"="data_d": digest SHA-256:${D_DATAD}[genericBlobDigest/v1] -`, MergeSubst(localDigests, subst))) - - fmt.Printf("SIGN B\n") - SignComponent(resolver, SIGNATURE, COMPONENTB, subst["D_COMPB_X"], HashByAlgo(sha512.Algorithm), DigestMode(DIGESTMODE_TOP)) - fmt.Printf("VERIFY B\n") - VerifyComponent(resolver, SIGNATURE, COMPONENTB, subst["D_COMPB_X"]) - - Defer(arch.Finalize) - } - - src, err := ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env) - Expect(err).To(Succeed()) - finalizer.Close(src) - cv, err := src.LookupComponentVersion(COMPONENTD, VERSION) - Expect(err).To(Succeed()) - finalizer.Close(cv) - Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(digestD)) - - Expect(cv.GetDescriptor().NestedDigests.String()).To(StringEqualTrimmedWithContext(` -github.com/mandelsoft/ref:v1: SHA-256:${D_COMPB}[jsonNormalisation/v1] - data_b:v1[]: SHA-256:${D_DATAB}[genericBlobDigest/v1] -github.com/mandelsoft/ref2:v1: SHA-256:${D_COMPC}[jsonNormalisation/v1] - data_c:v1[]: SHA-256:${D_DATAC}[genericBlobDigest/v1] -github.com/mandelsoft/test:v1: SHA-256:${D_COMPA}[jsonNormalisation/v1] - data_a:v1[]: SHA-256:${D_DATAA}[genericBlobDigest/v1] -`, MergeSubst(localDigests, subst))) - - cva, err := src.LookupComponentVersion(COMPONENTA, VERSION) - Expect(err).To(Succeed()) - finalizer.Close(cva) - Expect(len(cva.GetDescriptor().Signatures)).To(Equal(0)) - - //////// - fmt.Printf("VERIFY D\n") - VerifyComponent(src, SIGNATURE, COMPONENTD, digestD) - }, - Entry("legacy", Substitutions{ - "D_COMPB_X": D_COMPB512, - }, ocm.SkipDigest()), - Entry("hashed", Substitutions{ - "D_COMPB_X": D_COMPB_HASHED, - }), - ) - - DescribeTable("fixes digest mode", func(subst Substitutions, mopts ...ocm.ModificationOption) { - setup(mopts...) - - var finalizer Finalizer - defer Check(finalizer.Finalize) - - { // sign with mode local - src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) - finalizer.Close(src) - - resolver := ocm.NewCompoundResolver(src) - - fmt.Printf("SIGN B\n") - _ = SignComponent(resolver, SIGNATURE, COMPONENTB, D_COMPB, DigestMode(DIGESTMODE_LOCAL)) - VerifyComponent(src, SIGNATURE, COMPONENTB, D_COMPB) - Check(finalizer.Finalize) - } - { // check mode - src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) - finalizer.Close(src) - cv := Must(src.LookupComponentVersion(COMPONENTB, VERSION)) - finalizer.Close(cv) - Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(D_COMPB)) - Expect(cv.GetDescriptor().NestedDigests).To(BeNil()) - Expect(cv.GetDescriptor().References[0].Digest).NotTo(BeNil()) - Expect(GetDigestMode(cv.GetDescriptor())).To(Equal(DIGESTMODE_LOCAL)) - Check(finalizer.Finalize) - } - { // try resign with mode top - src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) - finalizer.Close(src) - - resolver := ocm.NewCompoundResolver(src) - - fmt.Printf("RESIGN B\n") - _ = SignComponent(resolver, SIGNATURE, COMPONENTB, D_COMPB, DigestMode(DIGESTMODE_TOP), SignatureName(SIGNATURE2, true)) - VerifyComponent(src, SIGNATURE, COMPONENTB, D_COMPB, SignatureName(SIGNATURE2, true)) - Check(finalizer.Finalize) - } - { // check mode - src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) - finalizer.Close(src) - cv := Must(src.LookupComponentVersion(COMPONENTB, VERSION)) - finalizer.Close(cv) - cd := cv.GetDescriptor() - Expect(len(cd.Signatures)).To(Equal(2)) - Expect(cd.Signatures[0].Digest.Value).To(Equal(D_COMPB)) - Expect(cd.Signatures[0].Name).To(Equal(SIGNATURE)) - Expect(cd.Signatures[1].Digest.Value).To(Equal(D_COMPB)) - Expect(cd.Signatures[1].Name).To(Equal(SIGNATURE2)) - Expect(cv.GetDescriptor().NestedDigests).To(BeNil()) - Expect(cv.GetDescriptor().References[0].Digest).NotTo(BeNil()) - Expect(GetDigestMode(cd)).To(Equal(DIGESTMODE_LOCAL)) - Check(finalizer.Finalize) - } - }, - Entry("legacy", Substitutions{}, ocm.SkipDigest()), - Entry("hashed", Substitutions{}), - ) - - DescribeTable("fixes digest mode in recursive signing", func(subst Substitutions, mopts ...ocm.ModificationOption) { - var finalizer Finalizer - defer Check(finalizer.Finalize) - - setup(mopts...) - - { // sign with mode local - src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) - finalizer.Close(src) - - resolver := ocm.NewCompoundResolver(src) - - fmt.Printf("SIGN B\n") - _ = SignComponent(resolver, SIGNATURE, COMPONENTB, D_COMPB, DigestMode(DIGESTMODE_TOP), SignatureName(SIGNATURE2, true)) - VerifyComponent(src, SIGNATURE, COMPONENTB, D_COMPB, SignatureName(SIGNATURE2, true)) - Check(finalizer.Finalize) - } - { // check mode - src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) - finalizer.Close(src) - cv := Must(src.LookupComponentVersion(COMPONENTB, VERSION)) - finalizer.Close(cv) - Expect(cv.GetDescriptor().Signatures[0].Digest.Value).To(Equal(D_COMPB)) - Expect(cv.GetDescriptor().NestedDigests).NotTo(BeNil()) - Expect(cv.GetDescriptor().References[0].Digest).To(BeNil()) - Expect(GetDigestMode(cv.GetDescriptor())).To(Equal(DIGESTMODE_TOP)) - Check(finalizer.Finalize) - } - { // resign recursively from top - src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) - finalizer.Close(src) - - resolver := ocm.NewCompoundResolver(src) - - fmt.Printf("SIGN D\n") - _ = SignComponent(resolver, SIGNATURE, COMPONENTD, D_COMPD, Recursive(), DigestMode(DIGESTMODE_LOCAL)) - VerifyComponent(src, SIGNATURE, COMPONENTD, D_COMPD) - Check(finalizer.Finalize) - } - { // check mode - src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) - finalizer.Close(src) - - cv := Must(src.LookupComponentVersion(COMPONENTB, VERSION)) - finalizer.Close(cv) - cd := cv.GetDescriptor() - Expect(len(cd.Signatures)).To(Equal(2)) - Expect(cd.Signatures[0].Digest.Value).To(Equal(D_COMPB)) - Expect(cd.Signatures[0].Name).To(Equal(SIGNATURE2)) - Expect(cd.Signatures[1].Digest.Value).To(Equal(D_COMPB)) - Expect(cd.Signatures[1].Name).To(Equal(SIGNATURE)) - Expect(cd.NestedDigests).NotTo(BeNil()) - Expect(cd.References[0].Digest).To(BeNil()) - Expect(GetDigestMode(cd)).To(Equal(DIGESTMODE_TOP)) - - cv = Must(src.LookupComponentVersion(COMPONENTD, VERSION)) - finalizer.Close(cv) - cd = cv.GetDescriptor() - Expect(len(cd.Signatures)).To(Equal(1)) - Expect(cd.Signatures[0].Digest.Value).To(Equal(D_COMPD)) - Expect(cd.Signatures[0].Name).To(Equal(SIGNATURE)) - Expect(cv.GetDescriptor().NestedDigests).To(BeNil()) - Expect(cv.GetDescriptor().References[0].Digest).NotTo(BeNil()) - Expect(GetDigestMode(cd)).To(Equal(DIGESTMODE_LOCAL)) - - Check(finalizer.Finalize) - } - }, - Entry("legacy", Substitutions{}, ocm.SkipDigest()), - Entry("hashed", Substitutions{}), - ) - }) - - Context("ref hashes", func() { - BeforeEach(func() { - env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { - env.ComponentVersion(COMPONENTA, VERSION, func() { - env.Provider(PROVIDER) - }) - env.ComponentVersion(COMPONENTB, VERSION, func() { - env.Provider(PROVIDER) - env.Reference("refa", COMPONENTA, VERSION) - }) - env.ComponentVersion(COMPONENTC, VERSION, func() { - env.Provider(PROVIDER) - env.Reference("refb", COMPONENTB, VERSION) - }) - }) - }) - - It("handles top level signature", func() { - src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) - defer Close(src, "ctf") - - resolver := ocm.NewCompoundResolver(src) - - cv := Must(resolver.LookupComponentVersion(COMPONENTC, VERSION)) - defer cv.Close() - - opts := NewOptions( - Sign(signing.DefaultHandlerRegistry().GetSigner(SIGN_ALGO), SIGNATURE), - Resolver(resolver), - VerifyDigests(), - ) - MustBeSuccessful(opts.Complete(env)) - - digestC := "1e81ac0fe69614e6fd73ab7a1c809dd31fcbcb810f0036be7a296d226e4bd64b" - pr, buf := common.NewBufferedPrinter() - dig := Must(Apply(pr, nil, cv, opts)) - Expect(dig.Value).To(StringEqualWithContext(digestC)) - - Expect(cv.GetDescriptor().References[0].Digest.HashAlgorithm).To(Equal(sha256.Algorithm)) - - cvb := Must(resolver.LookupComponentVersion(COMPONENTB, VERSION)) - defer Close(cvb) - Expect(cvb.GetDescriptor().References[0].Digest).NotTo(BeNil()) - _ = buf - }) - }) - - Context("keyless verification", func() { - ca, capriv := Must2(rsa.CreateRootCertificate(signutils.CommonName("ca-authority"), 10*time.Hour)) - intercert, interpem, interpriv := Must3(rsa.CreateSigningCertificate(signutils.CommonName("acme.org"), ca, ca, capriv, 5*time.Hour, true)) - certIssuer := &pkix.Name{ - CommonName: PROVIDER, - Country: []string{"DE", "US"}, - Locality: []string{"Walldorf d"}, - StreetAddress: []string{"x y"}, - PostalCode: []string{"69169"}, - Province: []string{"BW"}, - } - cert, pemBytes, priv := Must3(rsa.CreateSigningCertificate(certIssuer, interpem, ca, interpriv, time.Hour)) - - certs := Must(signutils.GetCertificateChain(pemBytes, false)) - Expect(len(certs)).To(Equal(3)) - - ctx := ocm.DefaultContext() - - var cv ocm.ComponentVersionAccess - - BeforeEach(func() { - cv = composition.NewComponentVersion(ctx, COMPONENTA, VERSION) - }) - - It("is consistent", func() { - MustBeSuccessful(signutils.VerifyCertificate(intercert, interpem, ca, nil)) - MustBeSuccessful(signutils.VerifyCertificate(cert, pemBytes, ca, nil)) - }) - - It("signs with certificate and default issuer", func() { - digest := "9cf14695c864411cad03071a8766e6769bb00373bdd8c65887e4644cc285dc78" - res := ocm.NewDedicatedResolver(cv) - - buf := SignComponent(res, PROVIDER, COMPONENTA, digest, PrivateKey(PROVIDER, priv), PublicKey(PROVIDER, pemBytes), RootCertificates(ca)) - Expect(buf).To(StringEqualTrimmedWithContext(` -applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v1]... -`)) - - i := cv.GetDescriptor().GetSignatureIndex(PROVIDER) - Expect(i).To(BeNumerically(">=", 0)) - sig := cv.GetDescriptor().Signatures[i].Signature - Expect(sig.MediaType).To(Equal(signutils.MediaTypePEM)) - _, algo, chain := Must3(signutils.GetSignatureFromPem([]byte(sig.Value))) - Expect(algo).To(Equal(rsa.Algorithm)) - Expect(len(chain)).To(Equal(3)) - - VerifyComponent(res, PROVIDER, COMPONENTA, digest, RootCertificates(ca)) - }) - - It("signs with certificate and explicit CN issuer", func() { - digest := "9cf14695c864411cad03071a8766e6769bb00373bdd8c65887e4644cc285dc78" - res := ocm.NewDedicatedResolver(cv) - - buf := SignComponent(res, SIGNATURE, COMPONENTA, digest, PrivateKey(SIGNATURE, priv), PublicKey(SIGNATURE, pemBytes), RootCertificates(ca), Issuer(PROVIDER)) - Expect(buf).To(StringEqualTrimmedWithContext(` -applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v1]... -`)) - - i := cv.GetDescriptor().GetSignatureIndex(SIGNATURE) - Expect(i).To(BeNumerically(">=", 0)) - sig := cv.GetDescriptor().Signatures[i].Signature - Expect(sig.MediaType).To(Equal(signutils.MediaTypePEM)) - _, algo, chain := Must3(signutils.GetSignatureFromPem([]byte(sig.Value))) - Expect(algo).To(Equal(rsa.Algorithm)) - Expect(len(chain)).To(Equal(3)) - - VerifyComponent(res, SIGNATURE, COMPONENTA, digest, RootCertificates(ca), Issuer(PROVIDER)) - - FailVerifyComponent(res, SIGNATURE, COMPONENTA, digest, - `github.com/mandelsoft/test:v1: public key from signature: public key certificate: issuer mismatch in public key certificate: common name "mandelsoft" is invalid`, - RootCertificates(ca)) - }) - - It("signs with certificate and issuer", func() { - digest := "9cf14695c864411cad03071a8766e6769bb00373bdd8c65887e4644cc285dc78" - res := ocm.NewDedicatedResolver(cv) - issuer := &pkix.Name{ - CommonName: PROVIDER, - Country: []string{"DE"}, - } - - buf := SignComponent(res, SIGNATURE, COMPONENTA, digest, PrivateKey(SIGNATURE, priv), PublicKey(SIGNATURE, pemBytes), RootCertificates(ca), PKIXIssuer(*issuer)) - Expect(buf).To(StringEqualTrimmedWithContext(` -applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v1]... -`)) - - i := cv.GetDescriptor().GetSignatureIndex(SIGNATURE) - Expect(i).To(BeNumerically(">=", 0)) - sig := cv.GetDescriptor().Signatures[i].Signature - Expect(sig.MediaType).To(Equal(signutils.MediaTypePEM)) - _, algo, chain := Must3(signutils.GetSignatureFromPem([]byte(sig.Value))) - Expect(algo).To(Equal(rsa.Algorithm)) - Expect(len(chain)).To(Equal(3)) - dn := Must(signutils.ParseDN(sig.Issuer)) - Expect(dn).To(Equal(certIssuer)) - - VerifyComponent(res, SIGNATURE, COMPONENTA, digest, RootCertificates(ca), PKIXIssuer(*issuer)) - - issuer.Country = []string{"XX"} - FailVerifyComponent(res, SIGNATURE, COMPONENTA, digest, - `github.com/mandelsoft/test:v1: public key from signature: public key certificate: issuer mismatch in public key certificate: country "XX" not found`, - RootCertificates(ca), PKIXIssuer(*issuer)) - }) - - It("signs with certificate, issuer and tsa", func() { - digest := "9cf14695c864411cad03071a8766e6769bb00373bdd8c65887e4644cc285dc78" - res := ocm.NewDedicatedResolver(cv) - issuer := &pkix.Name{ - CommonName: "mandelsoft", - Country: []string{"DE"}, - } - - buf := SignComponent(res, SIGNATURE, COMPONENTA, digest, PrivateKey(SIGNATURE, priv), PublicKey(SIGNATURE, pemBytes), RootCertificates(ca), PKIXIssuer(*issuer), UseTSA()) - Expect(buf).To(StringEqualTrimmedWithContext(` -applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v1]... -`)) - - i := cv.GetDescriptor().GetSignatureIndex(SIGNATURE) - Expect(i).To(BeNumerically(">=", 0)) - sig := cv.GetDescriptor().Signatures[i] - Expect(sig.Signature.MediaType).To(Equal(signutils.MediaTypePEM)) - _, algo, chain := Must3(signutils.GetSignatureFromPem([]byte(sig.Signature.Value))) - Expect(algo).To(Equal(rsa.Algorithm)) - Expect(len(chain)).To(Equal(3)) - dn := Must(signutils.ParseDN(sig.Signature.Issuer)) - Expect(dn).To(Equal(certIssuer)) - - Expect(sig.Timestamp).NotTo(BeNil()) - Expect(sig.Timestamp.Value).NotTo(Equal("")) - Expect(sig.Timestamp.Time).NotTo(BeNil()) - Expect(time.Now().Sub(sig.Timestamp.Time.Time()).Minutes()).To(BeNumerically("<", 2)) - VerifyComponent(res, SIGNATURE, COMPONENTA, digest, RootCertificates(ca), PKIXIssuer(*issuer)) - - issuer.Country = []string{"XX"} - FailVerifyComponent(res, SIGNATURE, COMPONENTA, digest, - `github.com/mandelsoft/test:v1: public key from signature: public key certificate: issuer mismatch in public key certificate: country "XX" not found`, - RootCertificates(ca), PKIXIssuer(*issuer)) - }) - }) -}) - -func HashComponent(resolver ocm.ComponentVersionResolver, name string, digest string, other ...Option) string { - cv, err := resolver.LookupComponentVersion(name, VERSION) - Expect(err).To(Succeed()) - defer cv.Close() - - opts := NewOptions( - Resolver(resolver), - Update(), VerifyDigests(), - ) - opts.Eval(other...) - ExpectWithOffset(1, opts.Complete(signingattr.Get(DefaultContext))).To(Succeed()) - - pr, buf := common.NewBufferedPrinter() - dig, err := Apply(pr, nil, cv, opts) - ExpectWithOffset(1, err).To(Succeed()) - ExpectWithOffset(1, dig.Value).To(StringEqualWithContext(digest)) - return buf.String() -} - -func VerifyHashes(resolver ocm.ComponentVersionResolver, name string, digest string) { - cv, err := resolver.LookupComponentVersion(name, VERSION) - ExpectWithOffset(1, err).To(Succeed()) - defer cv.Close() - - opts := NewOptions( - Resolver(resolver), - VerifyDigests(), - ) - ExpectWithOffset(1, opts.Complete(signingattr.Get(DefaultContext))).To(Succeed()) - dig, err := Apply(nil, nil, cv, opts) - ExpectWithOffset(1, err).To(Succeed()) - ExpectWithOffset(1, dig.Value).To(Equal(digest)) -} - -func SignComponent(resolver ocm.ComponentVersionResolver, signame, name string, digest string, other ...Option) string { - cv, err := resolver.LookupComponentVersion(name, VERSION) - Expect(err).To(Succeed()) - defer cv.Close() - - opts := NewOptions( - Sign(signingattr.Get(cv.GetContext()).GetSigner(SIGN_ALGO), signame), - Resolver(resolver), - VerifyDigests(), - ) - opts.Eval(other...) - ExpectWithOffset(1, opts.Complete(cv.GetContext())).To(Succeed()) - - pr, buf := common.NewBufferedPrinter() - dig, err := Apply(pr, nil, cv, opts) - ExpectWithOffset(1, err).To(Succeed()) - ExpectWithOffset(1, dig.Value).To(StringEqualWithContext(digest)) - return buf.String() -} - -func VerifyComponent(resolver ocm.ComponentVersionResolver, signame, name string, digest string, other ...Option) { - cv, err := resolver.LookupComponentVersion(name, VERSION) - ExpectWithOffset(1, err).To(Succeed()) - defer cv.Close() - - opts := NewOptions( - VerifySignature(signame), - Resolver(resolver), - VerifyDigests(), - ) - opts.Eval(other...) - ExpectWithOffset(1, opts.Complete(cv.GetContext())).To(Succeed()) - dig, err := Apply(nil, nil, cv, opts) - ExpectWithOffset(1, err).To(Succeed()) - ExpectWithOffset(1, dig.Value).To(Equal(digest)) -} - -func FailVerifyComponent(resolver ocm.ComponentVersionResolver, signame, name string, digest string, msg string, other ...Option) { - cv, err := resolver.LookupComponentVersion(name, VERSION) - ExpectWithOffset(1, err).To(Succeed()) - defer cv.Close() - - opts := NewOptions( - VerifySignature(signame), - Resolver(resolver), - VerifyDigests(), - ) - opts.Eval(other...) - ExpectWithOffset(1, opts.Complete(cv.GetContext())).To(Succeed()) - _, err = Apply(nil, nil, cv, opts) - ExpectWithOffset(1, err).To(MatchError(msg)) -} - -func Check(f func() error) { - ExpectWithOffset(1, f()).To(Succeed()) -} - -func CheckResourceDigests(cd *compdesc.ComponentDescriptor, digests map[string]*metav1.DigestSpec, offsets ...int) { - o := 4 - for _, a := range offsets { - o += a - } - for i, r := range cd.Resources { - By(fmt.Sprintf("resource %d", i), func() { - if none.IsNone(r.Access.GetKind()) { - ExpectWithOffset(o, r.Digest).To(BeNil()) - } else { - ExpectWithOffset(o, r.Digest).NotTo(BeNil()) - if digests != nil { - ExpectWithOffset(o, r.Digest).To(Equal(digests[r.Name])) - } - } - }) - } -} - -//////////////////////////////////////////////////////////////////////////////// - -const wrongDigest = "0a835d52867572bdaf7da7fb35ee59ad45c3db2dacdeeca62178edd5d07ef08c" // any wrong value - -type EntryCheck interface { - Mode() string - Check1CheckD(cvd ocm.ComponentVersionAccess, d Substitutions) - Check1CheckA(cva ocm.ComponentVersionAccess, d *metav1.DigestSpec, mopts ...ocm.ModificationOption) - Check1Corrupt(cva ocm.ComponentVersionAccess, f *Finalizer, cvd ocm.ComponentVersionAccess) - - Check2Ref(cv ocm.ComponentVersionAccess, name string, d string) -} - -type EntryLocal struct{} - -func (*EntryLocal) Mode() string { - return DIGESTMODE_LOCAL -} - -func (*EntryLocal) Check1CheckD(cvd ocm.ComponentVersionAccess, _ Substitutions) { - ExpectWithOffset(1, cvd.GetDescriptor().NestedDigests).To(BeNil()) -} - -func (*EntryLocal) Check1CheckA(cva ocm.ComponentVersionAccess, d *metav1.DigestSpec, _ ...ocm.ModificationOption) { - CheckResourceDigests(cva.GetDescriptor(), map[string]*metav1.DigestSpec{ - "data_a": d, - }, 1) -} - -func (*EntryLocal) Check1Corrupt(cva ocm.ComponentVersionAccess, f *Finalizer, _ ocm.ComponentVersionAccess) { - cva.GetDescriptor().Resources[0].Digest.Value = wrongDigest - MustBeSuccessful(cva.Update()) - Check(f.Finalize) -} - -func (*EntryLocal) Check2Ref(cv ocm.ComponentVersionAccess, name string, d string) { - CheckCompRef(cv, name, CompDigestSpec(d), 1) -} - -////////// - -type EntryTop struct { - found int -} - -func (*EntryTop) Mode() string { - return DIGESTMODE_TOP -} - -func (*EntryTop) Check1CheckD(cvd ocm.ComponentVersionAccess, digests Substitutions) { - ExpectWithOffset(1, cvd.GetDescriptor().NestedDigests).NotTo(BeNil()) - ExpectWithOffset(1, cvd.GetDescriptor().NestedDigests.String()).To(StringEqualTrimmedWithContext(` -github.com/mandelsoft/ref:v1: SHA-256:${D_COMPB}[jsonNormalisation/v1] - data_b:v1[]: SHA-256:${D_DATAB}[genericBlobDigest/v1] -github.com/mandelsoft/ref2:v1: SHA-256:${D_COMPC}[jsonNormalisation/v1] - data_c:v1[]: SHA-256:${D_DATAC}[genericBlobDigest/v1] -github.com/mandelsoft/test:v1: SHA-256:${D_COMPA}[jsonNormalisation/v1] - data_a:v1[]: SHA-256:${D_DATAA}[genericBlobDigest/v1] -`, digests)) -} - -func (*EntryTop) Check1CheckA(cva ocm.ComponentVersionAccess, d *metav1.DigestSpec, mopts ...ocm.ModificationOption) { - if ocm.NewModificationOptions(mopts...).IsSkipDigest() { - ExpectWithOffset(1, cva.GetDescriptor().Resources[0].Digest).To(BeNil()) - } else { - CheckResourceDigests(cva.GetDescriptor(), map[string]*metav1.DigestSpec{ - "data_a": d, - }, 1) - } -} - -func (e *EntryTop) Check1Corrupt(_ ocm.ComponentVersionAccess, _ *Finalizer, cvd ocm.ComponentVersionAccess) { - e.found = -1 - for i, n := range cvd.GetDescriptor().NestedDigests { - if n.Name == COMPONENTA { - n.Resources[0].Digest.Value = wrongDigest - e.found = i - } - } - ExpectWithOffset(1, e.found).NotTo(Equal(-1)) -} - -func (*EntryTop) Check2Ref(cv ocm.ComponentVersionAccess, name string, d string) { - CheckCompRef(cv, name, nil, 1) -} diff --git a/pkg/contexts/ocm/signing/signingtest/testdata.go b/pkg/contexts/ocm/signing/signingtest/testdata.go deleted file mode 100644 index ea51fa190..000000000 --- a/pkg/contexts/ocm/signing/signingtest/testdata.go +++ /dev/null @@ -1,13 +0,0 @@ -package signingtest - -import ( - "github.com/open-component-model/ocm/pkg/env" -) - -func TestData(dest ...string) env.Option { - return env.ProjectTestDataForCaller(dest...) -} - -func ModifiableTestData(dest ...string) env.Option { - return env.ModifiableProjectTestDataForCaller(dest...) -} diff --git a/pkg/contexts/ocm/testhelper/references.go b/pkg/contexts/ocm/testhelper/references.go deleted file mode 100644 index 2838193e1..000000000 --- a/pkg/contexts/ocm/testhelper/references.go +++ /dev/null @@ -1,35 +0,0 @@ -package testhelper - -import ( - "fmt" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/ocm" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/normalizations/jsonv1" - "github.com/open-component-model/ocm/pkg/signing/hasher/sha256" -) - -func CompDigestSpec(d string) *metav1.DigestSpec { - return &metav1.DigestSpec{ - HashAlgorithm: sha256.Algorithm, - NormalisationAlgorithm: jsonv1.Algorithm, - Value: d, - } -} - -func CheckCompRef(cv ocm.ComponentVersionAccess, name string, d *metav1.DigestSpec, offsets ...int) { - o := 1 - for _, a := range offsets { - o += a - } - for _, ref := range cv.GetDescriptor().References { - if ref.Name == name { - ExpectWithOffset(o, ref.Digest).To(Equal(d)) - return - } - } - Fail(fmt.Sprintf("ref %s not found", name), o) -} diff --git a/pkg/contexts/ocm/testhelper/refmgmt.go b/pkg/contexts/ocm/testhelper/refmgmt.go deleted file mode 100644 index 3c0ac7783..000000000 --- a/pkg/contexts/ocm/testhelper/refmgmt.go +++ /dev/null @@ -1,12 +0,0 @@ -package testhelper - -import ( - "github.com/mandelsoft/logging" - - ocmlog "github.com/open-component-model/ocm/pkg/logging" - "github.com/open-component-model/ocm/pkg/refmgmt" -) - -func EnableRefMgmtLog() { - ocmlog.Context().AddRule(logging.NewConditionRule(logging.TraceLevel, refmgmt.ALLOC_REALM)) -} diff --git a/pkg/contexts/ocm/testhelper/resources.go b/pkg/contexts/ocm/testhelper/resources.go deleted file mode 100644 index 8e7b9486a..000000000 --- a/pkg/contexts/ocm/testhelper/resources.go +++ /dev/null @@ -1,50 +0,0 @@ -package testhelper - -import ( - "github.com/mandelsoft/goutils/testutils" - - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/digester/digesters/blob" - "github.com/open-component-model/ocm/pkg/env/builder" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/signing/hasher/sha256" -) - -func TextResourceDigestSpec(d string) *metav1.DigestSpec { - return &metav1.DigestSpec{ - HashAlgorithm: sha256.Algorithm, - NormalisationAlgorithm: blob.GenericBlobDigestV1, - Value: d, - } -} - -var Digests = testutils.Substitutions{ - "D_TESTDATA": D_TESTDATA, - "D_OTHERDATA": D_OTHERDATA, -} - -const S_TESTDATA = "testdata" - -const D_TESTDATA = "810ff2fb242a5dee4220f2cb0e6a519891fb67f2f828a6cab4ef8894633b1f50" - -var DS_TESTDATA = TextResourceDigestSpec(D_TESTDATA) - -func TestDataResource(env *builder.Builder, funcs ...func()) { - env.Resource("testdata", "", "PlainText", metav1.LocalRelation, func() { - env.BlobStringData(mime.MIME_TEXT, S_TESTDATA) - env.Configure(funcs...) - }) -} - -const S_OTHERDATA = "otherdata" - -const D_OTHERDATA = "54b8007913ec5a907ca69001d59518acfd106f7b02f892eabf9cae3f8b2414b4" - -var DS_OTHERDATA = TextResourceDigestSpec(D_OTHERDATA) - -func OtherDataResource(env *builder.Builder, funcs ...func()) { - env.Resource("otherdata", "", "PlainText", metav1.LocalRelation, func() { - env.BlobStringData(mime.MIME_TEXT, S_OTHERDATA) - env.Configure(funcs...) - }) -} diff --git a/pkg/contexts/ocm/transfer/convenience.go b/pkg/contexts/ocm/transfer/convenience.go deleted file mode 100644 index e30fbd14e..000000000 --- a/pkg/contexts/ocm/transfer/convenience.go +++ /dev/null @@ -1,28 +0,0 @@ -package transfer - -import ( - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm" -) - -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// TransferWithHandler uses the specified transfer handler to control -// the transfer process. -func TransferWithHandler(pr common.Printer, cv ocm.ComponentVersionAccess, tgt ocm.Repository, handler TransferHandler) error { - return TransferVersion(pr, nil, cv, tgt, handler) -} - -// Transfer uses the transfer handler based on the given options to control -// the transfer process. The default handler is the standard handler. -func Transfer(cv ocm.ComponentVersionAccess, tgt ocm.Repository, optlist ...TransferOption) error { - h, err := NewTransferHandler(optlist...) - if err != nil { - return err - } - var local localOptions - err = local.Eval(optlist...) - if err != nil { - return err - } - return TransferWithHandler(local.printer, cv, tgt, h) -} diff --git a/pkg/contexts/ocm/transfer/init.go b/pkg/contexts/ocm/transfer/init.go deleted file mode 100644 index 677b70341..000000000 --- a/pkg/contexts/ocm/transfer/init.go +++ /dev/null @@ -1,7 +0,0 @@ -package transfer - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/config" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/spiff" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" -) diff --git a/pkg/contexts/ocm/transfer/internal/merge.go b/pkg/contexts/ocm/transfer/internal/merge.go deleted file mode 100644 index 0b50eb032..000000000 --- a/pkg/contexts/ocm/transfer/internal/merge.go +++ /dev/null @@ -1,121 +0,0 @@ -package internal - -import ( - "github.com/mandelsoft/logging" - - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "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/contexts/ocm/valuemergehandler" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/hpi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -// PrepareDescriptor provides a descriptor for the transport target based on a -// descriptor from the transport source and a descriptor already prsent at the -// target. -func PrepareDescriptor(log logging.Logger, ctx ocm.Context, s *compdesc.ComponentDescriptor, t *compdesc.ComponentDescriptor) (*compdesc.ComponentDescriptor, error) { - if ctx == nil { - ctx = ocm.DefaultContext() - } - - n := s.Copy() - err := MergeSignatures(t.Signatures, &n.Signatures) - if err == nil { - err = MergeLabels(log, ctx, t.Labels, &n.Labels) - } - if err == nil { - err = MergeLabels(log, ctx, t.Provider.Labels, &n.Provider.Labels) - } - if err == nil { - err = MergeElements(log, ctx, t.Sources, n.Sources) - } - if err == nil { - err = MergeElements(log, ctx, t.Resources, n.Resources) - } - if err == nil { - err = MergeElements(log, ctx, t.References, n.References) - } - - if err != nil { - return nil, err - } - return n, nil -} - -func MergeElements(log logging.Logger, ctx ocm.Context, s compdesc.ElementAccessor, t compdesc.ElementAccessor) error { - for i := 0; i < s.Len(); i++ { - es := s.Get(i) - id := es.GetMeta().GetIdentity(s) - et := compdesc.GetByIdentity(t, id) - if et != nil { - if err := MergeLabels(log, ctx, es.GetMeta().Labels, &et.GetMeta().Labels); err != nil { - return err - } - - // keep access for same digest - if aes, ok := es.(compdesc.ElementArtifactAccessor); ok { - if des, ok := es.(compdesc.ElementDigestAccessor); ok { - if des.GetDigest() != nil && des.GetDigest().Equal(et.(compdesc.ElementDigestAccessor).GetDigest()) { - et.(compdesc.ElementArtifactAccessor).SetAccess(aes.GetAccess()) - } - } - } - // keep digest for locally signed/hashed elements - if des, ok := es.(compdesc.ElementDigestAccessor); ok { - if des.GetDigest() != nil { - det := et.(compdesc.ElementDigestAccessor) - if det.GetDigest() == nil { - det.SetDigest(des.GetDigest()) - } - } - } - } - } - return nil -} - -// MergeLabels tries to merge old label states into the new target state. -func MergeLabels(log logging.Logger, ctx ocm.Context, s metav1.Labels, t *metav1.Labels) error { - for _, l := range s { - if l.Signing { - continue - } - idx := t.GetIndex(l.Name) - if idx < 0 { - log.Trace("appending label", "name", l.Name, "value", l.Value) - *t = append(*t, l) - } else { - err := MergeLabel(ctx, l, &(*t)[idx]) - if err != nil { - return err - } - log.Trace("merge result", "name", l.Name, "result", (*t)[idx].Value) - } - } - return nil -} - -func MergeLabel(ctx ocm.Context, s metav1.Label, t *metav1.Label) error { - r := valuemergehandler.Value{t.Value} - v := t.Version - if v == "" { - v = "v1" - } - mod, err := valuemergehandler.Merge(ctx, t.Merge, hpi.LabelHint(t.Name, v), runtime.RawValue{s.Value}, &r) - if mod { - t.Value = r.RawMessage - } - return err -} - -// MergeSignatures tries to merge old signatures into the new target state. -func MergeSignatures(s metav1.Signatures, t *metav1.Signatures) error { - for _, sig := range s { - idx := t.GetIndex(sig.Name) - if idx < 0 { - *t = append(*t, sig) - } - } - return nil -} diff --git a/pkg/contexts/ocm/transfer/logging.go b/pkg/contexts/ocm/transfer/logging.go deleted file mode 100644 index aec3cdcae..000000000 --- a/pkg/contexts/ocm/transfer/logging.go +++ /dev/null @@ -1,18 +0,0 @@ -package transfer - -import ( - "github.com/mandelsoft/logging" - - "github.com/open-component-model/ocm/pkg/contexts/ocm" - ocmlog "github.com/open-component-model/ocm/pkg/logging" -) - -var REALM = ocmlog.DefineSubRealm("OCM transfer handling", "transfer") - -type ContextProvider interface { - GetContext() ocm.Context -} - -func Logger(c ContextProvider, keyValuePairs ...interface{}) logging.Logger { - return c.GetContext().Logger(REALM).WithValues(keyValuePairs...) -} diff --git a/pkg/contexts/ocm/transfer/options.go b/pkg/contexts/ocm/transfer/options.go deleted file mode 100644 index 679ac3250..000000000 --- a/pkg/contexts/ocm/transfer/options.go +++ /dev/null @@ -1,69 +0,0 @@ -package transfer - -import ( - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler" -) - -type ( - // TransferOption if the interface for options given to transfer functions. - // The can influence the behaviour of the transfer process by configuring - // appropriate transfer handlers. - TransferOption = transferhandler.TransferOption - - // TransferOptions is the target interface for consumers of - // a TransferOption. - TransferOptions = transferhandler.TransferOptions - - // TransferHandler controls the transfer of component versions. - // It can be used to control the value transport of sources and resources - // on artifact level (by providing specific handling for dedicated artifact attributes), - // the concrete re/source transfer step, and the way how - // nested component version are transported. - // There are two implementations delivered as part of the OCM library: - // - package transferhandler.standard: able to select recursive transfer - // general value artifact transport. - // - package transferhandler.spiff: controls transfer using a spiff script. - // Custom implemetations can be used to gain fine-grained control - // over the transfer process, whose general flow is handled by - // a uniform Transfer function. - TransferHandler = transferhandler.TransferHandler -) - -// Local options do not relate to the transfer handler, but directly to the -// processing logic. They are formal transferhandler options to be passable to -// the option list but apply themselves only for the localOptions object. -// To distinguish them from transferhandler options, they do NOT implement -// the transferhandler.TransferOptionsCreator interface. -type localOptions struct { - printer common.Printer -} - -func (opts *localOptions) Eval(optlist ...transferhandler.TransferOption) error { - for _, o := range optlist { - if _, ok := o.(transferhandler.TransferOptionsCreator); !ok { - err := o.ApplyTransferOption(opts) - if err != nil { - return err - } - } - } - return nil -} - -// WithPrinter provides a explicit printer object. By default, -// a non-printing printer will be used. -func WithPrinter(p common.Printer) transferhandler.TransferOption { - return &localOptions{ - printer: p, - } -} - -func (l *localOptions) ApplyTransferOption(options TransferOptions) error { - if t, ok := options.(*localOptions); ok { - if l.printer != nil { - t.printer = l.printer - } - } - return nil -} diff --git a/pkg/contexts/ocm/transfer/transfer.go b/pkg/contexts/ocm/transfer/transfer.go deleted file mode 100644 index 2033d4e9a..000000000 --- a/pkg/contexts/ocm/transfer/transfer.go +++ /dev/null @@ -1,353 +0,0 @@ -package transfer - -import ( - "fmt" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/finalizer" - "github.com/mandelsoft/logging" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/none" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - ocmcpi "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/internal" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" - "github.com/open-component-model/ocm/pkg/errkind" - "github.com/open-component-model/ocm/pkg/runtime" -) - -type WalkingState = common.WalkingState[*struct{}, interface{}] - -type TransportClosure = common.NameVersionInfo[*struct{}] - -func TransferVersion(printer common.Printer, closure TransportClosure, src ocmcpi.ComponentVersionAccess, tgt ocmcpi.Repository, handler TransferHandler) error { - if closure == nil { - closure = TransportClosure{} - } - state := WalkingState{Closure: closure} - return transferVersion(common.AssurePrinter(printer), Logger(src), state, src, tgt, handler) -} - -func transferVersion(printer common.Printer, log logging.Logger, state WalkingState, src ocmcpi.ComponentVersionAccess, tgt ocmcpi.Repository, handler TransferHandler) (rerr error) { - nv := common.VersionedElementKey(src) - log = log.WithValues("history", state.History.String(), "version", nv) - if ok, err := state.Add(ocm.KIND_COMPONENTVERSION, nv); !ok { - return err - } - log.Info("transferring version") - printer.Printf("transferring version %q...\n", nv) - if handler == nil { - var err error - handler, err = standard.New(standard.Overwrite()) - if err != nil { - return err - } - } - - var finalize finalizer.Finalizer - defer finalize.FinalizeWithErrorPropagation(&rerr) - - d := src.GetDescriptor() - - comp, err := tgt.LookupComponent(src.GetName()) - if err != nil { - return errors.Wrapf(err, "%s: lookup target component", state.History) - } - finalize.Close(comp, "closing target component") - - var ok bool - t, err := comp.LookupVersion(src.GetVersion()) - finalize.Close(t, "existing target version") - - // references have always to be handled, because of potentially different - // transport modes, which could affect the desired access methods in - // the target environment. - - // doTransport controls, whether the transport of the local component - // version has to be re-considered. - doTransport := true - - // doMerge controls. whether a potential current version in the target - // environment has to be merged into the transported one. - doMerge := false - - // doCopy controls, whether the artifact content has to be considered. - doCopy := true - - if err != nil { - if errors.IsErrNotFound(err) { - t, err = comp.NewVersion(src.GetVersion()) - finalize.Close(t, "new target version") - } - } else { - ok, err = handler.EnforceTransport(src, t) - if err != nil { - return err - } - if ok { - // execute transport as if the component version were not present - // on the target side. - } else { - // determine transport mode for component version present - // on the target side. - if eq := d.Equivalent(t.GetDescriptor()); eq.IsHashEqual() { - if eq.IsEquivalent() { - if !needsResourceTransport(src, d, t.GetDescriptor(), handler) { - printer.Printf(" version %q already present -> skip transport\n", nv) - doTransport = false - } else { - printer.Printf(" version %q already present -> but requires resource transport\n", nv) - } - } else { - ok, err = handler.UpdateVersion(src, t) - if err != nil { - return err - } - if !ok { - printer.Printf(" version %q requires update of volatile data, but skipped\n", nv) - return nil - } - ok, err = handler.OverwriteVersion(src, t) - if ok { - printer.Printf(" warning: version %q already present, but transport enforced by overwrite option)\n", nv) - doMerge = false - doCopy = true - } else { - printer.Printf(" updating volatile properties of %q\n", nv) - doMerge = true - doCopy = false - } - } - } else { - msg := " version %q already present, but" - if eq.IsLocalHashEqual() { - if eq.IsArtifactDetectable() { - msg += " differs because some artifact digests are changed" - } else { - // TODO: option to precalculate missing digests (as pre equivalent step). - msg += " might differ, because not all artifact digests are known" - } - } else { - if eq.IsArtifactDetectable() { - if eq.IsArtifactEqual() { - msg += " differs because signature relevant properties have been changed" - } else { - msg += " differs because some artifacts and signature relevant properties have been changed" - } - } else { - msg += "differs because signature relevant properties have been changed (and not all artifact digests are known)" - } - } - ok, err = handler.OverwriteVersion(src, t) - if ok { - doMerge = false - printer.Printf("warning: "+msg+" (transport enforced by overwrite option)\n", nv) - } else { - printer.Printf(msg+" -> transport aborted (use option overwrite option to enforce transport)\n", nv) - return errors.ErrAlreadyExists(ocm.KIND_COMPONENTVERSION, nv.String()) - } - } - } - } - if err != nil { - return errors.Wrapf(err, "%s: creating target version", state.History) - } - - subp := printer.AddGap(" ") - list := errors.ErrListf("component references for %s", nv) - log.Info(" transferring references") - for _, r := range d.References { - cv, shdlr, err := handler.TransferVersion(src.Repository(), src, &r, tgt) - if err != nil { - return errors.Wrapf(err, "%s: nested component %s[%s:%s]", state.History, r.GetName(), r.ComponentName, r.GetVersion()) - } - if cv != nil { - list.Add(transferVersion(subp, log.WithValues("ref", r.Name), state, cv, tgt, shdlr)) - list.Addf(nil, cv.Close(), "closing reference %s", r.Name) - } - } - - if doTransport { - var n *compdesc.ComponentDescriptor - if doMerge { - log.WithValues("source", src.GetDescriptor(), "target", t.GetDescriptor()).Info(" applying 2-way merge") - n, err = internal.PrepareDescriptor(log, src.GetContext(), src.GetDescriptor(), t.GetDescriptor()) - if err != nil { - return err - } - } else { - n = src.GetDescriptor().Copy() - } - - var unstr *runtime.UnstructuredTypedObject - if !ocm.IsIntermediate(tgt.GetSpecification()) { - unstr, err = runtime.ToUnstructuredTypedObject(tgt.GetSpecification()) - if err != nil { - unstr = nil - } - } - if unstr != nil { - n.RepositoryContexts = append(n.RepositoryContexts, unstr) - } - - // just to be sure: both modes set to false would produce - // corrupted content in target. - // If no copy is done, merge must keep the access methods in target!!! - if !doMerge || doCopy { - err = copyVersion(printer, log, state.History, src, t, n, handler) - if err != nil { - return err - } - } else { - *t.GetDescriptor() = *n - } - - printer.Printf("...adding component version...\n") - log.Info(" adding component version") - list.Add(comp.AddVersion(t)) - } - return list.Result() -} - -func CopyVersion(printer common.Printer, log logging.Logger, hist common.History, src ocm.ComponentVersionAccess, t ocm.ComponentVersionAccess, handler TransferHandler) (rerr error) { - return copyVersion(common.AssurePrinter(printer), log, hist, src, t, src.GetDescriptor().Copy(), handler) -} - -// copyVersion (purely internal) expects an already prepared target comp desc for t given as prep. -func copyVersion(printer common.Printer, log logging.Logger, hist common.History, src ocm.ComponentVersionAccess, t ocm.ComponentVersionAccess, prep *compdesc.ComponentDescriptor, handler TransferHandler) (rerr error) { - var finalize finalizer.Finalizer - - defer errors.PropagateError(&rerr, finalize.Finalize) - - if handler == nil { - handler = standard.NewDefaultHandler(nil) - } - - srccd := src.GetDescriptor() - cur := *t.GetDescriptor() - *t.GetDescriptor() = *prep - log.Info(" transferring resources") - for i, r := range src.GetResources() { - var m ocmcpi.AccessMethod - - nested := finalize.Nested() - - a, err := r.Access() - if err == nil { - m, err = a.AccessMethod(src) - nested.Close(m, fmt.Sprintf("%s: transferring resource %d: closing access method", hist, i)) - } - if err == nil { - ok := a.IsLocal(src.GetContext()) - if !ok { - if !none.IsNone(a.GetKind()) { - ok, err = handler.TransferResource(src, a, r) - if err == nil && !ok { - log.Info("transport omitted", "resource", r.Meta().Name, "index", i, "access", a.GetType()) - } - } - } - if ok { - var old compdesc.Resource - - hint := ocmcpi.ArtifactNameHint(a, src) - old, err = cur.GetResourceByIdentity(r.Meta().GetIdentity(srccd.Resources)) - - changed := err != nil || old.Digest == nil || !old.Digest.Equal(r.Meta().Digest) - valueNeeded := err == nil && needsTransport(src.GetContext(), r, &old) - if changed || valueNeeded { - var msgs []interface{} - if !errors.IsErrNotFound(err) { - if err != nil { - return err - } - if !changed && valueNeeded { - msgs = []interface{}{"copy"} - } else { - msgs = []interface{}{"overwrite"} - } - } - notifyArtifactInfo(printer, log, "resource", i, r.Meta(), hint, msgs...) - err = handler.HandleTransferResource(r, m, hint, t) - } else { - if err == nil { // old resource found -> keep current access method - t.SetResource(r.Meta(), old.Access, ocm.ModifyResource(), ocm.SkipVerify()) - } - notifyArtifactInfo(printer, log, "resource", i, r.Meta(), hint, "already present") - } - } - } - if err != nil { - if !errors.IsErrUnknownKind(err, errkind.KIND_ACCESSMETHOD) { - return errors.Wrapf(err, "%s: transferring resource %d", hist, i) - } - printer.Printf("WARN: %s: transferring resource %d: %s (enforce transport by reference)\n", hist, i, err) - } - err = nested.Finalize() - if err != nil { - return err - } - } - - log.Info(" transferring sources") - for i, r := range src.GetSources() { - var m ocmcpi.AccessMethod - - a, err := r.Access() - if err == nil { - m, err = a.AccessMethod(src) - } - if err == nil { - ok := a.IsLocal(src.GetContext()) - if !ok { - if !none.IsNone(a.GetKind()) { - ok, err = handler.TransferSource(src, a, r) - if err == nil && !ok { - log.Info("transport omitted", "source", r.Meta().Name, "index", i, "access", a.GetType()) - } - } - } - if ok { - // sources do not have digests fo far, so they have to copied, always. - hint := ocmcpi.ArtifactNameHint(a, src) - notifyArtifactInfo(printer, log, "source", i, r.Meta(), hint) - err = errors.Join(err, handler.HandleTransferSource(r, m, hint, t)) - } - err = errors.Join(err, m.Close()) - } - if err != nil { - if !errors.IsErrUnknownKind(err, errkind.KIND_ACCESSMETHOD) { - return errors.Wrapf(err, "%s: transferring source %d", hist, i) - } - printer.Printf("WARN: %s: transferring source %d: %s (enforce transport by reference)\n", hist, i, err) - } - } - return nil -} - -func notifyArtifactInfo(printer common.Printer, log logging.Logger, kind string, index int, meta compdesc.ArtifactMetaAccess, hint string, msgs ...interface{}) { - msg := "copying" - cmsg := "..." - if len(msgs) > 0 { - if m, ok := msgs[0].(string); ok { - msg = fmt.Sprintf(m, msgs[1:]...) - } else { - msg = fmt.Sprint(msgs...) - } - cmsg = " (" + msg + ")" - } - if printer != nil { - if hint != "" { - printer.Printf("...%s %d %s[%s](%s)%s\n", kind, index, meta.GetName(), meta.GetType(), hint, cmsg) - } else { - printer.Printf("...%s %d %s[%s]%s\n", kind, index, meta.GetName(), meta.GetType(), cmsg) - } - } - if hint != "" { - log.Debug("handle artifact", "kind", kind, "name", meta.GetName(), "type", meta.GetType(), "index", index, "hint", hint, "message", msg) - } else { - log.Debug("handle artifact", "kind", kind, "name", meta.GetName(), "type", meta.GetType(), "index", index, "message", msg) - } -} diff --git a/pkg/contexts/ocm/transfer/transferhandler/config/config.go b/pkg/contexts/ocm/transfer/transferhandler/config/config.go deleted file mode 100644 index 2a45ba832..000000000 --- a/pkg/contexts/ocm/transfer/transferhandler/config/config.go +++ /dev/null @@ -1,102 +0,0 @@ -package scriptoption - -import ( - cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - ConfigType = "transport.ocm" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX - ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" -) - -func init() { - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1, usage)) -} - -// Config describes a set of transport options. -type Config struct { - runtime.ObjectVersionedType `json:",inline"` - Recursive *bool `json:"recursive,omitempty"` - ResourcesByValue *bool `json:"resourcesByValue,omitempty"` - LocalByValue *bool `json:"localResourcesByValue,omitempty"` - SourcesByValue *bool `json:"sourcesByValue,omitempty"` - KeepGlobalAccess *bool `json:"keepGlobalAccess,omitempty"` - StopOnExisting *bool `json:"stopOnExistingVersion,omitempty"` - Overwrite *bool `json:"overwrite,omitempty"` - OmitAccessTypes []string `json:"omitAccessTypes,omitempty"` -} - -// NewConfig creates a new memory ConfigSpec. -func NewConfig() *Config { - return &Config{ - ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), - } -} - -func (c *Config) GetType() string { - return ConfigType -} - -func (c *Config) ApplyTo(ctx cfgcpi.Context, target interface{}) error { - if c.Recursive != nil { - if opts, ok := target.(standard.RecursiveOption); ok { - opts.SetRecursive(*c.Recursive) - } - } - if c.ResourcesByValue != nil { - if opts, ok := target.(standard.ResourcesByValueOption); ok { - opts.SetResourcesByValue(*c.ResourcesByValue) - } - } - if c.LocalByValue != nil { - if opts, ok := target.(standard.LocalResourcesByValueOption); ok { - opts.SetLocalResourcesByValue(*c.LocalByValue) - } - } - if c.SourcesByValue != nil { - if opts, ok := target.(standard.SourcesByValueOption); ok { - opts.SetSourcesByValue(*c.SourcesByValue) - } - } - if c.KeepGlobalAccess != nil { - if opts, ok := target.(standard.KeepGlobalAccessOption); ok { - opts.SetKeepGlobalAccess(*c.KeepGlobalAccess) - } - } - if c.StopOnExisting != nil { - if opts, ok := target.(standard.StopOnExistingVersionOption); ok { - opts.SetStopOnExistingVersion(*c.StopOnExisting) - } - } - if c.Overwrite != nil { - if opts, ok := target.(standard.OverwriteOption); ok { - opts.SetOverwrite(*c.Overwrite) - } - } - if c.OmitAccessTypes != nil { - if opts, ok := target.(standard.OmitAccessTypesOption); ok { - opts.SetOmittedAccessTypes(c.OmitAccessTypes...) - } - } - return nil -} - -const usage = ` -The config type ` + ConfigType + ` can be used to define transfer scripts: - -
-    type: ` + ConfigType + `
-    recursive: true
-    overwrite: true
-    localResourcesByValue: false
-    resourcesByValue: true
-    sourcesByValue: false
-    keepGlobalAccess: false
-    stopOnExistingVersion: false
-    omitAccessTypes:
-    - s3
-
-` diff --git a/pkg/contexts/ocm/transfer/transferhandler/doc.go b/pkg/contexts/ocm/transfer/transferhandler/doc.go deleted file mode 100644 index 348ce7a5d..000000000 --- a/pkg/contexts/ocm/transfer/transferhandler/doc.go +++ /dev/null @@ -1,40 +0,0 @@ -// Package transferhandler provides the API for transfer handlers used -// during the transfer process of an OCM component. -// There is a common generic transfer functionality in package transfer, -// which can be used to transfer component versions from one OCM repository -// to another one. To flexible enough for various use cases this generic handling -// uses extension points to control the concrete transfer behaviour. -// These extension points can be implemented by dedicated transfer handlers -// to influence the transfer process. -// -// For example: -// -// - In a dedicated scenario only components from a dedicated provider should -// not be transferred per value in a transfer operation. -// - Another scenario could require to replace dedicated access methods. -// -// Such specific logic is hard to formalize, but nevertheless the basic transfer -// flow is always the same. Therefore, it makes no sense to reimplement the flow -// just because some inner process steps or decisions should look slightly -// different. Because of this transfer handlers are introduces, which allow to -// separate such decisions and step implementations form the generic flow and -// to provided scenario specific implementations with reimplementing the complete -// transitive transfer handling. -// -// For common use cases two standard handlers are provided, which can be used -// to formally describe typical transfer scenarios: -// - package [github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard] -// provides a standard behaviour configurable -// by some common options, like transport-by-value. -// - package [github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/spiff] -// provides a handler configurable by a [spiff] script. -// -// Transfer handlers may accept transfer options to be configured. -// For operations not taking a transferhandler but only transfer options, -// the given options are used to find the best matching transfer handling accepting -// those options. Therefore, standard transfer handles can be registered. -// Besides this any user defined transfer handler may be used by using operation -// flavors directly accepting a transfer handler. -// -// [spiff]: https://github.com/mandelsoft/spiff/README.md -package transferhandler diff --git a/pkg/contexts/ocm/transfer/transferhandler/spiff/handler.go b/pkg/contexts/ocm/transfer/transferhandler/spiff/handler.go deleted file mode 100644 index d06f8a946..000000000 --- a/pkg/contexts/ocm/transfer/transferhandler/spiff/handler.go +++ /dev/null @@ -1,309 +0,0 @@ -package spiff - -import ( - "encoding/json" - "strconv" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/spiff/dynaml" - "github.com/mandelsoft/spiff/features" - "github.com/mandelsoft/spiff/spiffing" - "github.com/mandelsoft/spiff/yaml" - "github.com/sirupsen/logrus" - - "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/transfer/transferhandler" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" - "github.com/open-component-model/ocm/pkg/runtime" -) - -type Handler struct { - standard.Handler - opts *Options - spiff spiffing.Spiff -} - -func New(opts ...transferhandler.TransferOption) (transferhandler.TransferHandler, error) { - options := &Options{} - err := transferhandler.ApplyOptions(options, opts...) - if err != nil { - return nil, err - } - spiff := spiffing.New().WithFeatures(features.CONTROL, features.INTERPOLATION) - if options.GetScriptFilesystem() != nil { - spiff = spiff.WithFileSystem(options.fs) - } - return &Handler{ - Handler: *standard.NewDefaultHandler(&options.Options), - opts: options, - spiff: spiff, - }, nil -} - -// TODO: handle update and overwrite per script - -func (h *Handler) UpdateVersion(src ocm.ComponentVersionAccess, tgt ocm.ComponentVersionAccess) (bool, error) { - if ok, err := h.Handler.UpdateVersion(src, tgt); !ok || err != nil { - return ok, nil - } - if h.opts.GetScript() == nil { - return false, nil - } - binding := h.getBinding(src, nil, nil, nil, nil) - return h.EvalBool("update", binding, "process") -} - -func (h *Handler) EnforceTransport(src ocm.ComponentVersionAccess, tgt ocm.ComponentVersionAccess) (bool, error) { - if ok, err := h.Handler.EnforceTransport(src, tgt); ok || err != nil { - return ok, nil - } - if h.opts.GetScript() == nil { - return false, nil - } - binding := h.getBinding(src, nil, nil, nil, nil) - return h.EvalBool("enforceTransport", binding, "process") -} - -func (h *Handler) OverwriteVersion(src ocm.ComponentVersionAccess, tgt ocm.ComponentVersionAccess) (bool, error) { - if ok, err := h.Handler.OverwriteVersion(src, tgt); ok || err != nil { - return ok, nil - } - if h.opts.GetScript() == nil { - return false, nil - } - binding := h.getBinding(src, nil, nil, nil, nil) - return h.EvalBool("overwrite", binding, "process") -} - -func (h *Handler) TransferVersion(repo ocm.Repository, src ocm.ComponentVersionAccess, meta *compdesc.ComponentReference, tgt ocm.Repository) (ocm.ComponentVersionAccess, transferhandler.TransferHandler, error) { - if src == nil || h.opts.IsRecursive() { - if h.opts.GetScript() == nil { - return h.Handler.TransferVersion(repo, src, meta, tgt) - } - binding := h.getBinding(src, nil, &meta.ElementMeta, nil, tgt) - result, r, s, err := h.EvalRecursion("componentversion", binding, "process") - if err != nil { - return nil, nil, err - } - if result { - if r != nil { - repo, err = repo.GetContext().RepositoryForConfig(r, runtime.DefaultJSONEncoding) - if err != nil { - return nil, nil, err - } - } - if s == nil { - return h.Handler.TransferVersion(repo, src, meta, tgt) - } - opts := *h.opts - opts.script = s - cv, _, err := h.Handler.TransferVersion(repo, src, meta, tgt) - return cv, &Handler{ - Handler: h.Handler, - opts: &opts, - }, err - } - } - return nil, nil, nil -} - -func (h *Handler) TransferResource(src ocm.ComponentVersionAccess, a ocm.AccessSpec, r ocm.ResourceAccess) (bool, error) { - if !h.opts.IsResourcesByValue() { - return false, nil - } - if h.opts.GetScript() == nil { - return true, nil - } - binding := h.getBinding(src, a, &r.Meta().ElementMeta, &r.Meta().Type, nil) - return h.EvalBool("resource", binding, "process") -} - -func (h *Handler) TransferSource(src ocm.ComponentVersionAccess, a ocm.AccessSpec, r ocm.SourceAccess) (bool, error) { - if !h.opts.IsSourcesByValue() { - return false, nil - } - if h.opts.GetScript() == nil { - return true, nil - } - binding := h.getBinding(src, a, &r.Meta().ElementMeta, &r.Meta().Type, nil) - return h.EvalBool("source", binding, "process") -} - -func (h *Handler) getBinding(src ocm.ComponentVersionAccess, a ocm.AccessSpec, m *compdesc.ElementMeta, typ *string, tgt ocm.Repository) map[string]interface{} { - binding := map[string]interface{}{} - if src != nil { - binding["component"] = getCVAttrs(src) - } - - if a != nil { - binding["access"] = getData(a) - } - if m != nil { - binding["element"] = getData(m) - } - if typ != nil { - binding["element"].(map[string]interface{})["type"] = *typ - } - if tgt != nil { - binding["target"] = getData(tgt.GetSpecification()) - } - return binding -} - -func getData(in interface{}) interface{} { - var v interface{} - - d, err := json.Marshal(in) - if err != nil { - logrus.Error(err) - } - - if err := json.Unmarshal(d, &v); err != nil { - logrus.Error(err) - } - - return v -} - -func getCVAttrs(cv ocm.ComponentVersionAccess) map[string]interface{} { - provider := map[string]interface{}{} - data, err := json.Marshal(cv.GetDescriptor().Provider) - if err != nil { - logrus.Error(err) - } - json.Unmarshal(data, &provider) - - labels := cv.GetDescriptor().Labels.AsMap() - - values := map[string]interface{}{} - values["name"] = cv.GetName() - values["version"] = cv.GetVersion() - values["provider"] = provider - values["labels"] = labels - return values -} - -func (h *Handler) Eval(binding map[string]interface{}) (spiffing.Node, error) { - spiff, err := h.spiff.WithValues(binding) - if err != nil { - return nil, err - } - node, err := spiff.Unmarshal("script", h.opts.GetScript()) - if err != nil { - return nil, err - } - return spiff.Cascade(node, nil) -} - -func (h *Handler) EvalBool(mode string, binding map[string]interface{}, key string) (bool, error) { - binding = map[string]interface{}{ - "mode": mode, - "values": binding, - } - r, err := h.Eval(binding) - if err != nil { - return false, err - } - return h.evalBool(r, key) -} - -func (h *Handler) EvalRecursion(mode string, binding map[string]interface{}, key string) (bool, []byte, []byte, error) { - binding = map[string]interface{}{ - "mode": mode, - "values": binding, - } - r, err := h.Eval(binding) - if err != nil { - return false, nil, nil, err - } - - valueMap, ok := r.Value().(map[string]spiffing.Node) - if !ok { - return false, nil, nil, errors.ErrUnknown("transfer script field", key) - } - r = valueMap[key] - if r == nil { - return false, nil, nil, errors.ErrUnknown("transfer script field", key) - } - - b, err := h.evalBoolValue(r) - if err == nil { - // flat boolean without result structure - return b, nil, nil, nil - } - valueMap, ok = r.Value().(map[string]spiffing.Node) - if !ok { - return false, nil, nil, errors.ErrInvalid("transfer script result field type", dynaml.ExpressionType(r)) - } - // now we expect a result structure - // process: bool - // repospec: map - // script: template - b, err = h.evalBool(r, key) - if err != nil || !b { - return false, nil, nil, err - } - var script []byte - v := valueMap["script"] - if v != nil && v.Value() != nil { - if t, ok := v.Value().(dynaml.TemplateValue); ok { - if m, ok := t.Orig.Value().(map[string]spiffing.Node); ok { - delete(m, "<<") - delete(m, "<<<") - } else { - return false, nil, nil, errors.ErrInvalid("script template type", dynaml.ExpressionType(t.Orig)) - } - script, err = yaml.Marshal(t.Orig) - if err != nil { - return false, nil, nil, err - } - } else { - return false, nil, nil, errors.ErrInvalid("script type", dynaml.ExpressionType(v)) - } - } - - var repospec []byte - v = valueMap["repospec"] - if v != nil && v.Value() != nil { - if _, ok := v.Value().(map[string]spiffing.Node); ok { - spec, err := yaml.Normalize(v) - if err == nil { - repospec, err = json.Marshal(spec) - } - if err != nil { - return false, nil, nil, errors.Wrapf(err, "invalid field repospec") - } - } else { - return false, nil, nil, errors.ErrInvalid("repospec type", dynaml.ExpressionType(v)) - } - } - return true, repospec, script, nil -} - -func (h *Handler) evalBool(r spiffing.Node, key string) (bool, error) { - v := r.Value().(map[string]spiffing.Node)[key] - if v == nil { - return false, errors.ErrUnknown("field", key) - } - return h.evalBoolValue(v) -} - -func (h *Handler) evalBoolValue(v spiffing.Node) (bool, error) { - switch b := v.Value().(type) { - case bool: - return b, nil - case int64: - return b != 0, nil - case float64: - return b != 0, nil - case string: - r, err := strconv.ParseBool(b) - if err != nil { - return len(b) > 0, nil - } - return r, nil - default: - return false, errors.ErrInvalid("boolean result", dynaml.ExpressionType(v)) - } -} diff --git a/pkg/contexts/ocm/transfer/transferhandler/spiff/handler_test.go b/pkg/contexts/ocm/transfer/transferhandler/spiff/handler_test.go deleted file mode 100644 index f58abbc03..000000000 --- a/pkg/contexts/ocm/transfer/transferhandler/spiff/handler_test.go +++ /dev/null @@ -1,298 +0,0 @@ -package spiff_test - -import ( - "encoding/json" - "fmt" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/oci/testhelper" - . "github.com/open-component-model/ocm/pkg/env/builder" - - "github.com/mandelsoft/goutils/testutils" - - "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/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/spiff" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" - ocmutils "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" - "github.com/open-component-model/ocm/pkg/mime" -) - -const ( - ARCH = "/tmp/ctf" - ARCH2 = "/tmp/ctf2" - PROVIDER = "mandelsoft" - VERSION = "v1" - COMPONENT = "github.com/mandelsoft/test" - COMPONENT2 = "github.com/mandelsoft/test2" - OUT = "/tmp/res" - OCIPATH = "/tmp/oci" - OCIHOST = "alias" -) - -const script1 = ` -rules: - resource: - <<: (( &template )) - process: (( values.component.name == "github.com/mandelsoft/test" )) - componentversion: - <<: (( &template )) - process: - process: true - script: (( rules.default )) - repospec: - type: dummy - default: - <<: (( &template )) - process: false - -process: (( (*(rules[mode] || rules.default)).process )) -` - -var _ = Describe("Transfer handler", func() { - It("handles bool", func() { - handler, err := spiff.New(spiff.Script([]byte(script1))) - Expect(err).To(Succeed()) - - binding := map[string]interface{}{ - "component": map[string]interface{}{ - "name": COMPONENT, - "version": VERSION, - }, - } - ok, err := handler.(*spiff.Handler).EvalBool("resource", binding, "process") - Expect(err).To(Succeed()) - Expect(ok).To(BeTrue()) - }) - - It("handles componentversion", func() { - handler, err := spiff.New(spiff.Script([]byte(script1))) - Expect(err).To(Succeed()) - - binding := map[string]interface{}{ - "component": map[string]interface{}{ - "name": COMPONENT, - "version": VERSION, - }, - } - ok, r, s, err := handler.(*spiff.Handler).EvalRecursion("componentversion", binding, "process") - Expect(err).To(Succeed()) - Expect(ok).To(BeTrue()) - Expect(string(s)).To(Equal("process: false\n")) - Expect(string(r)).To(Equal("{\"type\":\"dummy\"}")) - }) - - It("handles simple componentversion", func() { - handler, err := spiff.New(spiff.Script([]byte(script1))) - Expect(err).To(Succeed()) - binding := map[string]interface{}{ - "component": map[string]interface{}{ - "name": COMPONENT, - "version": VERSION, - }, - } - ok, r, s, err := handler.(*spiff.Handler).EvalRecursion("resource", binding, "process") - Expect(err).To(Succeed()) - Expect(ok).To(BeTrue()) - Expect(r).To(BeNil()) - Expect(s).To(BeNil()) - }) - - Context("handler", func() { - var env *Builder - var ldesc *artdesc.Descriptor - - BeforeEach(func() { - env = NewBuilder() - - FakeOCIRepo(env, OCIPATH, OCIHOST) - - env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { - ldesc = OCIManifest1(env) - OCIManifest2(env) - }) - - env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { - env.Component(COMPONENT, func() { - env.Version(VERSION, func() { - env.Provider(PROVIDER) - env.Resource("testdata", "", "PlainText", metav1.LocalRelation, func() { - env.BlobStringData(mime.MIME_TEXT, "testdata") - }) - env.Resource("value", "", resourcetypes.OCI_IMAGE, metav1.LocalRelation, func() { - env.Access( - ociartifact.New(oci.StandardOCIRef(OCIHOST+".alias", OCINAMESPACE, OCIVERSION)), - ) - env.Label("transportByValue", true) - }) - env.Resource("ref", "", resourcetypes.OCI_IMAGE, metav1.LocalRelation, func() { - env.Access( - ociartifact.New(oci.StandardOCIRef(OCIHOST+".alias", OCINAMESPACE2, OCIVERSION)), - ) - }) - }) - }) - }) - - env.OCMCommonTransport(ARCH2, accessio.FormatDirectory, func() { - env.Component(COMPONENT2, func() { - env.Version(VERSION, func() { - env.Reference("ref", COMPONENT, VERSION) - env.Provider(PROVIDER) - }) - }) - }) - }) - - AfterEach(func() { - env.Cleanup() - }) - - const script2 = ` -rules: - resource: - <<: (( &template )) -# process: (( values.element.name == "value" )) - process: (( values.element.labels.transportByValue.value || false )) - - default: - <<: (( &template )) - process: false - -process: (( (*(rules[mode] || rules.default)).process )) -` - - It("it should copy all resource by value to a ctf file without script", func() { - src, err := ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env) - Expect(err).To(Succeed()) - cv, err := src.LookupComponentVersion(COMPONENT, VERSION) - Expect(err).To(Succeed()) - tgt, err := ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env) - Expect(err).To(Succeed()) - defer tgt.Close() - - handler, err := spiff.New(standard.ResourcesByValue()) - Expect(err).To(Succeed()) - err = transfer.TransferVersion(nil, nil, cv, tgt, handler) - Expect(err).To(Succeed()) - Expect(env.DirExists(OUT)).To(BeTrue()) - - list, err := tgt.ComponentLister().GetComponents("", true) - Expect(err).To(Succeed()) - Expect(list).To(Equal([]string{COMPONENT})) - comp, err := tgt.LookupComponentVersion(COMPONENT, VERSION) - Expect(err).To(Succeed()) - Expect(len(comp.GetDescriptor().Resources)).To(Equal(3)) - - data, err := json.Marshal(comp.GetDescriptor().Resources[2].Access) - Expect(err).To(Succeed()) - fmt.Printf("%s\n", string(data)) - hash := HashManifest2(artifactset.DefaultArtifactSetDescriptorFileName) - Expect(string(data)).To(testutils.StringEqualWithContext("{\"localReference\":\"" + hash + "\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\"" + OCINAMESPACE2 + ":" + OCIVERSION + "\",\"type\":\"localBlob\"}")) - - data, err = json.Marshal(comp.GetDescriptor().Resources[1].Access) - Expect(err).To(Succeed()) - fmt.Printf("%s\n", string(data)) - hash = HashManifest1(artifactset.DefaultArtifactSetDescriptorFileName) - Expect(string(data)).To(Equal("{\"localReference\":\"" + hash + "\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\"" + OCINAMESPACE + ":" + OCIVERSION + "\",\"type\":\"localBlob\"}")) - - racc, err := comp.GetResourceByIndex(1) - Expect(err).To(Succeed()) - reader, err := ocmutils.GetResourceReader(racc) - Expect(err).To(Succeed()) - defer reader.Close() - set, err := artifactset.Open(accessobj.ACC_READONLY, "", 0, accessio.Reader(reader)) - Expect(err).To(Succeed()) - defer set.Close() - - _, blob, err := set.GetBlobData(ldesc.Digest) - Expect(err).To(Succeed()) - data, err = blob.Get() - Expect(err).To(Succeed()) - Expect(string(data)).To(Equal("manifestlayer")) - }) - - It("it should copy one resource by value to a ctf file with script", func() { - src, err := ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env) - Expect(err).To(Succeed()) - cv, err := src.LookupComponentVersion(COMPONENT, VERSION) - Expect(err).To(Succeed()) - tgt, err := ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env) - Expect(err).To(Succeed()) - defer tgt.Close() - - handler, err := spiff.New(standard.ResourcesByValue(), spiff.Script([]byte(script2))) - Expect(err).To(Succeed()) - err = transfer.TransferVersion(nil, nil, cv, tgt, handler) - Expect(err).To(Succeed()) - Expect(env.DirExists(OUT)).To(BeTrue()) - - list, err := tgt.ComponentLister().GetComponents("", true) - Expect(err).To(Succeed()) - Expect(list).To(Equal([]string{COMPONENT})) - comp, err := tgt.LookupComponentVersion(COMPONENT, VERSION) - Expect(err).To(Succeed()) - Expect(len(comp.GetDescriptor().Resources)).To(Equal(3)) - - // index 2: by ref - data, err := json.Marshal(comp.GetDescriptor().Resources[2].Access) - Expect(err).To(Succeed()) - Expect(string(data)).To(Equal("{\"imageReference\":\"" + oci.StandardOCIRef(OCIHOST+".alias", OCINAMESPACE2, OCIVERSION) + "\",\"type\":\"" + ociartifact.Type + "\"}")) - - // index 1: by value - data, err = json.Marshal(comp.GetDescriptor().Resources[1].Access) - Expect(err).To(Succeed()) - hash := HashManifest1(artifactset.DefaultArtifactSetDescriptorFileName) - Expect(string(data)).To(Equal("{\"localReference\":\"" + hash + "\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\"" + OCINAMESPACE + ":" + OCIVERSION + "\",\"type\":\"localBlob\"}")) - - racc, err := comp.GetResourceByIndex(1) - Expect(err).To(Succeed()) - reader, err := ocmutils.GetResourceReader(racc) - Expect(err).To(Succeed()) - defer reader.Close() - set, err := artifactset.Open(accessobj.ACC_READONLY, "", 0, accessio.Reader(reader)) - Expect(err).To(Succeed()) - defer set.Close() - - _, blob, err := set.GetBlobData(ldesc.Digest) - Expect(err).To(Succeed()) - data, err = blob.Get() - Expect(err).To(Succeed()) - Expect(string(data)).To(Equal("manifestlayer")) - }) - - It("it should use additional resolver to resolve component ref", func() { - parentSrc, err := ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH2, 0, env) - Expect(err).To(Succeed()) - cv, err := parentSrc.LookupComponentVersion(COMPONENT2, VERSION) - Expect(err).To(Succeed()) - childSrc, err := ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env) - Expect(err).To(Succeed()) - tgt, err := ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env) - Expect(err).To(Succeed()) - defer tgt.Close() - handler, err := standard.New(standard.Recursive(), standard.Resolver(childSrc)) - Expect(err).To(Succeed()) - err = transfer.TransferVersion(nil, nil, cv, tgt, handler) - Expect(err).To(Succeed()) - Expect(env.DirExists(OUT)).To(BeTrue()) - - list, err := tgt.ComponentLister().GetComponents("", true) - Expect(err).To(Succeed()) - Expect(list).To(ContainElements([]string{COMPONENT2, COMPONENT})) - _, err = tgt.LookupComponentVersion(COMPONENT2, VERSION) - Expect(err).To(Succeed()) - _, err = tgt.LookupComponentVersion(COMPONENT, VERSION) - Expect(err).To(Succeed()) - }) - }) -}) diff --git a/pkg/contexts/ocm/transfer/transferhandler/spiff/options.go b/pkg/contexts/ocm/transfer/transferhandler/spiff/options.go deleted file mode 100644 index ae395dbb0..000000000 --- a/pkg/contexts/ocm/transfer/transferhandler/spiff/options.go +++ /dev/null @@ -1,149 +0,0 @@ -package spiff - -import ( - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/spiff/spiffing" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" - "github.com/open-component-model/ocm/pkg/utils" -) - -func init() { - transferhandler.RegisterHandler(100, &TransferOptionsCreator{}) -} - -type Options struct { - standard.Options - script []byte - fs vfs.FileSystem -} - -var ( - _ transferhandler.TransferOption = (*Options)(nil) - - _ ScriptOption = (*Options)(nil) - _ ScriptFilesystemOption = (*Options)(nil) -) - -type TransferOptionsCreator = transferhandler.SpecializedOptionsCreator[*Options, Options] - -func (o *Options) NewOptions() transferhandler.TransferHandlerOptions { - return &Options{} -} - -func (o *Options) NewTransferHandler() (transferhandler.TransferHandler, error) { - return New(o) -} - -func (o *Options) ApplyTransferOption(target transferhandler.TransferOptions) error { - if len(o.script) > 0 { - if opts, ok := target.(ScriptOption); ok { - opts.SetScript(o.script) - } - } - if o.fs != nil { - if opts, ok := target.(ScriptFilesystemOption); ok { - opts.SetScriptFilesystem(o.fs) - } - } - return o.Options.ApplyTransferOption(target) -} - -func (o *Options) SetScript(data []byte) { - o.script = data -} - -func (o *Options) GetScript() []byte { - return o.script -} - -func (o *Options) SetScriptFilesystem(fs vfs.FileSystem) { - o.fs = fs -} - -func (o *Options) GetScriptFilesystem() vfs.FileSystem { - return o.fs -} - -/////////////////////////////////////////////////////////////////////////////// - -type ScriptOption interface { - SetScript(data []byte) - GetScript() []byte -} - -type scriptOption struct { - TransferOptionsCreator - source string - script func() ([]byte, error) -} - -func (o *scriptOption) ApplyTransferOption(to transferhandler.TransferOptions) error { - if o.script == nil { - return nil - } - script, err := o.script() - if err != nil { - return err - } - _, err = spiffing.New().Unmarshal(o.source, script) - if err != nil { - return err - } - - if eff, ok := to.(ScriptOption); ok { - eff.SetScript(script) - return nil - } else { - return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "script") - } -} - -func Script(data []byte) transferhandler.TransferOption { - if data == nil { - return &scriptOption{ - source: "script", - } - } - return &scriptOption{ - source: "script", - script: func() ([]byte, error) { return data, nil }, - } -} - -func ScriptByFile(path string, fss ...vfs.FileSystem) transferhandler.TransferOption { - path, _ = utils.ResolvePath(path) - return &scriptOption{ - source: path, - script: func() ([]byte, error) { return vfs.ReadFile(utils.FileSystem(fss...), path) }, - } -} - -/////////////////////////////////////////////////////////////////////////////// - -type ScriptFilesystemOption interface { - SetScriptFilesystem(fs vfs.FileSystem) - GetScriptFilesystem() vfs.FileSystem -} - -type filesystemOption struct { - TransferOptionsCreator - fs vfs.FileSystem -} - -func (o *filesystemOption) ApplyTransferOption(to transferhandler.TransferOptions) error { - if eff, ok := to.(ScriptFilesystemOption); ok { - eff.SetScriptFilesystem(o.fs) - return nil - } else { - return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "script filesystem") - } -} - -func ScriptFilesystem(fs vfs.FileSystem) transferhandler.TransferOption { - return &filesystemOption{ - fs: fs, - } -} diff --git a/pkg/contexts/ocm/transfer/transferhandler/standard/handler.go b/pkg/contexts/ocm/transfer/transferhandler/standard/handler.go deleted file mode 100644 index 24965f387..000000000 --- a/pkg/contexts/ocm/transfer/transferhandler/standard/handler.go +++ /dev/null @@ -1,109 +0,0 @@ -package standard - -import ( - "time" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "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/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler" -) - -type Handler struct { - opts *Options -} - -func NewDefaultHandler(opts *Options) *Handler { - if opts == nil { - opts = &Options{} - } - return &Handler{opts: opts} -} - -func New(opts ...transferhandler.TransferOption) (transferhandler.TransferHandler, error) { - defaultOpts := &Options{} - err := transferhandler.ApplyOptions(defaultOpts, opts...) - if err != nil { - return nil, err - } - return NewDefaultHandler(defaultOpts), nil -} - -func (h *Handler) UpdateVersion(src ocm.ComponentVersionAccess, tgt ocm.ComponentVersionAccess) (bool, error) { - return !h.opts.IsSkipUpdate(), nil -} - -func (h *Handler) EnforceTransport(src ocm.ComponentVersionAccess, tgt ocm.ComponentVersionAccess) (bool, error) { - return h.opts.IsTransportEnforced(), nil -} - -func (h *Handler) OverwriteVersion(src ocm.ComponentVersionAccess, tgt ocm.ComponentVersionAccess) (bool, error) { - return h.opts.IsOverwrite(), nil -} - -func (h *Handler) TransferVersion(repo ocm.Repository, src ocm.ComponentVersionAccess, meta *compdesc.ComponentReference, tgt ocm.Repository) (ocm.ComponentVersionAccess, transferhandler.TransferHandler, error) { - if src == nil || h.opts.IsRecursive() { - if h.opts.IsStopOnExistingVersion() && tgt != nil { - if found, err := tgt.ExistsComponentVersion(meta.ComponentName, meta.Version); found || err != nil { - return nil, nil, errors.Wrapf(err, "failed looking up in target") - } - } - compoundResolver := ocm.NewCompoundResolver(repo, h.opts.GetResolver()) - cv, err := compoundResolver.LookupComponentVersion(meta.GetComponentName(), meta.Version) - return cv, h, err - } - return nil, nil, nil -} - -func (h *Handler) TransferResource(src ocm.ComponentVersionAccess, a ocm.AccessSpec, r ocm.ResourceAccess) (bool, error) { - if h.opts.IsAccessTypeOmitted(a.GetType()) { - return false, nil - } - if h.opts.IsLocalResourcesByValue() { - if r.Meta().Relation == metav1.LocalRelation { - return true, nil - } - } - return h.opts.IsResourcesByValue(), nil -} - -func (h *Handler) TransferSource(src ocm.ComponentVersionAccess, a ocm.AccessSpec, r ocm.SourceAccess) (bool, error) { - if h.opts.IsAccessTypeOmitted(a.GetType()) { - return false, nil - } - return h.opts.IsSourcesByValue(), nil -} - -func (h *Handler) HandleTransferResource(r ocm.ResourceAccess, m cpi.AccessMethod, hint string, t ocm.ComponentVersionAccess) error { - blob, err := accspeccpi.BlobAccessForAccessMethod(m) - if err != nil { - return err - } - defer blob.Close() - return accessio.Retry(h.opts.GetRetries(), time.Second, func() error { - return t.SetResourceBlob(r.Meta(), blob, hint, h.GlobalAccess(t.GetContext(), m), ocm.SkipVerify()) - }) -} - -func (h *Handler) HandleTransferSource(r ocm.SourceAccess, m cpi.AccessMethod, hint string, t ocm.ComponentVersionAccess) error { - blob, err := accspeccpi.BlobAccessForAccessMethod(m) - if err != nil { - return err - } - defer blob.Close() - return accessio.Retry(h.opts.GetRetries(), time.Second, func() error { - return t.SetSourceBlob(r.Meta(), blob, hint, h.GlobalAccess(t.GetContext(), m)) - }) -} - -func (h *Handler) GlobalAccess(ctx ocm.Context, m ocm.AccessMethod) ocm.AccessSpec { - if h.opts.IsKeepGlobalAccess() { - return m.AccessSpec().GlobalAccessSpec(ctx) - } - return nil -} diff --git a/pkg/contexts/ocm/transfer/transferhandler/standard/handler_test.go b/pkg/contexts/ocm/transfer/transferhandler/standard/handler_test.go deleted file mode 100644 index 2832442ba..000000000 --- a/pkg/contexts/ocm/transfer/transferhandler/standard/handler_test.go +++ /dev/null @@ -1,528 +0,0 @@ -package standard_test - -import ( - "encoding/json" - "fmt" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/oci/testhelper" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/testhelper" - . "github.com/open-component-model/ocm/pkg/env/builder" - - "github.com/mandelsoft/goutils/finalizer" - - "github.com/open-component-model/ocm/pkg/blobaccess" - "github.com/open-component-model/ocm/pkg/common" - "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/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/compositionmodeattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/signingattr" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" - ocmsign "github.com/open-component-model/ocm/pkg/contexts/ocm/signing" - "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/standard" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" -) - -const ( - ARCH = "/tmp/ctf" - ARCH2 = "/tmp/ctf2" - PROVIDER = "mandelsoft" - VERSION = "v1" - COMPONENT = "github.com/mandelsoft/test" - COMPONENT2 = "github.com/mandelsoft/test2" - OUT = "/tmp/res" - OCIPATH = "/tmp/oci" - OCIHOST = "alias" - SIGNATURE = "test" - SIGN_ALGO = rsa.Algorithm -) - -type optionsChecker struct { - standard.TransferOptionsCreator -} - -var _ transferhandler.TransferOption = (*optionsChecker)(nil) - -func (o *optionsChecker) ApplyTransferOption(options transferhandler.TransferOptions) error { - if _, ok := options.(*standard.Options); !ok { - return fmt.Errorf("unexpected options type %T", options) - } - return nil -} - -var _ = Describe("Transfer handler", func() { - var env *Builder - var ldesc *artdesc.Descriptor - - BeforeEach(func() { - env = NewBuilder() - - env.RSAKeyPair(SIGNATURE) - - env.OCICommonTransport(OCIPATH, accessio.FormatDirectory, func() { - ldesc = OCIManifest1(env) - }) - - FakeOCIRepo(env, OCIPATH, OCIHOST) - - env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { - env.Component(COMPONENT, func() { - env.Version(VERSION, func() { - env.Provider(PROVIDER) - TestDataResource(env) - env.Resource("artifact", "", resourcetypes.OCI_IMAGE, metav1.LocalRelation, func() { - env.Access( - ociartifact.New(oci.StandardOCIRef(OCIHOST+".alias", OCINAMESPACE, OCIVERSION)), - ) - }) - }) - }) - }) - - env.OCMCommonTransport(ARCH2, accessio.FormatDirectory, func() { - env.Component(COMPONENT2, func() { - env.Version(VERSION, func() { - env.Reference("ref", COMPONENT, VERSION) - env.Provider(PROVIDER) - }) - }) - }) - }) - - AfterEach(func() { - env.Cleanup() - }) - - It("test", func() { - src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) - defer Close(src, "source") - cv := Must(src.LookupComponentVersion(COMPONENT, VERSION)) - defer Close(cv, "source cv") - tgt := Must(ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) - defer Close(tgt, "target") - - tcv := Must(tgt.NewComponentVersion(cv.GetName(), cv.GetVersion())) - defer Close(tcv, "target version") - - res := Must(cv.GetResource(metav1.NewIdentity("artifact"))) - acc := Must(res.Access()) - - m := Must(acc.AccessMethod(cv)) - defer Close(m, "method") - - blob := Must(accspeccpi.BlobAccessForAccessMethod(m)) - defer Close(blob, "blob") - MustBeSuccessful(tcv.SetResourceBlob(res.Meta(), blob, "", nil, ocm.SkipVerify())) - - MustBeSuccessful(tgt.AddComponentVersion(tcv)) - }) - - DescribeTable("it should copy a resource by value to a ctf file", func(acc string, compose bool, topts ...transferhandler.TransferOption) { - compositionmodeattr.Set(env.OCMContext(), compose) - src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) - defer Close(src, "source") - cv := Must(src.LookupComponentVersion(COMPONENT, VERSION)) - defer Close(cv, "source cv") - tgt := Must(ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) - defer Close(tgt, "target") - - // handler, err := standard.New(standard.ResourcesByValue()) - p, buf := common.NewBufferedPrinter() - opts := append(topts, standard.ResourcesByValue(), transfer.WithPrinter(p), &optionsChecker{}) - MustBeSuccessful(transfer.Transfer(cv, tgt, opts...)) - Expect(env.DirExists(OUT)).To(BeTrue()) - - Expect(string(buf.Bytes())).To(StringEqualTrimmedWithContext(` -transferring version "github.com/mandelsoft/test:v1"... -...resource 0 testdata[PlainText]... -...resource 1 artifact[ociImage](ocm/value:v2.0)... -...adding component version... -`)) - }, - Entry("without preserve global", - "{\"localReference\":\"%s\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\""+OCINAMESPACE+":"+OCIVERSION+"\",\"type\":\"localBlob\"}", - false), - Entry("with preserve global", - "{\"globalAccess\":{\"imageReference\":\"alias.alias/ocm/value:v2.0\",\"type\":\"ociArtifact\"},\"localReference\":\"%s\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\"ocm/value:v2.0\",\"type\":\"localBlob\"}", - false, - standard.KeepGlobalAccess()), - - Entry("with composition and without preserve global", - "{\"localReference\":\"%s\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\""+OCINAMESPACE+":"+OCIVERSION+"\",\"type\":\"localBlob\"}", - true), - Entry("with composition and with preserve global", - "{\"globalAccess\":{\"imageReference\":\"alias.alias/ocm/value:v2.0\",\"type\":\"ociArtifact\"},\"localReference\":\"%s\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\"ocm/value:v2.0\",\"type\":\"localBlob\"}", - true, - standard.KeepGlobalAccess()), - ) - - DescribeTable("it should copy a resource by value to a ctf file", func(acc string, compose bool, topts ...transferhandler.TransferOption) { - compositionmodeattr.Set(env.OCMContext(), compose) - src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) - defer Close(src, "source") - cv := Must(src.LookupComponentVersion(COMPONENT, VERSION)) - defer Close(cv, "source cv") - tgt := Must(ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) - defer Close(tgt, "target") - - // handler, err := standard.New(standard.ResourcesByValue()) - p, buf := common.NewBufferedPrinter() - opts := append(topts, standard.ResourcesByValue(), transfer.WithPrinter(p), &optionsChecker{}) - MustBeSuccessful(transfer.Transfer(cv, tgt, opts...)) - Expect(env.DirExists(OUT)).To(BeTrue()) - - Expect(string(buf.Bytes())).To(StringEqualTrimmedWithContext(` -transferring version "github.com/mandelsoft/test:v1"... -...resource 0 testdata[PlainText]... -...resource 1 artifact[ociImage](ocm/value:v2.0)... -...adding component version... -`)) - - var nested finalizer.Finalizer - defer Defer(nested.Finalize) - - list := Must(tgt.ComponentLister().GetComponents("", true)) - Expect(list).To(Equal([]string{COMPONENT})) - tcv := Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) - nested.Close(tcv, "target cv") - Expect(len(tcv.GetDescriptor().Resources)).To(Equal(2)) - data := Must(json.Marshal(tcv.GetDescriptor().Resources[1].Access)) - - fmt.Printf("%s\n", string(data)) - hash := HashManifest1(artifactset.DefaultArtifactSetDescriptorFileName) - Expect(string(data)).To(StringEqualWithContext(fmt.Sprintf(acc, hash))) - - tcd := tcv.GetDescriptor().Copy() - r := Must(tcv.GetResourceByIndex(1)) - meth := Must(r.AccessMethod()) - nested.Close(meth, "method") - reader := Must(meth.Reader()) - nested.Close(reader, "reader") - set := Must(artifactset.Open(accessobj.ACC_READONLY, "", 0, accessio.Reader(reader))) - nested.Close(set, "set") - - _, blob := Must2(set.GetBlobData(ldesc.Digest)) - nested.Close(blob) - data = Must(blob.Get()) - Expect(string(data)).To(Equal("manifestlayer")) - - MustBeSuccessful(nested.Finalize()) - - // retransfer - buf.Reset() - MustBeSuccessful(transfer.Transfer(cv, tgt, opts...)) - Expect(string(buf.Bytes())).To(StringEqualTrimmedWithContext(` -transferring version "github.com/mandelsoft/test:v1"... - version "github.com/mandelsoft/test:v1" already present -> skip transport`)) - tcv = Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) - nested.Close(tcv, "target cv") - Expect(tcd).To(DeepEqual(tcv.GetDescriptor())) - MustBeSuccessful(nested.Finalize()) - - // modify volatile - cv.GetDescriptor().Labels.Set("new", "newvalue") - tcd.Labels.Set("new", "newvalue") - buf.Reset() - MustBeSuccessful(transfer.Transfer(cv, tgt, opts...)) - Expect(string(buf.Bytes())).To(StringEqualTrimmedWithContext(` -transferring version "github.com/mandelsoft/test:v1"... - updating volatile properties of "github.com/mandelsoft/test:v1" -...adding component version... -`)) - tcv = Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) - nested.Close(tcv, "target cv") - Expect(tcd).To(DeepEqual(tcv.GetDescriptor())) - MustBeSuccessful(nested.Finalize()) - - // modify one artifact and overwrite - MustBeSuccessful(cv.SetResourceBlob(Must(cv.GetResourceByIndex(0)).Meta().Fresh(), blobaccess.ForString(mime.MIME_TEXT, "otherdata"), "", nil)) - tcd.Resources[0].Digest = DS_OTHERDATA - tcd.Resources[0].Access = Must(runtime.ToUnstructuredVersionedTypedObject(localblob.New("sha256:"+D_OTHERDATA, "", mime.MIME_TEXT, nil))) - buf.Reset() - MustBeSuccessful(transfer.Transfer(cv, tgt, append(opts, standard.Overwrite())...)) - Expect(string(buf.Bytes())).To(StringEqualTrimmedWithContext(` -transferring version "github.com/mandelsoft/test:v1"... -warning: version "github.com/mandelsoft/test:v1" already present, but differs because some artifact digests are changed (transport enforced by overwrite option) -...resource 0 testdata[PlainText] (overwrite) -...resource 1 artifact[ociImage](ocm/value:v2.0) (already present) -...adding component version... -`)) - tcv = Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) - nested.Close(tcv, "target cv") - Expect(tcd).To(DeepEqual(tcv.GetDescriptor())) - MustBeSuccessful(nested.Finalize()) - }, - Entry("without preserve global", - "{\"localReference\":\"%s\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\""+OCINAMESPACE+":"+OCIVERSION+"\",\"type\":\"localBlob\"}", - false), - Entry("with preserve global", - "{\"globalAccess\":{\"imageReference\":\"alias.alias/ocm/value:v2.0\",\"type\":\"ociArtifact\"},\"localReference\":\"%s\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\"ocm/value:v2.0\",\"type\":\"localBlob\"}", - false, - standard.KeepGlobalAccess()), - - Entry("with composition and without preserve global", - "{\"localReference\":\"%s\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\""+OCINAMESPACE+":"+OCIVERSION+"\",\"type\":\"localBlob\"}", - true), - Entry("with composition and with preserve global", - "{\"globalAccess\":{\"imageReference\":\"alias.alias/ocm/value:v2.0\",\"type\":\"ociArtifact\"},\"localReference\":\"%s\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\"ocm/value:v2.0\",\"type\":\"localBlob\"}", - true, - standard.KeepGlobalAccess()), - ) - - DescribeTable("it should copy a resource by value to a ctf file for re-transport", func(acc string, mode bool, topts ...transferhandler.TransferOption) { - compositionmodeattr.Set(env.OCMContext(), mode) - src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) - defer Close(src, "source") - cv := Must(src.LookupComponentVersion(COMPONENT, VERSION)) - defer Close(cv, "source cv") - tgt := Must(ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) - defer Close(tgt, "target") - - // transfer by reference, first - p, buf := common.NewBufferedPrinter() - opts := append(topts, transfer.WithPrinter(p), &optionsChecker{}) - MustBeSuccessful(transfer.Transfer(cv, tgt, opts...)) - Expect(env.DirExists(OUT)).To(BeTrue()) - Expect(string(buf.Bytes())).To(StringEqualTrimmedWithContext(` -transferring version "github.com/mandelsoft/test:v1"... -...resource 0 testdata[PlainText]... -...adding component version... -`)) - var nested finalizer.Finalizer - defer Defer(nested.Finalize) - - list := Must(tgt.ComponentLister().GetComponents("", true)) - Expect(list).To(Equal([]string{COMPONENT})) - tcv := Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) - nested.Close(tcv, "target cv") - Expect(len(tcv.GetDescriptor().Resources)).To(Equal(2)) - data := Must(json.Marshal(tcv.GetDescriptor().Resources[1].Access)) - - tcd := tcv.GetDescriptor().Copy() - r := Must(tcv.GetResourceByIndex(1)) - Expect(Must(r.Access()).GetType()).To(Equal(ociartifact.Type)) - MustBeSuccessful(nested.Finalize()) - - // retransfer with value transport - buf.Reset() - opts = append(topts, standard.ResourcesByValue(), transfer.WithPrinter(p), &optionsChecker{}) - MustBeSuccessful(transfer.Transfer(cv, tgt, opts...)) - Expect(env.DirExists(OUT)).To(BeTrue()) - Expect(string(buf.Bytes())).To(StringEqualTrimmedWithContext(` -transferring version "github.com/mandelsoft/test:v1"... - version "github.com/mandelsoft/test:v1" already present -> but requires resource transport -...resource 0 testdata[PlainText] (already present) -...resource 1 artifact[ociImage](ocm/value:v2.0) (copy) -...adding component version... -`)) - - list = Must(tgt.ComponentLister().GetComponents("", true)) - Expect(list).To(Equal([]string{COMPONENT})) - tcv = Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) - nested.Close(tcv, "target cv") - Expect(len(tcv.GetDescriptor().Resources)).To(Equal(2)) - data = Must(json.Marshal(tcv.GetDescriptor().Resources[1].Access)) - - fmt.Printf("%s\n", string(data)) - hash := HashManifest1(artifactset.DefaultArtifactSetDescriptorFileName) - Expect(string(data)).To(StringEqualWithContext(fmt.Sprintf(acc, hash))) - - tcd = tcv.GetDescriptor().Copy() - r = Must(tcv.GetResourceByIndex(1)) - meth := Must(r.AccessMethod()) - nested.Close(meth, "method") - reader := Must(meth.Reader()) - nested.Close(reader, "reader") - set := Must(artifactset.Open(accessobj.ACC_READONLY, "", 0, accessio.Reader(reader))) - nested.Close(set, "set") - - _, blob := Must2(set.GetBlobData(ldesc.Digest)) - nested.Close(blob) - data = Must(blob.Get()) - Expect(string(data)).To(Equal("manifestlayer")) - - MustBeSuccessful(nested.Finalize()) - - // retransfer - buf.Reset() - MustBeSuccessful(transfer.Transfer(cv, tgt, opts...)) - Expect(string(buf.Bytes())).To(StringEqualTrimmedWithContext(` -transferring version "github.com/mandelsoft/test:v1"... - version "github.com/mandelsoft/test:v1" already present -> skip transport`)) - tcv = Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) - nested.Close(tcv, "target cv") - Expect(tcd).To(DeepEqual(tcv.GetDescriptor())) - MustBeSuccessful(nested.Finalize()) - - // modify volatile - cv.GetDescriptor().Labels.Set("new", "newvalue") - tcd.Labels.Set("new", "newvalue") - buf.Reset() - MustBeSuccessful(transfer.Transfer(cv, tgt, opts...)) - Expect(string(buf.Bytes())).To(StringEqualTrimmedWithContext(` -transferring version "github.com/mandelsoft/test:v1"... - updating volatile properties of "github.com/mandelsoft/test:v1" -...adding component version... -`)) - tcv = Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) - nested.Close(tcv, "target cv") - Expect(tcd).To(DeepEqual(tcv.GetDescriptor())) - MustBeSuccessful(nested.Finalize()) - - // modify one artifact and overwrite - MustBeSuccessful(cv.SetResourceBlob(Must(cv.GetResourceByIndex(0)).Meta().Fresh(), blobaccess.ForString(mime.MIME_TEXT, "otherdata"), "", nil)) - tcd.Resources[0].Digest = DS_OTHERDATA - tcd.Resources[0].Access = Must(runtime.ToUnstructuredVersionedTypedObject(localblob.New("sha256:"+D_OTHERDATA, "", mime.MIME_TEXT, nil))) - buf.Reset() - transfer.Breakpoints = true - MustBeSuccessful(transfer.Transfer(cv, tgt, append(opts, standard.ResourcesByValue(), standard.Overwrite())...)) - transfer.Breakpoints = false - Expect(string(buf.Bytes())).To(StringEqualTrimmedWithContext(` -transferring version "github.com/mandelsoft/test:v1"... -warning: version "github.com/mandelsoft/test:v1" already present, but differs because some artifact digests are changed (transport enforced by overwrite option) -...resource 0 testdata[PlainText] (overwrite) -...resource 1 artifact[ociImage](ocm/value:v2.0) (already present) -...adding component version... -`)) - tcv = Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) - nested.Close(tcv, "target cv") - ntcd := tcv.GetDescriptor() - Expect(tcd).To(DeepEqual(ntcd)) - MustBeSuccessful(nested.Finalize()) - }, - Entry("without preserve global", - "{\"localReference\":\"%s\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\""+OCINAMESPACE+":"+OCIVERSION+"\",\"type\":\"localBlob\"}", - false), - Entry("with preserve global", - "{\"globalAccess\":{\"imageReference\":\"alias.alias/ocm/value:v2.0\",\"type\":\"ociArtifact\"},\"localReference\":\"%s\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\"ocm/value:v2.0\",\"type\":\"localBlob\"}", - false, - standard.KeepGlobalAccess()), - Entry("with composition and without preserve global", - "{\"localReference\":\"%s\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\""+OCINAMESPACE+":"+OCIVERSION+"\",\"type\":\"localBlob\"}", - true), - Entry("with composition and with preserve global", - "{\"globalAccess\":{\"imageReference\":\"alias.alias/ocm/value:v2.0\",\"type\":\"ociArtifact\"},\"localReference\":\"%s\",\"mediaType\":\"application/vnd.oci.image.manifest.v1+tar+gzip\",\"referenceName\":\"ocm/value:v2.0\",\"type\":\"localBlob\"}", - true, - standard.KeepGlobalAccess()), - ) - - It("disable value transport of oci access", func() { - src, err := ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env) - Expect(err).To(Succeed()) - cv, err := src.LookupComponentVersion(COMPONENT, VERSION) - Expect(err).To(Succeed()) - tgt, err := ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env) - Expect(err).To(Succeed()) - defer tgt.Close() - - opts := &standard.Options{} - Expect(opts.Apply(standard.ResourcesByValue(), standard.OmitAccessTypes(ociartifact.Type))).To(Succeed()) - Expect(opts.IsResourcesByValue()).To(BeTrue()) - Expect(opts.IsAccessTypeOmitted(ociartifact.Type)).To(BeTrue()) - Expect(opts.IsAccessTypeOmitted(ociartifact.LegacyType)).To(BeFalse()) - - handler := standard.NewDefaultHandler(opts) - Expect(err).To(Succeed()) - err = transfer.TransferVersion(nil, nil, cv, tgt, handler) - Expect(err).To(Succeed()) - Expect(env.DirExists(OUT)).To(BeTrue()) - - list, err := tgt.ComponentLister().GetComponents("", true) - Expect(err).To(Succeed()) - Expect(list).To(Equal([]string{COMPONENT})) - comp, err := tgt.LookupComponentVersion(COMPONENT, VERSION) - Expect(err).To(Succeed()) - Expect(len(comp.GetDescriptor().Resources)).To(Equal(2)) - - r, err := comp.GetResourceByIndex(1) - Expect(err).To(Succeed()) - - a, err := r.Access() - Expect(err).To(Succeed()) - Expect(a.GetType()).To(Equal(ociartifact.Type)) - }) - - It("it should use additional resolver to resolve component ref", func() { - parentSrc, err := ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH2, 0, env) - Expect(err).To(Succeed()) - cv, err := parentSrc.LookupComponentVersion(COMPONENT2, VERSION) - Expect(err).To(Succeed()) - childSrc, err := ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env) - Expect(err).To(Succeed()) - tgt, err := ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env) - Expect(err).To(Succeed()) - defer tgt.Close() - handler, err := standard.New(standard.Recursive(), standard.Resolver(childSrc)) - Expect(err).To(Succeed()) - err = transfer.TransferVersion(nil, nil, cv, tgt, handler) - Expect(err).To(Succeed()) - Expect(env.DirExists(OUT)).To(BeTrue()) - - list, err := tgt.ComponentLister().GetComponents("", true) - Expect(err).To(Succeed()) - Expect(list).To(ContainElements([]string{COMPONENT2, COMPONENT})) - _, err = tgt.LookupComponentVersion(COMPONENT2, VERSION) - Expect(err).To(Succeed()) - _, err = tgt.LookupComponentVersion(COMPONENT, VERSION) - Expect(err).To(Succeed()) - }) - - It("it should copy signatures", func() { - src, err := ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env) - Expect(err).To(Succeed()) - cv, err := src.LookupComponentVersion(COMPONENT, VERSION) - Expect(err).To(Succeed()) - - resolver := ocm.NewCompoundResolver(src) - - opts := ocmsign.NewOptions( - ocmsign.Sign(signingattr.Get(env.OCMContext()).GetSigner(SIGN_ALGO), SIGNATURE), - ocmsign.Resolver(resolver), - ocmsign.Update(), ocmsign.VerifyDigests(), - ) - Expect(opts.Complete(env.OCMContext())).To(Succeed()) - digest := "45aefd9317bde6c66d5edca868cf7b9a5313a6f965609af4e58bbfd44ae6e92c" - dig, err := ocmsign.Apply(nil, nil, cv, opts) - Expect(err).To(Succeed()) - fmt.Print(dig.Value) - Expect(dig.Value).To(Equal(digest)) - - Expect(len(cv.GetDescriptor().Signatures)).To(Equal(1)) - - tgt, err := ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env) - Expect(err).To(Succeed()) - defer tgt.Close() - handler, err := standard.New(standard.ResourcesByValue()) - Expect(err).To(Succeed()) - err = transfer.TransferVersion(nil, nil, cv, tgt, handler) - Expect(err).To(Succeed()) - Expect(env.DirExists(OUT)).To(BeTrue()) - - resolver = ocm.NewCompoundResolver(tgt) - - opts = ocmsign.NewOptions( - ocmsign.Resolver(resolver), - ocmsign.VerifySignature(SIGNATURE), - ocmsign.Update(), ocmsign.VerifyDigests(), - ) - Expect(opts.Complete(env.OCMContext())).To(Succeed()) - dig, err = ocmsign.Apply(nil, nil, cv, opts) - Expect(err).To(Succeed()) - Expect(dig.Value).To(Equal(digest)) - }) -}) diff --git a/pkg/contexts/ocm/transfer/transferhandler/standard/options.go b/pkg/contexts/ocm/transfer/transferhandler/standard/options.go deleted file mode 100644 index c22a972dd..000000000 --- a/pkg/contexts/ocm/transfer/transferhandler/standard/options.go +++ /dev/null @@ -1,662 +0,0 @@ -package standard - -import ( - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/maputils" - "github.com/mandelsoft/goutils/optionutils" - "github.com/mandelsoft/goutils/set" - "github.com/mandelsoft/goutils/sliceutils" - "golang.org/x/exp/slices" - - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler" - "github.com/open-component-model/ocm/pkg/runtime" -) - -func init() { - transferhandler.RegisterHandler(1000, &TransferOptionsCreator{}) -} - -type Options struct { - retries *int - recursive *bool - resourcesByValue *bool - localByValue *bool - sourcesByValue *bool - keepGlobalAccess *bool - stopOnExisting *bool - enforceTransport *bool - overwrite *bool - skipUpdate *bool - omitAccessTypes set.Set[string] - omitArtifactTypes set.Set[string] - resolver ocm.ComponentVersionResolver -} - -var ( - _ transferhandler.TransferOption = (*Options)(nil) - - _ RetryOption = (*Options)(nil) - _ ResourcesByValueOption = (*Options)(nil) - _ LocalResourcesByValueOption = (*Options)(nil) - _ EnforceTransportOption = (*Options)(nil) - _ OverwriteOption = (*Options)(nil) - _ SkipUpdateOption = (*Options)(nil) - _ SourcesByValueOption = (*Options)(nil) - _ RecursiveOption = (*Options)(nil) - _ ResolverOption = (*Options)(nil) - _ KeepGlobalAccessOption = (*Options)(nil) - _ OmitAccessTypesOption = (*Options)(nil) - _ OmitArtifactTypesOption = (*Options)(nil) -) - -type TransferOptionsCreator = transferhandler.SpecializedOptionsCreator[*Options, Options] - -func (o *Options) NewOptions() transferhandler.TransferHandlerOptions { - return &Options{} -} - -func (o *Options) NewTransferHandler() (transferhandler.TransferHandler, error) { - return New(o) -} - -func (o *Options) ApplyTransferOption(target transferhandler.TransferOptions) error { - if o.retries != nil { - if opts, ok := target.(RetryOption); ok { - opts.SetRetries(*o.retries) - } - } - if o.recursive != nil { - if opts, ok := target.(RecursiveOption); ok { - opts.SetRecursive(*o.recursive) - } - } - if o.skipUpdate != nil { - if opts, ok := target.(SkipUpdateOption); ok { - opts.SetSkipUpdate(*o.skipUpdate) - } - } - if o.resourcesByValue != nil { - if opts, ok := target.(ResourcesByValueOption); ok { - opts.SetResourcesByValue(*o.resourcesByValue) - } - } - if o.localByValue != nil { - if opts, ok := target.(LocalResourcesByValueOption); ok { - opts.SetLocalResourcesByValue(*o.localByValue) - } - } - if o.sourcesByValue != nil { - if opts, ok := target.(SourcesByValueOption); ok { - opts.SetSourcesByValue(*o.sourcesByValue) - } - } - if o.keepGlobalAccess != nil { - if opts, ok := target.(KeepGlobalAccessOption); ok { - opts.SetKeepGlobalAccess(*o.keepGlobalAccess) - } - } - if o.stopOnExisting != nil { - if opts, ok := target.(StopOnExistingVersionOption); ok { - opts.SetStopOnExistingVersion(*o.stopOnExisting) - } - } - if o.enforceTransport != nil { - if opts, ok := target.(EnforceTransportOption); ok { - opts.SetEnforceTransport(*o.enforceTransport) - } - } - if o.overwrite != nil { - if opts, ok := target.(OverwriteOption); ok { - opts.SetOverwrite(*o.overwrite) - } - } - if o.omitAccessTypes != nil { - if opts, ok := target.(OmitAccessTypesOption); ok { - opts.SetOmittedAccessTypes(maputils.OrderedKeys(o.omitAccessTypes)...) - } - } - if o.omitArtifactTypes != nil { - if opts, ok := target.(OmitArtifactTypesOption); ok { - opts.SetOmittedArtifactTypes(maputils.OrderedKeys(o.omitAccessTypes)...) - } - } - if o.resolver != nil { - if opts, ok := target.(ResolverOption); ok { - opts.SetResolver(o.resolver) - } - } - return nil -} - -func (o *Options) Apply(opts ...transferhandler.TransferOption) error { - return transferhandler.ApplyOptions(o, opts...) -} - -func (o *Options) SetEnforceTransport(enforce bool) { - o.enforceTransport = &enforce -} - -func (o *Options) IsTransportEnforced() bool { - return optionutils.AsBool(o.enforceTransport) -} - -func (o *Options) SetOverwrite(overwrite bool) { - o.overwrite = &overwrite -} - -func (o *Options) IsOverwrite() bool { - return optionutils.AsBool(o.overwrite) -} - -func (o *Options) SetSkipUpdate(skipupdate bool) { - o.skipUpdate = &skipupdate -} - -func (o *Options) IsSkipUpdate() bool { - return optionutils.AsBool(o.skipUpdate) -} - -func (o *Options) SetRecursive(recursive bool) { - o.recursive = &recursive -} - -func (o *Options) IsRecursive() bool { - return optionutils.AsBool(o.recursive) -} - -func (o *Options) SetResourcesByValue(resourcesByValue bool) { - o.resourcesByValue = &resourcesByValue -} - -func (o *Options) IsResourcesByValue() bool { - return optionutils.AsBool(o.resourcesByValue) -} - -func (o *Options) SetLocalResourcesByValue(resourcesByValue bool) { - o.localByValue = &resourcesByValue -} - -func (o *Options) IsLocalResourcesByValue() bool { - return optionutils.AsBool(o.localByValue) -} - -func (o *Options) SetSourcesByValue(sourcesByValue bool) { - o.sourcesByValue = &sourcesByValue -} - -func (o *Options) IsSourcesByValue() bool { - return optionutils.AsBool(o.sourcesByValue) -} - -func (o *Options) SetKeepGlobalAccess(keepGlobalAccess bool) { - o.keepGlobalAccess = &keepGlobalAccess -} - -func (o *Options) IsKeepGlobalAccess() bool { - return optionutils.AsBool(o.keepGlobalAccess) -} - -func (o *Options) SetRetries(retries int) { - o.retries = &retries -} - -func (o *Options) GetRetries() int { - if o.retries == nil { - return 0 - } - return *o.retries -} - -func (o *Options) SetResolver(resolver ocm.ComponentVersionResolver) { - o.resolver = resolver -} - -func (o *Options) GetResolver() ocm.ComponentVersionResolver { - return o.resolver -} - -func (o *Options) SetStopOnExistingVersion(stopOnExistingVersion bool) { - o.stopOnExisting = &stopOnExistingVersion -} - -func (o *Options) IsStopOnExistingVersion() bool { - return optionutils.AsBool(o.stopOnExisting) -} - -func (o *Options) SetOmittedAccessTypes(list ...string) { - o.omitAccessTypes = set.New[string]() - for _, t := range list { - o.omitAccessTypes.Add(t) - } -} - -func (o *Options) AddOmittedAccessTypes(list ...string) { - if o.omitAccessTypes == nil { - o.omitAccessTypes = set.New[string]() - } - for _, t := range list { - o.omitAccessTypes.Add(t) - } -} - -func (o *Options) GetOmittedAccessTypes() []string { - if o.omitAccessTypes == nil { - return nil - } - return maputils.OrderedKeys(o.omitAccessTypes) -} - -func (o *Options) IsAccessTypeOmitted(t string) bool { - if o.omitAccessTypes == nil { - return false - } - if o.omitAccessTypes.Contains(t) { - return true - } - k, _ := runtime.KindVersion(t) - return o.omitAccessTypes.Contains(k) -} - -func (o *Options) SetOmittedArtifactTypes(list ...string) { - o.omitArtifactTypes = set.New[string]() - for _, t := range list { - o.omitArtifactTypes.Add(t) - } -} - -func (o *Options) AddOmittedArtifactTypes(list ...string) { - if o.omitArtifactTypes == nil { - o.omitArtifactTypes = set.New[string]() - } - for _, t := range list { - o.omitArtifactTypes.Add(t) - } -} - -func (o *Options) GetOmittedArtifactTypes() []string { - if o.omitArtifactTypes == nil { - return nil - } - return maputils.OrderedKeys(o.omitArtifactTypes) -} - -func (o *Options) IsArtifactTypeOmitted(t string) bool { - if o.omitArtifactTypes == nil { - return false - } - if o.omitArtifactTypes.Contains(t) { - return true - } - return o.omitArtifactTypes.Contains(t) -} - -////////////////////////////////////////////////////////////////////////////// - -type EnforceTransportOption interface { - SetEnforceTransport(bool) - IsTransportEnforced() bool -} - -type enforceTransportOption struct { - TransferOptionsCreator - enforce bool -} - -func (o *enforceTransportOption) ApplyTransferOption(to transferhandler.TransferOptions) error { - if eff, ok := to.(EnforceTransportOption); ok { - eff.SetEnforceTransport(o.enforce) - return nil - } else { - return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "enforceTransport") - } -} - -// EnforceTransport enforces a transport of a component version as it is. -// This controls whether transport is carried out -// as if the component version were not present at the destination. -func EnforceTransport(args ...bool) transferhandler.TransferOption { - return &enforceTransportOption{ - enforce: optionutils.GetOptionFlag(args...), - } -} - -/////////////////////////////////////////////////////////////////////////////// - -type OverwriteOption interface { - SetOverwrite(bool) - IsOverwrite() bool -} - -type overwriteOption struct { - TransferOptionsCreator - overwrite bool -} - -func (o *overwriteOption) ApplyTransferOption(to transferhandler.TransferOptions) error { - if eff, ok := to.(OverwriteOption); ok { - eff.SetOverwrite(o.overwrite) - return nil - } else { - return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "overwrite") - } -} - -// Overwrite enables the modification of digest relevant information in a component version. -func Overwrite(args ...bool) transferhandler.TransferOption { - return &overwriteOption{ - overwrite: optionutils.GetOptionFlag(args...), - } -} - -/////////////////////////////////////////////////////////////////////////////// - -type SkipUpdateOption interface { - SetSkipUpdate(bool) - IsSkipUpdate() bool -} - -type skipUpdateOption bool - -func (o skipUpdateOption) ApplyTransferOption(to transferhandler.TransferOptions) error { - if eff, ok := to.(SkipUpdateOption); ok { - eff.SetSkipUpdate(bool(o)) - return nil - } else { - return errors.ErrNotSupported("skip-update") - } -} - -// SkipUpdate enables the modification of non-digest (volatile) relevant information in a component version. -func SkipUpdate(args ...bool) transferhandler.TransferOption { - return skipUpdateOption(optionutils.GetOptionFlag(args...)) -} - -/////////////////////////////////////////////////////////////////////////////// - -type RetryOption interface { - SetRetries(n int) - GetRetries() int -} - -type retryOption struct { - TransferOptionsCreator - retries int -} - -func (o *retryOption) ApplyTransferOption(to transferhandler.TransferOptions) error { - if eff, ok := to.(RetryOption); ok { - eff.SetRetries(o.retries) - return nil - } else { - return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "retry") - } -} - -// Retries sets the number of retries for failing update operations. -func Retries(retries int) transferhandler.TransferOption { - return &retryOption{retries: retries} -} - -/////////////////////////////////////////////////////////////////////////////// - -type RecursiveOption interface { - SetRecursive(bool) - IsRecursive() bool -} - -type recursiveOption struct { - TransferOptionsCreator - flag bool -} - -func (o *recursiveOption) ApplyTransferOption(to transferhandler.TransferOptions) error { - if eff, ok := to.(RecursiveOption); ok { - eff.SetRecursive(o.flag) - return nil - } else { - return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "recursive") - } -} - -// Recursive enables the transport of the reference closure of a component version. -func Recursive(args ...bool) transferhandler.TransferOption { - return &recursiveOption{flag: optionutils.GetOptionFlag(args...)} -} - -/////////////////////////////////////////////////////////////////////////////// - -type ResourcesByValueOption interface { - SetResourcesByValue(bool) - IsResourcesByValue() bool -} - -type resourcesByValueOption struct { - TransferOptionsCreator - flag bool -} - -func (o *resourcesByValueOption) ApplyTransferOption(to transferhandler.TransferOptions) error { - if eff, ok := to.(ResourcesByValueOption); ok { - eff.SetResourcesByValue(o.flag) - return nil - } else { - return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "resources-by-value") - } -} - -// ResourcesByValue enables the transport a resources by values instead of by-reference. -func ResourcesByValue(args ...bool) transferhandler.TransferOption { - return &resourcesByValueOption{flag: optionutils.GetOptionFlag(args...)} -} - -/////////////////////////////////////////////////////////////////////////////// - -type LocalResourcesByValueOption interface { - SetLocalResourcesByValue(bool) - IsLocalResourcesByValue() bool -} - -type intrscsByValueOption struct { - TransferOptionsCreator - flag bool -} - -func (o *intrscsByValueOption) ApplyTransferOption(to transferhandler.TransferOptions) error { - if eff, ok := to.(LocalResourcesByValueOption); ok { - eff.SetLocalResourcesByValue(o.flag) - return nil - } else { - return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "local-resources by-value") - } -} - -// LocalResourcesByValue enables the transport a local (relation) resources by values instead of by-reference. -func LocalResourcesByValue(args ...bool) transferhandler.TransferOption { - return &intrscsByValueOption{flag: optionutils.GetOptionFlag(args...)} -} - -/////////////////////////////////////////////////////////////////////////////// - -type SourcesByValueOption interface { - SetSourcesByValue(bool) - IsSourcesByValue() bool -} - -type sourcesByValueOption struct { - TransferOptionsCreator - flag bool -} - -func (o *sourcesByValueOption) ApplyTransferOption(to transferhandler.TransferOptions) error { - if eff, ok := to.(SourcesByValueOption); ok { - eff.SetSourcesByValue(o.flag) - return nil - } else { - return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "sources by-value") - } -} - -// SourcesByValue enables the transport a sources by values instead of by-reference. -func SourcesByValue(args ...bool) transferhandler.TransferOption { - return &sourcesByValueOption{flag: optionutils.GetOptionFlag(args...)} -} - -/////////////////////////////////////////////////////////////////////////////// - -type ResolverOption interface { - GetResolver() ocm.ComponentVersionResolver - SetResolver(ocm.ComponentVersionResolver) -} - -type resolverOption struct { - TransferOptionsCreator - resolver ocm.ComponentVersionResolver -} - -func (o *resolverOption) ApplyTransferOption(to transferhandler.TransferOptions) error { - if eff, ok := to.(ResolverOption); ok { - eff.SetResolver(o.resolver) - return nil - } else { - return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "resolver") - } -} - -// Resolver specifies a resolver used to resolve nested component versions. -func Resolver(resolver ocm.ComponentVersionResolver) transferhandler.TransferOption { - return &resolverOption{ - resolver: resolver, - } -} - -/////////////////////////////////////////////////////////////////////////////// - -type KeepGlobalAccessOption interface { - SetKeepGlobalAccess(bool) - IsKeepGlobalAccess() bool -} - -type keepGlobalOption struct { - TransferOptionsCreator - flag bool -} - -func (o *keepGlobalOption) ApplyTransferOption(to transferhandler.TransferOptions) error { - if eff, ok := to.(KeepGlobalAccessOption); ok { - eff.SetKeepGlobalAccess(o.flag) - return nil - } else { - return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "keep-global-access") - } -} - -// KeepGlobalAccess enables to keep local blobs if uploaders are used to upload imported blobs. -func KeepGlobalAccess(args ...bool) transferhandler.TransferOption { - return &keepGlobalOption{flag: optionutils.GetOptionFlag(args...)} -} - -/////////////////////////////////////////////////////////////////////////////// - -type StopOnExistingVersionOption interface { - SetStopOnExistingVersion(bool) - IsStopOnExistingVersion() bool -} - -type stopOnExistingVersionOption struct { - TransferOptionsCreator - flag bool -} - -func (o *stopOnExistingVersionOption) ApplyTransferOption(to transferhandler.TransferOptions) error { - if eff, ok := to.(StopOnExistingVersionOption); ok { - eff.SetStopOnExistingVersion(o.flag) - return nil - } else { - return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "stop-on-existing") - } -} - -// StopOnExistingVersion stops the recursion on component versions already present in target. -func StopOnExistingVersion(args ...bool) transferhandler.TransferOption { - return &stopOnExistingVersionOption{flag: optionutils.GetOptionFlag(args...)} -} - -/////////////////////////////////////////////////////////////////////////////// - -type OmitAccessTypesOption interface { - SetOmittedAccessTypes(...string) - GetOmittedAccessTypes() []string -} - -type omitAccessTypesOption struct { - TransferOptionsCreator - add bool - list []string -} - -func (o *omitAccessTypesOption) ApplyTransferOption(to transferhandler.TransferOptions) error { - if eff, ok := to.(OmitAccessTypesOption); ok { - if o.add { - eff.SetOmittedAccessTypes(sliceutils.CopyAppend(eff.GetOmittedAccessTypes(), o.list...)...) - } else { - eff.SetOmittedAccessTypes(o.list...) - } - return nil - } else { - return errors.ErrNotSupported(transferhandler.KIND_TRANSFEROPTION, "omit-access-types") - } -} - -// OmitAccessTypes somits the specified access types from value transport. -func OmitAccessTypes(list ...string) transferhandler.TransferOption { - return &omitAccessTypesOption{ - list: slices.Clone(list), - } -} - -func AddOmittedAccessTypes(list ...string) transferhandler.TransferOption { - return &omitAccessTypesOption{ - add: true, - list: slices.Clone(list), - } -} - -/////////////////////////////////////////////////////////////////////////////// - -type OmitArtifactTypesOption interface { - SetOmittedArtifactTypes(...string) - GetOmittedArtifactTypes() []string -} - -type omitArtifactTypesOption struct { - add bool - list []string -} - -func (o *omitArtifactTypesOption) ApplyTransferOption(to transferhandler.TransferOptions) error { - if eff, ok := to.(OmitAccessTypesOption); ok { - if o.add { - eff.SetOmittedAccessTypes(append(eff.GetOmittedAccessTypes(), o.list...)...) - } else { - eff.SetOmittedAccessTypes(o.list...) - } - return nil - } else { - return errors.ErrNotSupported("omit-artifact-types") - } -} - -// OmitArtifactTypes somits the specified artifact types from value transport. -func OmitArtifactTypes(list ...string) transferhandler.TransferOption { - return &omitArtifactTypesOption{ - list: slices.Clone(list), - } -} - -func AddOmittedArtifactTypes(list ...string) transferhandler.TransferOption { - return &omitArtifactTypesOption{ - add: true, - list: slices.Clone(list), - } -} diff --git a/pkg/contexts/ocm/transfer/transferhandler/standard/utils_test.go b/pkg/contexts/ocm/transfer/transferhandler/standard/utils_test.go deleted file mode 100644 index ea7cc3503..000000000 --- a/pkg/contexts/ocm/transfer/transferhandler/standard/utils_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package standard_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/mandelsoft/goutils/generics" - - me "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" -) - -type Target struct { - flag *bool - text string -} - -////// - -var _ Flag = (*Target)(nil) - -type Flag interface { - SetFlag(b bool) -} - -func (t *Target) SetFlag(b bool) { - t.flag = &b -} - -////// - -var _ Text = (*Target)(nil) - -type Text interface { - SetText(v string) -} - -func (t *Target) SetText(v string) { - t.text = v -} - -var _ = Describe("utils", func() { - It("handles pointer arg", func() { - var v *bool - t := &Target{} - - me.HandleOption[Flag](v, t) - Expect(t.flag).To(BeNil()) - - v = generics.Pointer(false) - me.HandleOption[Flag](v, t) - Expect(t.flag).NotTo(BeNil()) - Expect(*t.flag).To(BeFalse()) - }) - - It("handles value arg", func() { - var v string - t := &Target{text: "old"} - - me.HandleOption[Text](v, t) - Expect(t.text).To(Equal("old")) - - v = "test" - me.HandleOption[Text](v, t) - Expect(t.text).To(Equal("test")) - }) -}) diff --git a/pkg/contexts/ocm/utils.go b/pkg/contexts/ocm/utils.go deleted file mode 100644 index 6eefbd60e..000000000 --- a/pkg/contexts/ocm/utils.go +++ /dev/null @@ -1,126 +0,0 @@ -package ocm - -import ( - "fmt" - "io" - "strings" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/common" - "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/ocm/compdesc" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -//////////////////////////////////////////////////////////////////////////////// - -func AssureTargetRepository(session Session, ctx Context, targetref string, opts ...interface{}) (Repository, error) { - var format accessio.FileFormat - var archive string - var fs vfs.FileSystem - - for _, o := range opts { - switch v := o.(type) { - case vfs.FileSystem: - if fs == nil && v != nil { - fs = v - } - case accessio.FileFormat: - format = v - case string: - archive = v - default: - return nil, fmt.Errorf("invalid option type %T", o) - } - } - - ref, err := ParseRepo(targetref) - if err != nil { - return nil, err - } - if ref.TypeHint == "" { - ref.TypeHint = archive - } - if format != "" && ref.TypeHint != "" && !strings.Contains(ref.TypeHint, "+") { - for _, f := range ctf.SupportedFormats() { - if f == format { - ref.TypeHint += "+" + format.String() - } - } - } - ref.CreateIfMissing = true - target, err := session.DetermineRepositoryBySpec(ctx, &ref) - if err != nil { - if !errors.IsErrUnknown(err) || vfs.IsErrNotExist(err) || ref.Info == "" { - return nil, err - } - if ref.Type == "" { - ref.Type = format.String() - } - if ref.Type == "" { - return nil, fmt.Errorf("ctf format type required to create ctf") - } - target, err = ctf.Create(ctx, accessobj.ACC_CREATE, ref.Info, 0o770, accessio.PathFileSystem(utils.FileSystem(fs))) - if err != nil { - return nil, err - } - session.Closer(target) - } - return target, nil -} - -type AccessMethodSource = cpi.AccessMethodSource - -// ResourceReader gets a Reader for a given resource/source access. -// It provides a Reader handling the Close contract for the access method -// by connecting the access method's Close method to the Readers Close method . -// Deprecated: use ocmutils.GetResourceReader. -// It must be deprecated because of the support of free-floating ReSourceAccess -// implementations, they not necessarily provide an AccessMethod. -func ResourceReader(s AccessMethodSource) (io.ReadCloser, error) { - return cpi.ResourceReader(s) -} - -func IsIntermediate(spec RepositorySpec) bool { - if s, ok := spec.(IntermediateRepositorySpecAspect); ok { - return s.IsIntermediate() - } - return false -} - -func ComponentRefKey(ref *compdesc.ComponentReference) common.NameVersion { - return common.NewNameVersion(ref.GetComponentName(), ref.GetVersion()) -} - -func IsUnknownRepositorySpec(s RepositorySpec) bool { - return runtime.IsUnknown(s) -} - -func IsUnknownAccessSpec(s AccessSpec) bool { - return runtime.IsUnknown(s) -} - -func WrapContextProvider(ctx LocalContextProvider) ContextProvider { - return internal.WrapContextProvider(ctx) -} - -func ReferenceHint(spec AccessSpec, cv ComponentVersionAccess) string { - if h, ok := spec.(internal.HintProvider); ok { - return h.GetReferenceHint(cv) - } - return "" -} - -func GlobalAccess(spec AccessSpec, ctx Context) AccessSpec { - if h, ok := spec.(internal.GlobalAccessProvider); ok { - return h.GlobalAccessSpec(ctx) - } - return nil -} diff --git a/pkg/contexts/ocm/utils/check/check.go b/pkg/contexts/ocm/utils/check/check.go deleted file mode 100644 index 7eef618a2..000000000 --- a/pkg/contexts/ocm/utils/check/check.go +++ /dev/null @@ -1,148 +0,0 @@ -package check - -import ( - "encoding/json" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/optionutils" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" -) - -type Result struct { - Missing Missing `json:"missing,omitempty"` - Resources []metav1.Identity `json:"resources,omitempty"` - Sources []metav1.Identity `json:"sources,omitempty"` -} - -func newResult() *Result { - return &Result{Missing: Missing{}} -} - -func (r *Result) IsEmpty() bool { - if r == nil { - return true - } - return len(r.Missing) == 0 && len(r.Resources) == 0 && len(r.Sources) == 0 -} - -type Missing map[common.NameVersion]common.History - -func (n Missing) MarshalJSON() ([]byte, error) { - m := map[string]common.History{} - for k, v := range n { - m[k.String()] = v - } - return json.Marshal(m) -} - -type Cache = map[common.NameVersion]*Result - -//////////////////////////////////////////////////////////////////////////////// - -// Check provides a check object for checking component versions -// to completely available in an ocm repository. -// By default, it only checks the component reference closure -// to be in the same repository. -// Optionally, it is possible to check for inlined -// resources and sources, also. -func Check(opts ...Option) *Options { - return optionutils.EvalOptions(opts...) -} - -func (a *Options) For(cv ocm.ComponentVersionAccess) (*Result, error) { - cache := Cache{} - return a.handle(cache, cv, common.History{common.VersionedElementKey(cv)}) -} - -func (a *Options) ForId(repo ocm.Repository, id common.NameVersion) (*Result, error) { - cv, err := repo.LookupComponentVersion(id.GetName(), id.GetVersion()) - if err != nil { - return nil, err - } - defer cv.Close() - return a.For(cv) -} - -func (a *Options) check(cache Cache, repo ocm.Repository, id common.NameVersion, h common.History) (*Result, error) { - if r, ok := cache[id]; ok { - return r, nil - } - - err := h.Add(ocm.KIND_COMPONENTVERSION, id) - if err != nil { - return nil, err - } - cv, err := repo.LookupComponentVersion(id.GetName(), id.GetVersion()) - if err != nil { - if !errors.IsErrNotFound(err) { - return nil, err - } - err = nil - } - - var r *Result - if cv == nil { - r = &Result{Missing: Missing{id: h}} - } else { - defer cv.Close() - r, err = a.handle(cache, cv, h) - } - cache[id] = r - return r, err -} - -func (a *Options) handle(cache Cache, cv ocm.ComponentVersionAccess, h common.History) (*Result, error) { - result := newResult() - - for _, r := range cv.GetDescriptor().References { - id := common.NewNameVersion(r.ComponentName, r.Version) - n, err := a.check(cache, cv.Repository(), id, h) - if err != nil { - return result, err - } - if n != nil && len(n.Missing) > 0 { - for k, v := range n.Missing { - result.Missing[k] = v - } - } - } - - var err error - - list := errors.ErrorList{} - if optionutils.AsBool(a.CheckLocalResources) { - result.Resources, err = a.checkArtifacts(cv.GetContext(), cv.GetDescriptor().Resources) - list.Add(err) - } - if optionutils.AsBool(a.CheckLocalSources) { - result.Sources, err = a.checkArtifacts(cv.GetContext(), cv.GetDescriptor().Sources) - list.Add(err) - } - if result.IsEmpty() { - result = nil - } - return result, list.Result() -} - -func (a *Options) checkArtifacts(ctx ocm.Context, accessor compdesc.ElementAccessor) ([]metav1.Identity, error) { - var result []metav1.Identity - - list := errors.ErrorList{} - for i := 0; i < accessor.Len(); i++ { - e := accessor.Get(i).(compdesc.ElementArtifactAccessor) - - m, err := ctx.AccessSpecForSpec(e.GetAccess()) - if err == nil { - if !m.IsLocal(ctx) { - result = append(result, e.GetMeta().GetIdentity(accessor)) - } - } else { - list.Add(err) - } - } - return result, list.Result() -} diff --git a/pkg/contexts/ocm/utils/check/options.go b/pkg/contexts/ocm/utils/check/options.go deleted file mode 100644 index 055ae93c4..000000000 --- a/pkg/contexts/ocm/utils/check/options.go +++ /dev/null @@ -1,45 +0,0 @@ -package check - -import ( - "github.com/mandelsoft/goutils/optionutils" - - "github.com/open-component-model/ocm/pkg/utils" -) - -type Option = optionutils.Option[*Options] - -type Options struct { - CheckLocalResources *bool - CheckLocalSources *bool -} - -var _ Option = (*Options)(nil) - -func (o *Options) ApplyTo(opts *Options) { - optionutils.ApplyOption(o.CheckLocalResources, &opts.CheckLocalResources) - optionutils.ApplyOption(o.CheckLocalSources, &opts.CheckLocalSources) -} - -//////////////////////////////////////////////////////////////////////////////// - -type localSources bool - -func LocalSourcesOnly(b ...bool) Option { - return localSources(utils.OptionalDefaultedBool(true, b...)) -} - -func (l localSources) ApplyTo(t *Options) { - t.CheckLocalSources = optionutils.PointerTo(bool(l)) -} - -//////////////////////////////////////////////////////////////////////////////// - -type localResources bool - -func LocalResourcesOnly(b ...bool) Option { - return localResources(utils.OptionalDefaultedBool(true, b...)) -} - -func (l localResources) ApplyTo(t *Options) { - t.CheckLocalResources = optionutils.PointerTo(bool(l)) -} diff --git a/pkg/contexts/ocm/utils/configure.go b/pkg/contexts/ocm/utils/configure.go deleted file mode 100644 index 68d047900..000000000 --- a/pkg/contexts/ocm/utils/configure.go +++ /dev/null @@ -1,132 +0,0 @@ -package utils - -import ( - "fmt" - "os" - "strings" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/pkgutils" - "github.com/mandelsoft/spiff/features" - "github.com/mandelsoft/spiff/spiffing" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/contexts/config" - configcfg "github.com/open-component-model/ocm/pkg/contexts/config/config" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils/defaultconfigregistry" - "github.com/open-component-model/ocm/pkg/utils" -) - -const DEFAULT_OCM_CONFIG = ".ocmconfig" - -const DEFAULT_OCM_CONFIG_DIR = ".ocm" - -func Configure(ctxp config.ContextProvider, path string, fss ...vfs.FileSystem) (ocm.Context, error) { - ctx, _, err := Configure2(ctxp, path, fss...) - return ctx, err -} - -func Configure2(ctx config.ContextProvider, path string, fss ...vfs.FileSystem) (ocm.Context, config.Config, error) { - var ocmctx ocm.Context - - cfg, err := configcfg.NewAggregator(false) - if err != nil { - return nil, nil, err - } - fs := utils.FileSystem(fss...) - if ctx == nil { - ocmctx = ocm.DefaultContext() - ctx = ocmctx - } else { - if c, ok := ctx.(ocm.Context); ok { - ocmctx = c - } - } - h, _ := os.UserHomeDir() - if path == "" { - if h != "" { - cfg := h + "/" + DEFAULT_OCM_CONFIG - if ok, err := vfs.FileExists(fs, cfg); ok && err == nil { - path = cfg - } else { - cfg := h + "/" + DEFAULT_OCM_CONFIG_DIR + "/ocmconfig" - if ok, err := vfs.FileExists(fs, cfg); ok && err == nil { - path = cfg - } else { - cfg := h + "/" + DEFAULT_OCM_CONFIG_DIR + "/config" - if ok, err := vfs.FileExists(fs, cfg); ok && err == nil { - path = cfg - } - } - } - } - } - if path != "" && path != "None" { - if strings.HasPrefix(path, "~"+string(os.PathSeparator)) { - if len(h) == 0 { - return nil, nil, fmt.Errorf("no home directory found for resolving path of ocm config file %q", path) - } - path = h + path[1:] - } - data, err := vfs.ReadFile(fs, path) - if err != nil { - return nil, nil, errors.Wrapf(err, "cannot read ocm config file %q", path) - } - - if eff, err := ConfigureByData2(ctx, data, path); err != nil { - return nil, nil, err - } else { - err = cfg.AddConfig(eff) - if err != nil { - return nil, nil, err - } - } - } else { - for _, h := range defaultconfigregistry.Get() { - desc, def, err := h(ctx.ConfigContext()) - if err != nil { - return nil, nil, err - } - if def != nil { - name, err := pkgutils.GetPackageName(h) - if err != nil { - name = "unknown handler" - } - err = ctx.ConfigContext().ApplyConfig(def, fmt.Sprintf("%s: %s", name, desc)) - if err != nil { - return nil, nil, errors.Wrapf(err, "cannot apply default config from %s(%s)", name, desc) - } - err = cfg.AddConfig(def) - if err != nil { - return nil, nil, err - } - } - } - } - return ocmctx, cfg.Get(), nil -} - -func ConfigureByData(ctx config.ContextProvider, data []byte, info string) error { - _, err := ConfigureByData2(ctx, data, info) - return err -} - -func ConfigureByData2(ctx config.ContextProvider, data []byte, info string) (config.Config, error) { - var err error - - sctx := spiffing.New().WithFeatures(features.INTERPOLATION, features.CONTROL) - data, err = spiffing.Process(sctx, spiffing.NewSourceData(info, data)) - if err != nil { - return nil, errors.Wrapf(err, "processing ocm config %q", info) - } - cfg, err := ctx.ConfigContext().GetConfigForData(data, nil) - if err != nil { - return nil, errors.Wrapf(err, "invalid ocm config file %q", info) - } - err = ctx.ConfigContext().ApplyConfig(cfg, info) - if err != nil { - return nil, errors.Wrapf(err, "cannot apply ocm config %q", info) - } - return cfg, nil -} diff --git a/pkg/contexts/ocm/utils/defaultconfigregistry/configure.go b/pkg/contexts/ocm/utils/defaultconfigregistry/configure.go deleted file mode 100644 index faf06f3d4..000000000 --- a/pkg/contexts/ocm/utils/defaultconfigregistry/configure.go +++ /dev/null @@ -1,68 +0,0 @@ -package defaultconfigregistry - -import ( - "strings" - "sync" - - "github.com/open-component-model/ocm/pkg/contexts/config" - "github.com/open-component-model/ocm/pkg/listformat" -) - -type DefaultConfigHandler func(cfg config.Context) (string, config.Config, error) - -type defaultConfigurationRegistry struct { - lock sync.Mutex - - list []entry -} - -type entry struct { - desc string - handler DefaultConfigHandler -} - -func (r *defaultConfigurationRegistry) Register(h DefaultConfigHandler, desc string) { - r.lock.Lock() - defer r.lock.Unlock() - - r.list = append(r.list, entry{desc, h}) -} - -func (r *defaultConfigurationRegistry) Get() []DefaultConfigHandler { - r.lock.Lock() - defer r.lock.Unlock() - - var result []DefaultConfigHandler - for _, h := range r.list { - result = append(result, h.handler) - } - return result -} - -func (r *defaultConfigurationRegistry) Description() string { - var result []string - - r.lock.Lock() - defer r.lock.Unlock() - - for _, h := range defaultConfigRegistry.list { - if h.desc != "" { - result = append(result, strings.TrimSpace(h.desc)) - } - } - return listformat.FormatDescriptionList("", result...) -} - -var defaultConfigRegistry = &defaultConfigurationRegistry{} - -func RegisterDefaultConfigHandler(h DefaultConfigHandler, desc string) { - defaultConfigRegistry.Register(h, desc) -} - -func Get() []DefaultConfigHandler { - return defaultConfigRegistry.Get() -} - -func Description() string { - return defaultConfigRegistry.Description() -} diff --git a/pkg/contexts/ocm/utils/localize/README.md b/pkg/contexts/ocm/utils/localize/README.md deleted file mode 100644 index 26fe125cc..000000000 --- a/pkg/contexts/ocm/utils/localize/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# Localization Tools - -This package (pkg/contexts/ocm/util/localize) contains some -yaml format definitions, (format.go) given by a Go structure. -and functions usable for the modification of filesystem snapshots. - -It covers -- OCM based localization descriptions used to describe image - location substitution and the -- merging with configuration input. - -This mechanism is intended to support the mapping of generic filesystem -snapshots containing deployment descriptions or manifests to -an installation specific snapshot incorporating the local location -of container images based on OCM component versions and installation specific -configuration values. - -The description format describes two basic specifications that incorporate external -information provided by a component version or some user config. - -- struct/format `Localization` describe a specification to - inject/modify image locations based on the information provided - by a component version. The substitution descriptions use relative resources - references to specify the resource whose image reference is used as basis - for the substituted value. - - This specification is intended to be stored as part of a resource artifact in a - component version which is then used to apply it. Thereby the contained relative - [resource reference](../../../../../docs/ocm/model.md#resource-reference) - are evaluated against the component version containing the specification to resolve - the final image location. - -- struct/format `Configuration` describes a specification for - applying a dedicated config value taken from a configuration source - to a filesytem snapshot. - -The function `Localize` and `Configure` accept a list of such -specifications and map them into an environment agnostic set of -`Substitution` specifications, which contain resolved data values, only. -A third function `Substitute` takes those environment agnostic specifications -and apply them to a filesystem. - -Finally, a compound specification `InstantiationRules` is provided, -that combines all those descriptions with the specification of the snapshot -resource and further helper parts, like json scheme validation for config files. - -Such a specification object can be applied by the function `Instantiate` -together with configuration values to -a component version. As substitution result it returns a virtual filesystem -with the snapshot according to the resolved substitutions. To get access to the -template resource containing the filesystem snapshot to be instantiated, the -configured downloaders (package `pkg/context/ocm/download`) is used. -Therefore, this method can be used together with any own resource type as long as -an appropriate downloader is configured. - -Additionally, there is a set of more basic types and methods, which can be used -to describe end execute localizations for single data objects (see `ImageMappings`, -`LocalizeMappings` and `SubstituteMappings`). \ No newline at end of file diff --git a/pkg/contexts/ocm/utils/localize/config.go b/pkg/contexts/ocm/utils/localize/config.go deleted file mode 100644 index e98346c42..000000000 --- a/pkg/contexts/ocm/utils/localize/config.go +++ /dev/null @@ -1,129 +0,0 @@ -package localize - -import ( - "fmt" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/sliceutils" - "github.com/mandelsoft/spiff/spiffing" - - "github.com/open-component-model/ocm/pkg/contexts/ocm" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/spiff" -) - -func Configure( - mappings []Configuration, cursubst []Substitution, - cv ocm.ComponentVersionAccess, resolver ocm.ComponentVersionResolver, - template []byte, config []byte, libraries []metav1.ResourceReference, schemedata []byte, -) (Substitutions, error) { - var err error - - if len(mappings) == 0 { - return nil, nil - } - if len(config) == 0 { - if len(schemedata) > 0 { - if err = spiff.ValidateByScheme([]byte("{}"), schemedata); err != nil { - return nil, errors.Wrapf(err, "config validation failed") - } - } - if len(template) == 0 { - return nil, nil - } - } - - stubs := spiff.Options{} - for i, lib := range libraries { - opt, err := func() (spiff.OptionFunction, error) { - res, eff, err := utils.ResolveResourceReference(cv, lib, resolver) - if err != nil { - return nil, errors.ErrNotFound("library resource %s not found", lib.String()) - } - defer eff.Close() - m, err := res.AccessMethod() - if err != nil { - return nil, errors.ErrNotFound("error accessing access method for library resource", lib.String()) - } - data, err := m.Get() - m.Close() - if err != nil { - return nil, errors.ErrNotFound("cannot access library resource", lib.String()) - } - return spiff.StubData(fmt.Sprintf("spiff lib%d", i), data), nil - }() - if err != nil { - return nil, err - } - stubs.Add(opt) - } - - if len(schemedata) > 0 { - err = spiff.ValidateByScheme(config, schemedata) - if err != nil { - return nil, errors.Wrapf(err, "validation failed") - } - } - - extlist := []interface{}{} - for _, e := range cursubst { - // TODO: escape spiff expressions, but should not occur, so omit it so far - extlist = append(extlist, e) - } - - cfglist := []interface{}{} - for _, e := range mappings { - cfglist = append(cfglist, e) - } - - var temp map[string]interface{} - if len(template) == 0 { - temp = map[string]interface{}{ - "adjustments": extlist, - "configRules": cfglist, - } - } else { - if err := runtime.DefaultYAMLEncoding.Unmarshal(template, &temp); err != nil { - return nil, errors.Wrapf(err, "cannot unmarshal template") - } - - if _, ok := temp["adjustments"]; ok { - return nil, errors.Newf("template may not contain 'adjustments'") - } - temp["adjustments"] = extlist - - if cur, ok := temp["configRules"]; ok { - if l, ok := cur.([]interface{}); !ok { - return nil, errors.Newf("node 'configRules' in template must be a list of configuration requests") - } else { - temp["configRules"] = sliceutils.CopyAppend(l, cfglist...) - } - } else { - temp["configRules"] = cfglist - } - } - - if _, ok := temp["utilities"]; !ok { - // prepare merging of spiff libraries using the node utilities as root path - temp["utilities"] = "" - } - - template, err = runtime.DefaultJSONEncoding.Marshal(temp) - if err != nil { - return nil, errors.Wrapf(err, "cannot marshal adjustments") - } - - config, err = spiff.CascadeWith(spiff.TemplateData("adjustments", template), stubs, spiff.Values(config), spiff.Mode(spiffing.MODE_PRIVATE)) - if err != nil { - return nil, errors.Wrapf(err, "error processing template") - } - - var subst struct { - Adjustments Substitutions `json:"adjustments,omitempty"` - ConfigRules Substitutions `json:"configRules,omitempty"` - } - err = runtime.DefaultYAMLEncoding.Unmarshal(config, &subst) - return sliceutils.CopyAppend(subst.Adjustments, subst.ConfigRules...), err -} diff --git a/pkg/contexts/ocm/utils/localize/config_test.go b/pkg/contexts/ocm/utils/localize/config_test.go deleted file mode 100644 index 5b8615c71..000000000 --- a/pkg/contexts/ocm/utils/localize/config_test.go +++ /dev/null @@ -1,230 +0,0 @@ -package localize_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/mandelsoft/vfs/pkg/vfs" - - "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/ocm" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils/localize" - "github.com/open-component-model/ocm/pkg/env/builder" - "github.com/open-component-model/ocm/pkg/mime" -) - -var config = []byte(` -values: - a: va - b: vb - c: - a: vca -`) - -var _ = Describe("config value mapping", func() { - It("handles simple values substitution", func() { - configs := UnmarshalConfigurations(` -- name: test1 - file: file1 - path: a.b.c - value: value -`) - subst, err := localize.Configure(configs, nil, nil, nil, nil, config, nil, nil) - Expect(err).To(Succeed()) - Expect(subst).To(Equal(UnmarshalSubstitutions(` -- name: test1 - file: file1 - path: a.b.c - value: value -`))) - }) - - It("handles simple expression substitution", func() { - configs := UnmarshalConfigurations(` -- name: test1 - file: file1 - path: a.b.c - value: (( values.a )) -`) - subst, err := localize.Configure(configs, nil, nil, nil, nil, config, nil, nil) - Expect(err).To(Succeed()) - Expect(subst).To(Equal(UnmarshalSubstitutions(` -- name: test1 - file: file1 - path: a.b.c - value: va -`))) - }) - - It("fails for invalid expression substitution", func() { - configs := UnmarshalConfigurations(` -- file: file1 - path: a.b.c - value: (( values.x )) -`) - _, err := localize.Configure(configs, nil, nil, nil, nil, config, nil, nil) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("*'values.x' not found")) - }) - - It("handles expression substitution with substitution context", func() { - context := ` -- name: context - file: file1 - path: a.b.c - value: contextvalue -` - configs := UnmarshalConfigurations(` -- name: test1 - file: file1 - path: a.b.c - value: (( adjustments.context.value )) -`) - subst, err := localize.Configure(configs, UnmarshalSubstitutions(context), nil, nil, nil, config, nil, nil) - Expect(err).To(Succeed()) - Expect(subst).To(Equal(UnmarshalSubstitutions(context + ` -- name: test1 - file: file1 - path: a.b.c - value: contextvalue -`))) - }) - It("handles expression substitution with template data", func() { - template := []byte(` -helper: - help: (( |x|->"helped " x )) -`) - configs := UnmarshalConfigurations(` -- name: test1 - file: file1 - path: a.b.c - value: (( helper.help(values.a) )) -`) - subst, err := localize.Configure(configs, nil, nil, nil, template, config, nil, nil) - Expect(err).To(Succeed()) - Expect(subst).To(Equal(UnmarshalSubstitutions(` -- name: test1 - file: file1 - path: a.b.c - value: helped va -`))) - }) - - const ( - ARCHIVE = "archive.ctf" - COMPONENT = "github.com/comp" - VERSION = "1.0.0" - LIB = "lib" - ) - - Context("with libs", func() { - var ( - repo ocm.Repository - cv ocm.ComponentVersionAccess - env *builder.Builder - ) - - BeforeEach(func() { - env = builder.NewBuilder(nil) - env.OCMCommonTransport(ARCHIVE, accessio.FormatDirectory, func() { - env.Component(COMPONENT, func() { - env.Version(VERSION, func() { - env.Provider("mandelsoft") - env.Resource(LIB, "", "Spiff", v1.LocalRelation, func() { - env.BlobStringData(mime.MIME_YAML, ` -utilities: - <<<: (( &inject &temporary(merge || ~) )) - - lib: - help: (( |x|->"lib " x )) -`) - }) - }) - }) - }) - - var err error - repo, err = ctf.Open(ocm.DefaultContext(), accessobj.ACC_READONLY, ARCHIVE, 0, env) - Expect(err).To(Succeed()) - - cv, err = repo.LookupComponentVersion(COMPONENT, VERSION) - Expect(err).To(Succeed()) - }) - - AfterEach(func() { - Expect(cv.Close()).To(Succeed()) - Expect(repo.Close()).To(Succeed()) - vfs.Cleanup(env) - }) - - It("uses resolved library from component version", func() { - configs := UnmarshalConfigurations(` -- name: test1 - file: file1 - path: a.b.c - value: (( utilities.lib.help(values.a) )) -`) - - libs := []v1.ResourceReference{ - { - Resource: v1.NewIdentity(LIB), - }, - } - subst, err := localize.Configure(configs, nil, cv, nil, nil, config, libs, nil) - Expect(err).To(Succeed()) - Expect(subst).To(Equal(UnmarshalSubstitutions(` -- name: test1 - file: file1 - path: a.b.c - value: lib va -`))) - }) - - It("uses templated configRules", func() { - configs := UnmarshalConfigurations(` -- name: test1 - file: file1 - path: a.b.c - value: (( values.a )) -`) - - template := ` -list: [ "a", "b" ] -helper: - <<<: (( &template )) - name: (( "gen" k )) - file: file1 - path: (( "some.path." k )) - value: (( values.a )) - -configRules: - - <<<: (( map[.list|k|->*.helper] )) -` - - libs := []v1.ResourceReference{ - { - Resource: v1.NewIdentity(LIB), - }, - } - subst, err := localize.Configure(configs, nil, cv, nil, []byte(template), config, libs, nil) - Expect(err).To(Succeed()) - Expect(subst).To(Equal(UnmarshalSubstitutions(` -- name: gena - file: file1 - path: some.path.a - value: va -- name: genb - file: file1 - path: some.path.b - value: va -- name: test1 - file: file1 - path: a.b.c - value: va -`))) - }) - }) -}) diff --git a/pkg/contexts/ocm/utils/localize/format.go b/pkg/contexts/ocm/utils/localize/format.go deleted file mode 100644 index da1018ad3..000000000 --- a/pkg/contexts/ocm/utils/localize/format.go +++ /dev/null @@ -1,223 +0,0 @@ -package localize - -import ( - "encoding/json" - "fmt" - "strings" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/ocm" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" - "github.com/open-component-model/ocm/pkg/runtime" -) - -// Definition of inbound substitution requests. -// - Localizations image locations substitution resolved using a component version -// - Configurations configuration substitution resolved by provided value data. -// -// Such requests can be given to merge externally provided data into -// some filesystem template. -// The evaluation of such requests results in a list -// of resolved substitution requests that can be applied without -// further value context to a filesystem structure. - -// ImageMapping describes a dedicated substitution of parts -// of container image names based on a relative OCM resource reference. -type ImageMapping struct { - // The optional but unique(!) name of the mapping to support referencing mapping entries - Name string `json:"name,omitempty"` - - // The resource reference used to resolve the substitution - v1.ResourceReference `json:",inline"` - - // The optional variants for the value determination - - // Path in target to substitute by the image tag/digest - Tag string `json:"tag,omitempty"` - // Path in target to substitute the image repository - Repository string `json:"repository,omitempty"` - // Path in target to substitute the complete image - Image string `json:"image,omitempty"` -} - -type ImageMappings []ImageMapping - -func (m *ImageMapping) Evaluate(idx int, cv ocm.ComponentVersionAccess, resolver ocm.ComponentVersionResolver) (ValueMappings, error) { - name := "image mapping" - if m.Name != "" { - name = fmt.Sprintf("%s %q", name, m.Name) - } else { - if idx >= 0 { - name = fmt.Sprintf("%s %d", name, idx+1) - } - } - acc, rcv, err := utils.ResolveResourceReference(cv, m.ResourceReference, resolver) - if err != nil { - return nil, errors.Wrapf(err, "mapping", fmt.Sprintf("%s (%s)", name, &m.ResourceReference)) - } - defer rcv.Close() - ref, err := utils.GetOCIArtifactRef(cv.GetContext(), acc) - if err != nil { - return nil, errors.Wrapf(err, "mapping %s: cannot resolve resource %s to an OCI Reference", name, &m.ResourceReference) - } - ix := strings.Index(ref, ":") - if ix < 0 { - ix = strings.Index(ref, "@") - if ix < 0 { - return nil, errors.Wrapf(err, "mapping %s: image tag or digest missing (%s)", name, ref) - } - } - repo := ref[:ix] - tag := ref[ix+1:] - - cnt := 0 - if m.Repository != "" { - cnt++ - } - if m.Tag != "" { - cnt++ - } - if m.Image != "" { - cnt++ - } - if cnt == 0 { - return nil, fmt.Errorf("no substitution target given for %s", name) - } - - var result ValueMappings - var r *ValueMapping - if m.Repository != "" { - if r, err = NewValueMapping(substitutionName(name, "repository", cnt), m.Repository, repo); err != nil { - return nil, errors.Wrapf(err, "setting repository for %s", substitutionName(name, "repository", cnt)) - } - result = append(result, *r) - } - if m.Tag != "" { - if r, err = NewValueMapping(substitutionName(name, "tag", cnt), m.Tag, tag); err != nil { - return nil, errors.Wrapf(err, "setting tag for %s", substitutionName(name, "tag", cnt)) - } - result = append(result, *r) - } - if m.Image != "" { - if r, err = NewValueMapping(substitutionName(name, "image", cnt), m.Image, ref); err != nil { - return nil, errors.Wrapf(err, "setting image for %s", substitutionName(name, "image", cnt)) - } - result = append(result, *r) - } - return result, nil -} - -// Localization is a request to substitute an image location. -// The specification describes substitution targets given by the file path and -// the YAML/JSON value paths of the elements in this file. -// The substitution value is calculated -// from the access specification of the given resource provided by the actual -// component version. -type Localization struct { - // The path of the file for the substitution - FilePath string `json:"file"` - // The image mapping request - ImageMapping `json:",inline"` -} - -// Configuration is a request to substitute a configuration value. -// The specification describes substitution targets given by the file path and -// the YAML/JSON value paths of the elements in this file. -// The substitution value is calculated -// by the value expression (spiff) based on given config data. -// It has the same structure as Substitution, but is a request based -// on external configuration data, while a Substitution describes a fixed target -// value. -type Configuration Substitution - -type ValueMapping struct { - // The optional but unique(!) name of the mapping to support referencing mapping entries - Name string `json:"name,omitempty"` - // The target path for the value substitution - ValuePath string `json:"path"` - // The value to set - Value json.RawMessage `json:"value"` -} - -func NewValueMapping(name, path string, value interface{}) (*ValueMapping, error) { - var ( - v []byte - err error - ) - - if value != nil { - v, err = runtime.DefaultJSONEncoding.Marshal(value) - if err != nil { - return nil, fmt.Errorf("cannot marshal substitution value: %w", err) - } - } - return &ValueMapping{ - Name: name, - ValuePath: path, - Value: v, - }, nil -} - -type ValueMappings []ValueMapping - -func (s *ValueMappings) Add(name, path string, value interface{}) error { - m, err := NewValueMapping(name, path, value) - if err != nil { - return err - } - *s = append(*s, *m) - return nil -} - -// Here comes the structure used for resolved execution requests. -// They can be applied to a filesystem content without further external information. -// It basically has the same structure as the configuration request, but -// the given value is just the target value without any further interpretation. -// This way configuration requests could just provide dedicated values, also - -// Substitution is a request to substitute the YAML/JSON -// element given by the value path in the given file path by the given -// direct value. -type Substitution struct { - // The path of the file for the substitution - FilePath string `json:"file"` - // The field mapping toapply to given file path - ValueMapping `json:",inline"` -} - -func (s *Substitution) GetValue() (interface{}, error) { - var value interface{} - err := runtime.DefaultYAMLEncoding.Unmarshal(s.Value, &value) - return value, err -} - -type Substitutions []Substitution - -func (s *Substitutions) AddValueMapping(m *ValueMapping, file string) { - *s = append(*s, Substitution{ - FilePath: file, - ValueMapping: *m, - }) -} - -func (s *Substitutions) Add(name, file, path string, value interface{}) error { - m, err := NewValueMapping(name, path, value) - if err != nil { - return err - } - s.AddValueMapping(m, file) - return nil -} - -// InstantiationRules bundle the localization of a filesystem resource -// covering image localization and applying instance configuration. -type InstantiationRules struct { - Template v1.ResourceReference `json:"templateResource,omitempty"` - LocalizationRules []Localization `json:"localizationRules,omitempty"` - ConfigRules []Configuration `json:"configRules,omitempty"` - ConfigScheme json.RawMessage `json:"configScheme,omitempty"` - ConfigTemplate json.RawMessage `json:"configTemplate,omitempty"` - ConfigLibraries []v1.ResourceReference `json:"configLibraries,omitempty"` -} diff --git a/pkg/contexts/ocm/utils/localize/subst.go b/pkg/contexts/ocm/utils/localize/subst.go deleted file mode 100644 index cbe35c0c8..000000000 --- a/pkg/contexts/ocm/utils/localize/subst.go +++ /dev/null @@ -1,80 +0,0 @@ -package localize - -import ( - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/utils/subst" -) - -func Substitute(subs Substitutions, fs vfs.FileSystem) error { - files := map[string]subst.SubstitutionTarget{} - - for i, s := range subs { - file, err := vfs.Canonical(fs, s.FilePath, true) - if err != nil { - return errors.Wrapf(err, "entry %d", i) - } - - fi, ok := files[file] - if !ok { - s, err := subst.ParseFile(file, fs) - if err != nil { - return errors.Wrapf(err, "entry %d", i) - } - files[file], fi = s, s - } - - if err = fi.SubstituteByData(s.ValuePath, s.Value); err != nil { - return errors.Wrapf(err, "entry %d: cannot substitute value", i+1) - } - } - - for file, fi := range files { - data, err := fi.Content() - if err != nil { - return errors.Wrapf(err, "cannot marshal %q after substitution ", file) - } - - if err := vfs.WriteFile(fs, file, data, vfs.ModePerm); err != nil { - return errors.Wrapf(err, "file %q", file) - } - } - return nil -} - -// SubstituteMappings substitutes value mappings for a dedicated substitution target. -func SubstituteMappings(subs ValueMappings, target subst.SubstitutionTarget) error { - for i, s := range subs { - if err := target.SubstituteByData(s.ValuePath, s.Value); err != nil { - return errors.Wrapf(err, "entry %d: cannot substitute value", i+1) - } - } - return nil -} - -// SubstituteMappingsForData substitutes value mappings for some data. -func SubstituteMappingsForData(subs ValueMappings, data []byte) ([]byte, error) { - target, err := subst.Parse(data) - if err != nil { - return nil, err - } - err = SubstituteMappings(subs, target) - if err != nil { - return nil, err - } - return target.Content() -} - -/* -2024-04-28 Don't see any use of this in either ocm or ocm-controller -As it exposes the implementation detail of what YAML model we use -_and_ we're switching to yqlib from goccy comment it out. -func Set(content *ast.File, path string, value *ast.File) error { - p, err := yaml.PathString("$." + path) - if err != nil { - return errors.Wrapf(err, "invalid substitution path") - } - return p.ReplaceWithFile(content, value) -} -*/ diff --git a/pkg/contexts/ocm/utils/localize/utils_test.go b/pkg/contexts/ocm/utils/localize/utils_test.go deleted file mode 100644 index 974f496d6..000000000 --- a/pkg/contexts/ocm/utils/localize/utils_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package localize_test - -import ( - . "github.com/onsi/gomega" - - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils/localize" - "github.com/open-component-model/ocm/pkg/runtime" -) - -func UnmarshalLocalizations(data string) []localize.Localization { - var v []localize.Localization - Expect(runtime.DefaultYAMLEncoding.Unmarshal([]byte(data), &v)).To(Succeed()) - return v -} - -func UnmarshalConfigurations(data string) []localize.Configuration { - var v []localize.Configuration - Expect(runtime.DefaultYAMLEncoding.Unmarshal([]byte(data), &v)).To(Succeed()) - return v -} - -func UnmarshalSubstitutions(data string) localize.Substitutions { - var v localize.Substitutions - Expect(runtime.DefaultYAMLEncoding.Unmarshal([]byte(data), &v)).To(Succeed()) - return v -} - -func UnmarshalImageMappings(data string) localize.ImageMappings { - var v localize.ImageMappings - Expect(runtime.DefaultYAMLEncoding.Unmarshal([]byte(data), &v)).To(Succeed()) - return v -} - -func UnmarshalValueMappings(data string) localize.ValueMappings { - var v localize.ValueMappings - Expect(runtime.DefaultYAMLEncoding.Unmarshal([]byte(data), &v)).To(Succeed()) - return v -} - -func UnmarshalInstRules(data string) *localize.InstantiationRules { - var v localize.InstantiationRules - Expect(runtime.DefaultYAMLEncoding.Unmarshal([]byte(data), &v)).To(Succeed()) - return &v -} - -func CheckYAMLFile(path string, fs vfs.FileSystem, content string) { - data, err := vfs.ReadFile(fs, path) - ExpectWithOffset(1, err).To(Succeed()) - ExpectWithOffset(1, string(data)).To(MatchYAML(content)) -} - -func CheckJSONFile(path string, fs vfs.FileSystem, content string) { - data, err := vfs.ReadFile(fs, path) - ExpectWithOffset(1, err).To(Succeed()) - ExpectWithOffset(1, string(data)).To(MatchJSON(content)) -} diff --git a/pkg/contexts/ocm/utils/registry/registry.go b/pkg/contexts/ocm/utils/registry/registry.go deleted file mode 100644 index ca11703a0..000000000 --- a/pkg/contexts/ocm/utils/registry/registry.go +++ /dev/null @@ -1,109 +0,0 @@ -package registry - -import ( - "strings" - - "github.com/mandelsoft/goutils/set" - "golang.org/x/exp/slices" - - "github.com/open-component-model/ocm/pkg/mime" -) - -type Key[K any] interface { - comparable - IsValid() bool - - GetMediaType() string - GetArtifactType() string - - SetArtifact(arttype, medtatype string) K -} - -type Registry[H any, K Key[K]] struct { - mappings map[K][]H -} - -func NewRegistry[H any, K Key[K]]() *Registry[H, K] { - return &Registry[H, K]{ - mappings: map[K][]H{}, - } -} - -func (p *Registry[H, K]) Copy() *Registry[H, K] { - r := &Registry[H, K]{ - mappings: map[K][]H{}, - } - for k, v := range p.mappings { - r.mappings[k] = slices.Clone(v) - } - return r -} - -func (p *Registry[H, K]) lookupMedia(key K) []H { - lookup := key - for { - if h, ok := p.mappings[lookup]; ok { - return h - } - if i := strings.LastIndex(lookup.GetMediaType(), "+"); i > 0 { - lookup = lookup.SetArtifact(lookup.GetArtifactType(), lookup.GetMediaType()[:i]) - } else { - break - } - } - return nil -} - -func (p *Registry[H, K]) GetHandler(key K) []H { - r := p.mappings[key] - if r == nil { - return nil - } - return slices.Clone(r) -} - -func (p *Registry[H, K]) LookupHandler(key K) []H { - h := p.lookupMedia(key) - if len(h) > 0 { - return h - } - - mediatype := key.GetMediaType() - arttype := key.GetArtifactType() - if h := p.mappings[key.SetArtifact(arttype, "")]; len(h) > 0 { - return h - } - return p.lookupMedia(key.SetArtifact("", mediatype)) -} - -func (p *Registry[H, K]) LookupKeys(key K) set.Set[K] { - found := set.New[K]() - - if len(p.LookupHandler(key)) > 0 { - found.Add(key) - } - if key.GetArtifactType() == "" { - for k := range p.mappings { - if k.GetArtifactType() != "" { - c := k.SetArtifact(k.GetArtifactType(), key.GetMediaType()) - if !found.Contains(c) && len(p.LookupHandler(c)) > 0 { - found.Add(c) - } - } - } - } else { - for k := range p.mappings { - if mime.IsMoreGeneral(key.GetMediaType(), k.GetMediaType()) { - c := k.SetArtifact(key.GetArtifactType(), k.GetMediaType()) - if !found.Contains(c) && len(p.LookupHandler(c)) > 0 { - found.Add(c) - } - } - } - } - return found -} - -func (p *Registry[H, K]) Register(key K, h H) { - p.mappings[key] = append(p.mappings[key], h) -} diff --git a/pkg/contexts/ocm/utils/registry/registry_test.go b/pkg/contexts/ocm/utils/registry/registry_test.go deleted file mode 100644 index 5ffe48702..000000000 --- a/pkg/contexts/ocm/utils/registry/registry_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package registry_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/mandelsoft/goutils/set" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/utils/registry" -) - -var ( - aKey = registry.RegistrationKey{}.SetArtifact("a", "") - mKey = registry.RegistrationKey{}.SetArtifact("", "m") - amKey = registry.RegistrationKey{}.SetArtifact("a", "m") - a1mKey = registry.RegistrationKey{}.SetArtifact("a1", "m") - am1Key = registry.RegistrationKey{}.SetArtifact("a", "m1") - amtarKey = registry.RegistrationKey{}.SetArtifact("a", "m+tar") -) - -var _ = Describe("lookup", func() { - var reg *registry.Registry[string, registry.RegistrationKey] - - BeforeEach(func() { - reg = registry.NewRegistry[string, registry.RegistrationKey]() - }) - - Context("lookup handler", func() { - It("looks up complete", func() { - reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m"), "test") - reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m1"), "testm") - reg.Register(registry.RegistrationKey{}.SetArtifact("a1", "m"), "testa") - - h := reg.LookupHandler(amKey) - Expect(h).To(Equal([]string{"test"})) - }) - - It("looks up partial artifact", func() { - reg.Register(registry.RegistrationKey{}.SetArtifact("a", ""), "test") - reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m1"), "testm") - reg.Register(registry.RegistrationKey{}.SetArtifact("a1", "m"), "testa") - - h := reg.LookupHandler(amKey) - Expect(h).To(Equal([]string{"test"})) - }) - - It("looks up partial media", func() { - reg.Register(registry.RegistrationKey{}.SetArtifact("", "m"), "test") - reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m1"), "testm") - reg.Register(registry.RegistrationKey{}.SetArtifact("a1", "m"), "testa") - - h := reg.LookupHandler(amKey) - Expect(h).To(Equal([]string{"test"})) - }) - - It("looks complete with media sub type", func() { - reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m"), "test") - reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m1"), "testm") - reg.Register(registry.RegistrationKey{}.SetArtifact("a1", "m"), "testa") - - h := reg.LookupHandler(amtarKey) - Expect(h).To(Equal([]string{"test"})) - }) - - It("looks partial with media sub type", func() { - reg.Register(registry.RegistrationKey{}.SetArtifact("", "m"), "test") - reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m1"), "testm") - reg.Register(registry.RegistrationKey{}.SetArtifact("a1", "m"), "testa") - - h := reg.LookupHandler(amtarKey) - Expect(h).To(Equal([]string{"test"})) - }) - - It("prefers art", func() { - reg.Register(registry.RegistrationKey{}.SetArtifact("", "m"), "testm") - reg.Register(registry.RegistrationKey{}.SetArtifact("a", ""), "test") - reg.Register(registry.RegistrationKey{}.SetArtifact("a1", "m"), "testa") - - h := reg.LookupHandler(amtarKey) - Expect(h).To(Equal([]string{"test"})) - }) - }) - - Context("lookup keys", func() { - It("fills missing", func() { - reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m"), "test") - reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m1"), "testm") - reg.Register(registry.RegistrationKey{}.SetArtifact("a1", "m"), "testa") - - keys := reg.LookupKeys(aKey) - Expect(keys).To(Equal(set.New(amKey, am1Key))) - }) - - It("fills missing", func() { - reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m+tar"), "test") - reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m1"), "testm") - reg.Register(registry.RegistrationKey{}.SetArtifact("a1", "m"), "testa") - - keys := reg.LookupKeys(mKey) - Expect(keys).To(Equal(set.New(a1mKey))) - }) - It("fills more specific media", func() { - reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m+tar"), "test") - reg.Register(registry.RegistrationKey{}.SetArtifact("a", "m1"), "testm") - reg.Register(registry.RegistrationKey{}.SetArtifact("a1", "m"), "testa") - - keys := reg.LookupKeys(amKey) - Expect(keys).To(Equal(set.New(amtarKey))) - }) - }) -}) diff --git a/pkg/contexts/ocm/utils/resource.go b/pkg/contexts/ocm/utils/resource.go deleted file mode 100644 index f7e6ccf2c..000000000 --- a/pkg/contexts/ocm/utils/resource.go +++ /dev/null @@ -1,58 +0,0 @@ -package utils - -import ( - "io" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/iotools" -) - -func GetResourceData(acc ocm.AccessProvider) ([]byte, error) { - return blobaccess.DataFromProvider(acc) -} - -func GetResourceDataForPath(cv ocm.ComponentVersionAccess, id metav1.Identity, path []metav1.Identity, resolvers ...ocm.ComponentVersionResolver) ([]byte, error) { - return GetResourceDataForRef(cv, metav1.NewNestedResourceRef(id, path), resolvers...) -} - -func GetResourceDataForRef(cv ocm.ComponentVersionAccess, ref metav1.ResourceReference, resolvers ...ocm.ComponentVersionResolver) ([]byte, error) { - var res ocm.ComponentVersionResolver - if len(resolvers) > 0 { - res = ocm.NewCompoundResolver(resolvers...) - } - a, c, err := ResolveResourceReference(cv, ref, res) - if err != nil { - return nil, err - } - defer c.Close() - - return GetResourceData(a) -} - -func GetResourceReader(acc ocm.AccessProvider) (io.ReadCloser, error) { - return blobaccess.ReaderFromProvider(acc) -} - -func GetResourceReaderForPath(cv ocm.ComponentVersionAccess, id metav1.Identity, path []metav1.Identity, resolvers ...ocm.ComponentVersionResolver) (io.ReadCloser, error) { - return GetResourceReaderForRef(cv, metav1.NewNestedResourceRef(id, path), resolvers...) -} - -func GetResourceReaderForRef(cv ocm.ComponentVersionAccess, ref metav1.ResourceReference, resolvers ...ocm.ComponentVersionResolver) (io.ReadCloser, error) { - var res ocm.ComponentVersionResolver - if len(resolvers) > 0 { - res = ocm.NewCompoundResolver(resolvers...) - } - a, c, err := ResolveResourceReference(cv, ref, res) - if err != nil { - return nil, err - } - - reader, err := GetResourceReader(a) - if err != nil { - c.Close() - return nil, err - } - return iotools.AddReaderCloser(reader, c), nil -} diff --git a/pkg/contexts/ocm/utils/utils.go b/pkg/contexts/ocm/utils/utils.go deleted file mode 100644 index ce6d93632..000000000 --- a/pkg/contexts/ocm/utils/utils.go +++ /dev/null @@ -1,29 +0,0 @@ -package utils - -import ( - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" -) - -func GetOCIArtifactRef(ctxp ocm.ContextProvider, r ocm.ResourceAccess) (string, error) { - ctx := ctxp.OCMContext() - - acc, err := r.Access() - if err != nil || acc == nil { - return "", err - } - - var cv cpi.ComponentVersionAccess - if p, ok := r.(cpi.ComponentVersionProvider); ok { - cv, err = p.GetComponentVersion() - if err != nil { - return "", errors.Wrapf(err, "cannot access component version for re/source") - } - defer cv.Close() - } - - return ociartifact.GetOCIArtifactReference(ctx, acc, cv) -} diff --git a/pkg/contexts/ocm/utils/walk.go b/pkg/contexts/ocm/utils/walk.go deleted file mode 100644 index 30809fc6b..000000000 --- a/pkg/contexts/ocm/utils/walk.go +++ /dev/null @@ -1,53 +0,0 @@ -package utils - -import ( - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/ocm" -) - -// WalkingStep is used to process a component version during graph traversal. -// If returned true, the traversal process follows local component references- -// If an error is returned the traversal is aborted with this error, -// Additionally, an info object of type T can be registered in the state for the -// component version. -type WalkingStep[T any] func(state common.WalkingState[T, ocm.ComponentVersionAccess]) (bool, error) - -// Walk traverses a component version graph using the WalkingStep to -// process found component version. -func Walk[T any](closure common.NameVersionInfo[T], cv ocm.ComponentVersionAccess, resolver ocm.ComponentVersionResolver, step WalkingStep[T]) (common.NameVersionInfo[T], error) { - if closure == nil { - closure = common.NameVersionInfo[T]{} - } - state := common.WalkingState[T, ocm.ComponentVersionAccess]{ - Closure: closure, - Context: cv, - } - err := walk[T](state, cv, resolver, step) - return closure, err -} - -func walk[T any](state common.WalkingState[T, ocm.ComponentVersionAccess], cv ocm.ComponentVersionAccess, resolver ocm.ComponentVersionResolver, step WalkingStep[T]) error { - nv := common.VersionedElementKey(cv) - if ok, err := state.Add(ocm.KIND_COMPONENTVERSION, nv); !ok || err != nil { - return err - } - c, err := step(state) - if err != nil { - return errors.Wrapf(err, "%s", state.History) - } - if c { - for _, ref := range cv.GetDescriptor().References { - n, err := resolver.LookupComponentVersion(ref.ComponentName, ref.Version) - if err != nil { - return errors.Wrapf(err, "%s: cannot resolve ref %s", state.History, ref) - } - err = errors.Join(walk[T](state, n, resolver, step), n.Close()) - if err != nil { - return err - } - } - } - return nil -} diff --git a/pkg/contexts/ocm/valuemergehandler/config/config_test.go b/pkg/contexts/ocm/valuemergehandler/config/config_test.go deleted file mode 100644 index 824ceb115..000000000 --- a/pkg/contexts/ocm/valuemergehandler/config/config_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package config_test - -import ( - "encoding/json" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/config" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/hpi" -) - -var _ = Describe("merge config", func() { - spec1 := Must(v1.NewMergeAlgorithmSpecification("test", "config1")) - spec2 := Must(v1.NewMergeAlgorithmSpecification("algo", "config2")) - - cfg := config.New() - cfg.Assign("test", spec1) - cfg.AssignLabel("l1", "v2", spec2) - - Context("serialize", func() { - It("serializes config", func() { - data := Must(json.Marshal(cfg)) - cfg2 := config.New() - MustBeSuccessful(json.Unmarshal(data, cfg2)) - Expect(cfg2).To(Equal(cfg)) - }) - }) - - Context("apply", func() { - It("applies directly", func() { - reg := hpi.NewRegistry() - - Expect(cfg.ApplyTo(nil, reg)).To(Succeed()) - - found := reg.GetAssignments() - expected := map[hpi.Hint]*hpi.Specification{ - "test": spec1, - "label:l1@v2": spec2, - } - - Expect(found).To(DeepEqual(expected)) - }) - - It("applies via config context", func() { - ctx := ocm.New(datacontext.MODE_INITIAL) - - Expect(ctx.ConfigContext().ApplyConfig(cfg, "programmatic")).To(Succeed()) - - Expect(valuemergehandler.For(ctx).GetHandlers()).To(Equal(valuemergehandler.Handlers{})) - found := valuemergehandler.For(ctx).GetAssignments() - expected := map[hpi.Hint]*hpi.Specification{ - "test": spec1, - "label:l1@v2": spec2, - } - Expect(found).To(DeepEqual(expected)) - }) - }) -}) diff --git a/pkg/contexts/ocm/valuemergehandler/config/type.go b/pkg/contexts/ocm/valuemergehandler/config/type.go deleted file mode 100644 index b51f73276..000000000 --- a/pkg/contexts/ocm/valuemergehandler/config/type.go +++ /dev/null @@ -1,116 +0,0 @@ -package config - -import ( - "github.com/open-component-model/ocm/pkg/contexts/config" - cfgcpi "github.com/open-component-model/ocm/pkg/contexts/config/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/hpi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - ConfigType = "merge" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX - ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" -) - -func init() { - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) - cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1)) -} - -// Config describes a memory based config interface. -type Config struct { - runtime.ObjectVersionedType `json:",inline"` - Labels []LabelAssignment - Assignments map[hpi.Hint]*hpi.Specification `json:"assignments,omitempty"` -} - -type LabelAssignment struct { - Name string `json:"name"` - Version string `json:"version,omitempty"` - Merge hpi.Specification `json:"merge,omitempty"` -} - -// New creates a new memory ConfigSpec. -func New() *Config { - return &Config{ - ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), - Assignments: map[hpi.Hint]*hpi.Specification{}, - } -} - -func (a *Config) GetType() string { - return ConfigType -} - -func (a *Config) Assign(name hpi.Hint, spec *hpi.Specification) { - if a.Assignments == nil { - a.Assignments = map[hpi.Hint]*hpi.Specification{} - } - if spec == nil { - delete(a.Assignments, name) - } else { - a.Assignments[name] = spec - } -} - -func (a *Config) AssignLabel(name string, version string, spec *hpi.Specification) { - if spec == nil { - for i, s := range a.Labels { - if s.Name == name && s.Version == version { - a.Labels = append(a.Labels[:i], a.Labels[i+1:]...) - return - } - } - } else { - a.Labels = append(a.Labels, LabelAssignment{ - Name: name, - Version: version, - Merge: *spec, - }) - } -} - -func (a *Config) ApplyTo(ctx config.Context, target interface{}) error { - var reg hpi.Registry - - t, ok := target.(hpi.Context) - if !ok { - reg, ok = target.(hpi.Registry) - if !ok { - return config.ErrNoContext(ConfigType) - } - } else { - reg = hpi.For(t) - } - - for n, s := range a.Assignments { - reg.AssignHandler(n, s) - } - for _, s := range a.Labels { - if s.Name == "" { - continue - } - reg.AssignHandler(hpi.LabelHint(s.Name, s.Version), &s.Merge) - } - return nil -} - -const usage = ` -The config type ` + ConfigType + ` can be used to set some -assignments for the merging of (label) values. It applies to a value -merge handler registry, either directly or via an OCM context. - -
-    type: ` + ConfigType + `
-    labels:
-    - name: acme.org/audit/level
-      merge:
-        algorithm: acme.org/audit
-        config: ...
-    assignments:
-       label:acme.org/audit/level@v1: 
-          algorithm: acme.org/audit
-          config: ...
-          ...
-
-` diff --git a/pkg/contexts/ocm/valuemergehandler/handlers/defaultmerge/config.go b/pkg/contexts/ocm/valuemergehandler/handlers/defaultmerge/config.go deleted file mode 100644 index 5288ead62..000000000 --- a/pkg/contexts/ocm/valuemergehandler/handlers/defaultmerge/config.go +++ /dev/null @@ -1,42 +0,0 @@ -package defaultmerge - -import ( - // special case to resolve dependency cycles. - "github.com/mandelsoft/goutils/errors" - - hpi "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/internal" -) - -type Mode string - -func (m Mode) String() string { - return string(m) -} - -const ( - MODE_DEFAULT = Mode("") - MODE_NONE = Mode("none") - MODE_LOCAL = Mode("local") - MODE_INBOUND = Mode("inbound") -) - -func NewConfig(overwrite Mode) *Config { - return &Config{ - Overwrite: overwrite, - } -} - -type Config struct { - Overwrite Mode `json:"overwrite"` -} - -func (c Config) Complete(ctx hpi.Context) error { - switch c.Overwrite { - case MODE_NONE, MODE_LOCAL, MODE_INBOUND: - case "": - // leave choice to using algorithm - default: - return errors.ErrInvalid("merge overwrite mode", string(c.Overwrite)) - } - return nil -} diff --git a/pkg/contexts/ocm/valuemergehandler/handlers/defaultmerge/handler.go b/pkg/contexts/ocm/valuemergehandler/handlers/defaultmerge/handler.go deleted file mode 100644 index 99d11bf43..000000000 --- a/pkg/contexts/ocm/valuemergehandler/handlers/defaultmerge/handler.go +++ /dev/null @@ -1,49 +0,0 @@ -package defaultmerge - -import ( - "fmt" - "reflect" - - // special case to resolve dependency cycles. - hpi "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/internal" -) - -const ALGORITHM = "default" - -func init() { - hpi.Register(New()) -} - -// Value is the minimal structure of values usable with the merge algorithm. -type Value interface{} - -func New() hpi.Handler { - return hpi.New(ALGORITHM, desc, merge) -} - -var desc = ` -This handler merges arbitrary label values by deciding for -one or none side. - -It supports the following config structure: -- *overwrite* *string* (optional) determines how to handle conflicts. - - - none no change possible, if entry differs the merge is rejected. - - local the local value is preserved. - - inbound (default) the inbound value overwrites the local one. -` - -func merge(ctx hpi.Context, c *Config, lv Value, tv *Value) (bool, error) { - modified := false - if !reflect.DeepEqual(lv, tv) { - switch c.Overwrite { - // default = INBOUND: keep precalculated tarted = inbound CD - case MODE_LOCAL: - *tv = lv - modified = true - case MODE_NONE: - return false, fmt.Errorf("target value changed") - } - } - return modified, nil -} diff --git a/pkg/contexts/ocm/valuemergehandler/handlers/defaultmerge/handler_test.go b/pkg/contexts/ocm/valuemergehandler/handlers/defaultmerge/handler_test.go deleted file mode 100644 index fe6a9406e..000000000 --- a/pkg/contexts/ocm/valuemergehandler/handlers/defaultmerge/handler_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package defaultmerge - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/runtime" -) - -var _ = Describe("list merge", func() { - handler := New() - - var e1, e2 Value - var a, b runtime.RawValue - - BeforeEach(func() { - e1 = "v1" - e2 = "v2" - - MustBeSuccessful(a.SetValue(e1)) - MustBeSuccessful(b.SetValue(e1)) - }) - - It("merges no change", func() { - MustBeSuccessful(handler.Merge(nil, a, &b, nil)) - Expect(b).To(Equal(a)) - }) - - It("modified keeps local", func() { - MustBeSuccessful(a.SetValue(e2)) - MustBeSuccessful(handler.Merge(nil, a, &b, NewConfig(MODE_LOCAL))) - - Expect(b).To(DeepEqual(a)) - }) - - It("modified accept inbound", func() { - MustBeSuccessful(b.SetValue(e2)) - r := b.Copy() - MustBeSuccessful(handler.Merge(nil, a, &b, nil)) - Expect(b).To(Equal(r)) - }) - - It("fails for none mode", func() { - MustBeSuccessful(b.SetValue(e2)) - MustFailWithMessage(ErrorFrom(handler.Merge(nil, a, &b, NewConfig(MODE_NONE))), "[default]: target value changed") - }) -}) diff --git a/pkg/contexts/ocm/valuemergehandler/handlers/init.go b/pkg/contexts/ocm/valuemergehandler/handlers/init.go deleted file mode 100644 index fb48f9b56..000000000 --- a/pkg/contexts/ocm/valuemergehandler/handlers/init.go +++ /dev/null @@ -1,8 +0,0 @@ -package handlers - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/handlers/defaultmerge" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/handlers/maplistmerge" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/handlers/simplelistmerge" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/handlers/simplemapmerge" -) diff --git a/pkg/contexts/ocm/valuemergehandler/handlers/maplistmerge/config.go b/pkg/contexts/ocm/valuemergehandler/handlers/maplistmerge/config.go deleted file mode 100644 index 226f19256..000000000 --- a/pkg/contexts/ocm/valuemergehandler/handlers/maplistmerge/config.go +++ /dev/null @@ -1,41 +0,0 @@ -package maplistmerge - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/handlers/defaultmerge" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/hpi" - "github.com/open-component-model/ocm/pkg/utils" -) - -type Mode = defaultmerge.Mode - -const ( - MODE_DEFAULT = defaultmerge.MODE_DEFAULT - MODE_NONE = defaultmerge.MODE_NONE - MODE_LOCAL = defaultmerge.MODE_LOCAL - MODE_INBOUND = defaultmerge.MODE_INBOUND -) - -func NewConfig(field string, overwrite Mode, entries ...*hpi.Specification) *Config { - return &Config{ - KeyField: field, - Config: *defaultmerge.NewConfig(overwrite), - Entries: utils.Optional(entries...), - } -} - -type Config struct { - defaultmerge.Config - KeyField string `json:"keyField"` - Entries *hpi.Specification `json:"entries,omitempty"` -} - -func (c *Config) Complete(ctx hpi.Context) error { - err := c.Config.Complete(ctx) - if err != nil { - return err - } - if c.KeyField == "" { - c.KeyField = "name" - } - return nil -} diff --git a/pkg/contexts/ocm/valuemergehandler/handlers/maplistmerge/handler.go b/pkg/contexts/ocm/valuemergehandler/handlers/maplistmerge/handler.go deleted file mode 100644 index a22c163b5..000000000 --- a/pkg/contexts/ocm/valuemergehandler/handlers/maplistmerge/handler.go +++ /dev/null @@ -1,94 +0,0 @@ -package maplistmerge - -import ( - "fmt" - "reflect" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/hpi" -) - -const ALGORITHM = "mapListMerge" - -func init() { - hpi.Register(New()) -} - -type ( - // Value is the minimal structure of values usable with the merge algorithm. - Value = []Entry - Entry = map[string]interface{} -) - -func New() hpi.Handler { - return hpi.New(ALGORITHM, desc, merge) -} - -var desc = ` -This handler merges values with a list of map values by observing a key field -to identify similar map entries. -The default entry key is taken from map field name. - -It supports the following config structure: -- *keyField* *string* (optional) - - the key field to identify entries in the maps. - -- *overwrite* *string* (optional) determines how to handle conflicts. - - - none (default) no change possible, if entry differs the merge is rejected. - - local the local value is preserved. - - inbound the inbound value overwrites the local one. - -- *entries *merge spec* (optional) - - The merge specification (algorithm and config) used to merge conflicting - changes in list entries. -` - -func merge(ctx cpi.Context, c *Config, lv Value, tv *Value) (bool, error) { - var err error - - subm := false - modified := false - for _, le := range lv { - key := le[c.KeyField] - if key != nil { - found := -1 - for i, te := range *tv { - if te[c.KeyField] == key { - found = i - if !reflect.DeepEqual(le, te) { - switch c.Overwrite { - case MODE_DEFAULT: - if c.Entries != nil { - subm, te, err = hpi.GenericMerge(ctx, c.Entries, "", le, te) - if err != nil { - return false, errors.Wrapf(err, "entry identity %q", key) - } - if subm { - (*tv)[i] = te - modified = true - } - break - } - fallthrough - case MODE_NONE: - return false, fmt.Errorf("target value for %q changed", key) - case MODE_LOCAL: - (*tv)[i] = le - modified = true - } - } - } - } - if found < 0 { - *tv = append(*tv, le) - modified = true - } - } - } - return modified, nil -} diff --git a/pkg/contexts/ocm/valuemergehandler/handlers/maplistmerge/handler_test.go b/pkg/contexts/ocm/valuemergehandler/handlers/maplistmerge/handler_test.go deleted file mode 100644 index 370d01f9f..000000000 --- a/pkg/contexts/ocm/valuemergehandler/handlers/maplistmerge/handler_test.go +++ /dev/null @@ -1,195 +0,0 @@ -package maplistmerge_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/ocm" - me "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/handlers/maplistmerge" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/handlers/simplemapmerge" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/hpi" -) - -type ( - Value = me.Value - VEntry = simplemapmerge.Value - Config = me.Config -) - -const ( - ALGORITHM = me.ALGORITHM - MODE_NONE = me.MODE_NONE - MODE_LOCAL = me.MODE_LOCAL - MODE_INBOUND = me.MODE_INBOUND -) - -var ( - NewConfig = me.NewConfig - New = me.New -) - -var _ = Describe("list merge", func() { - handler := New() - - var e1, e2, e3, e4 map[string]interface{} - var va, vn Value - var a, b hpi.Value - - BeforeEach(func() { - e1 = VEntry{ - "name": "name1", - "data": "entry1", - } - e2 = VEntry{ - "name": "name2", - "data": "entry2", - } - e3 = VEntry{ - "name": "name3", - "data": "entry3", - } - e4 = VEntry{ - "name": "name4", - "data": "entry4", - } - - va = Value{e1, e2} - vn = Value{e1, e2} - - MustBeSuccessful(a.SetValue(va)) - b = a.Copy() - }) - - It("merges no change", func() { - MustBeSuccessful(handler.Merge(nil, a, &b, nil)) - Expect(b).To(Equal(a)) - }) - - It("adds new entry", func() { - MustBeSuccessful(a.SetValue(append(va, e3))) - MustBeSuccessful(handler.Merge(nil, a, &b, nil)) - Expect(b).To(Equal(a)) - }) - - It("adds new entry on both sides", func() { - MustBeSuccessful(a.SetValue(append(vn, e4))) - MustBeSuccessful(b.SetValue(append(vn, e3))) - - MustBeSuccessful(handler.Merge(nil, a, &b, nil)) - - var r Value - MustBeSuccessful(b.GetValue(&r)) - - Expect(r).To(DeepEqual(append(vn, e3, e4))) - }) - - It("updates to inbound", func() { - vn[0]["data"] = "X" - MustBeSuccessful(b.SetValue(vn)) - r := b.Copy() - MustBeSuccessful(handler.Merge(nil, a, &b, NewConfig("name", MODE_INBOUND))) - - Expect(b).To(DeepEqual(r)) - }) - - It("keeps local", func() { - vn[0]["data"] = "X" - MustBeSuccessful(b.SetValue(vn)) - MustBeSuccessful(handler.Merge(nil, a, &b, NewConfig("name", MODE_LOCAL))) - - Expect(b).To(DeepEqual(a)) - }) - - It("fails for none mode", func() { - vn[0]["data"] = "X" - MustBeSuccessful(b.SetValue(vn)) - MustFailWithMessage(ErrorFrom(handler.Merge(nil, a, &b, NewConfig("name", MODE_NONE))), "[mapListMerge]: target value for \"name1\" changed") - }) - - It("fails for wrong type", func() { - MustBeSuccessful(b.SetValue(true)) - MustFailWithMessage(ErrorFrom(handler.Merge(nil, a, &b, nil)), "[mapListMerge] inbound value is not valid: json: cannot unmarshal bool into Go value of type []map[string]interface {}") - MustFailWithMessage(ErrorFrom(handler.Merge(nil, b, &a, nil)), "[mapListMerge] local value is not valid: json: cannot unmarshal bool into Go value of type []map[string]interface {}") - }) - - Context("cascading", func() { - var d1, d2 Value - var cfg *Config - var keycfg *Config - var m1, m2 simplemapmerge.Value - - BeforeEach(func() { - m1 = simplemapmerge.Value{ - "key": "name1", - "value": "value1", - } - m2 = simplemapmerge.Value{ - "key": "name2", - "value": "value3", - } - d1 = Value{ - m1, m2, - } - - MustBeSuccessful(a.SetValue(d1)) - b = a.Copy() - MustBeSuccessful(b.GetValue(&d2)) - cfg = NewConfig("key", "", Must(hpi.NewSpecification(simplemapmerge.ALGORITHM, simplemapmerge.NewConfig(simplemapmerge.MODE_INBOUND)))) - keycfg = NewConfig("key", "") - }) - - It("handles equal", func() { - MustBeSuccessful(handler.Merge(ocm.DefaultContext(), a, &b, cfg)) - - Expect(b).To(DeepEqual(a)) - }) - - It("handles merge", func() { - d1[0]["local"] = "local" - d2[0]["inbound"] = "inbound" - - MustBeSuccessful(a.SetValue(d1)) - MustBeSuccessful(b.SetValue(d2)) - - d2[0]["local"] = "local" - - MustFailWithMessage(ErrorFrom(handler.Merge(nil, a, &b, keycfg)), "[mapListMerge]: target value for \"name1\" changed") - MustBeSuccessful(handler.Merge(ocm.DefaultContext(), a, &b, cfg)) - - var r Value - MustBeSuccessful(b.GetValue(&r)) - Expect(r).To(DeepEqual(d2)) - }) - - It("resolves to inbound", func() { - d2[0]["data"] = "inbound" - - MustBeSuccessful(a.SetValue(d1)) - MustBeSuccessful(b.SetValue(d2)) - - MustFailWithMessage(ErrorFrom(handler.Merge(nil, a, &b, keycfg)), "[mapListMerge]: target value for \"name1\" changed") - MustBeSuccessful(handler.Merge(ocm.DefaultContext(), a, &b, cfg)) - - var r Value - MustBeSuccessful(b.GetValue(&r)) - Expect(r).To(DeepEqual(d2)) - }) - - It("resolves to local", func() { - cfg = NewConfig("key", "", Must(hpi.NewSpecification(simplemapmerge.ALGORITHM, simplemapmerge.NewConfig(MODE_LOCAL)))) - - d1[0]["data"] = "local" - - MustBeSuccessful(a.SetValue(d1)) - MustBeSuccessful(b.SetValue(d2)) - - MustFailWithMessage(ErrorFrom(handler.Merge(nil, a, &b, keycfg)), "[mapListMerge]: target value for \"name1\" changed") - MustBeSuccessful(handler.Merge(ocm.DefaultContext(), a, &b, cfg)) - - var r Value - MustBeSuccessful(b.GetValue(&r)) - Expect(r).To(DeepEqual(d1)) - }) - }) -}) diff --git a/pkg/contexts/ocm/valuemergehandler/handlers/plugin/config.go b/pkg/contexts/ocm/valuemergehandler/handlers/plugin/config.go deleted file mode 100644 index 70f6559e1..000000000 --- a/pkg/contexts/ocm/valuemergehandler/handlers/plugin/config.go +++ /dev/null @@ -1,17 +0,0 @@ -package plugin - -import ( - "encoding/json" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler" -) - -type Config struct { - json.RawMessage -} - -var _ valuemergehandler.Config = (*Config)(nil) - -func (c Config) Complete(valuemergehandler.Context) error { - return nil -} diff --git a/pkg/contexts/ocm/valuemergehandler/handlers/plugin/handler.go b/pkg/contexts/ocm/valuemergehandler/handlers/plugin/handler.go deleted file mode 100644 index 03cdbefcb..000000000 --- a/pkg/contexts/ocm/valuemergehandler/handlers/plugin/handler.go +++ /dev/null @@ -1,61 +0,0 @@ -package plugin - -import ( - "encoding/json" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/hpi" -) - -// pluginHandler delegates action to a plugin based handler. -type pluginHandler struct { - plugin plugin.Plugin - descriptor *descriptor.ValueMergeHandlerDescriptor -} - -func New(p plugin.Plugin, name string) (hpi.Handler, error) { - md := p.GetValueMergeHandlerDescriptor(name) - if md == nil { - return nil, errors.ErrUnknown(hpi.KIND_VALUE_MERGE_ALGORITHM, name, plugin.KIND_PLUGIN, p.Name()) - } - - return &pluginHandler{ - plugin: p, - descriptor: md, - }, nil -} - -func (b *pluginHandler) Algorithm() string { - return b.descriptor.Name -} - -func (b *pluginHandler) Description() string { - return b.descriptor.Description -} - -func (b *pluginHandler) DecodeConfig(data []byte) (hpi.Config, error) { - var cfg Config - err := json.Unmarshal(data, &cfg) - if err != nil { - return nil, err - } - return &cfg, nil -} - -func (b *pluginHandler) Merge(_ hpi.Context, src hpi.Value, tgt *hpi.Value, cfg hpi.Config) (bool, error) { - spec, err := hpi.NewSpecification(b.descriptor.Name, cfg) - if err != nil { - return false, err - } - mod, r, err := b.plugin.MergeValue(spec, src, *tgt) - if err != nil { - return false, err - } - if mod { - tgt.RawMessage = r.RawMessage - } - return mod, nil -} diff --git a/pkg/contexts/ocm/valuemergehandler/handlers/plugin/handler_test.go b/pkg/contexts/ocm/valuemergehandler/handlers/plugin/handler_test.go deleted file mode 100644 index 930dc5350..000000000 --- a/pkg/contexts/ocm/valuemergehandler/handlers/plugin/handler_test.go +++ /dev/null @@ -1,67 +0,0 @@ -//go:build unix - -package plugin_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/testutils" - . "github.com/open-component-model/ocm/pkg/env/builder" - - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/registration" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/handlers/defaultmerge" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/hpi" -) - -const ( - PLUGIN = "merge" - ALGORITHM = "acme.org/test" -) - -var _ = Describe("plugin value merge handler", func() { - var ctx ocm.Context - var env *Builder - var plugins TempPluginDir - var registry valuemergehandler.Registry - - BeforeEach(func() { - env = NewBuilder(nil) - ctx = env.OCMContext() - plugins = Must(ConfigureTestPlugins(ctx, "testdata")) - registry = valuemergehandler.For(ctx) - }) - - AfterEach(func() { - plugins.Cleanup() - env.Cleanup() - }) - - It("executes handler", func() { - registration.RegisterExtensions(ctx) - - Expect(registry.GetHandler(ALGORITHM)).NotTo(BeNil()) - - spec := Must(valuemergehandler.NewSpecification(ALGORITHM, defaultmerge.NewConfig("test"))) - var local, inbound valuemergehandler.Value - - local.SetValue("local") - inbound.SetValue("inbound") - mod := Must(valuemergehandler.Merge(ctx, spec, "", local, &inbound)) - - Expect(mod).To(BeTrue()) - Expect(inbound.RawMessage).To(YAMLEqual(`{"mode":"resolved"}`)) - }) - - It("assigns specs", func() { - registration.RegisterExtensions(ctx) - - Expect(registry.GetHandler(ALGORITHM)).NotTo(BeNil()) - - s := registry.GetAssignment(hpi.LabelHint("testlabel", "v2")) - Expect(s).NotTo(BeNil()) - Expect(s.Algorithm).To(Equal("simpleMapMerge")) - }) -}) diff --git a/pkg/contexts/ocm/valuemergehandler/handlers/simplelistmerge/config.go b/pkg/contexts/ocm/valuemergehandler/handlers/simplelistmerge/config.go deleted file mode 100644 index 78492c28e..000000000 --- a/pkg/contexts/ocm/valuemergehandler/handlers/simplelistmerge/config.go +++ /dev/null @@ -1,19 +0,0 @@ -package simplelistmerge - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/hpi" -) - -func NewConfig(fields ...string) *Config { - return &Config{IgnoredFields: fields} -} - -type Config struct { - IgnoredFields []string `json:"ignoredFields,omitempty"` -} - -var _ hpi.Config = (*Config)(nil) - -func (c *Config) Complete(ctx hpi.Context) error { - return nil -} diff --git a/pkg/contexts/ocm/valuemergehandler/handlers/simplelistmerge/handler.go b/pkg/contexts/ocm/valuemergehandler/handlers/simplelistmerge/handler.go deleted file mode 100644 index 0a30f4524..000000000 --- a/pkg/contexts/ocm/valuemergehandler/handlers/simplelistmerge/handler.go +++ /dev/null @@ -1,63 +0,0 @@ -package simplelistmerge - -import ( - "reflect" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/hpi" -) - -const ALGORITHM = "simpleListMerge" - -func init() { - hpi.Register(New()) -} - -type ( - // Value is the minimal structure of values usable with the merge algorithm. - Value = []Entry - Entry = interface{} -) - -func New() hpi.Handler { - return hpi.New(ALGORITHM, desc, merge) -} - -var desc = ` -This handler merges simple list labels values. - -It supports the following config structure: -- *overwrite* *string* (optional) determines how to handle conflicts. - -` - -func merge(ctx hpi.Context, c *Config, lv Value, tv *Value) (bool, error) { - modified := false -outer: - for _, le := range lv { - for _, te := range *tv { - if equal(c, le, te) { - continue outer - } - } - *tv = append(*tv, le) - modified = true - } - return modified, nil -} - -func equal(c *Config, le, te Entry) bool { - if c == nil || len(c.IgnoredFields) == 0 { - return reflect.DeepEqual(le, te) - } - - if lm, ok := le.(map[string]interface{}); ok { - if tm, ok := te.(map[string]interface{}); ok { - for _, n := range c.IgnoredFields { - delete(lm, n) - delete(tm, n) - } - return reflect.DeepEqual(lm, tm) - } - } - return reflect.DeepEqual(le, te) -} diff --git a/pkg/contexts/ocm/valuemergehandler/handlers/simplelistmerge/handler_test.go b/pkg/contexts/ocm/valuemergehandler/handlers/simplelistmerge/handler_test.go deleted file mode 100644 index b7bdd07cb..000000000 --- a/pkg/contexts/ocm/valuemergehandler/handlers/simplelistmerge/handler_test.go +++ /dev/null @@ -1,74 +0,0 @@ -package simplelistmerge_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - me "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/handlers/simplelistmerge" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/hpi" -) - -type ( - Value = me.Value - Config = me.Config -) - -var ( - NewConfig = me.NewConfig - New = me.New -) - -var _ = Describe("list merge", func() { - handler := New() - - var e1, e2 Value - var a, b hpi.Value - - BeforeEach(func() { - e1 = []interface{}{ - "name1", - "entry1", - } - e2 = []interface{}{ - "name1", - "entry1", - } - - MustBeSuccessful(a.SetValue(e1)) - b = a.Copy() - }) - - It("merges no change", func() { - MustBeSuccessful(handler.Merge(nil, a, &b, nil)) - Expect(b).To(Equal(a)) - }) - - It("adds new entry", func() { - e1 = append(e1, "local") - MustBeSuccessful(a.SetValue(e1)) - MustBeSuccessful(handler.Merge(nil, a, &b, nil)) - Expect(b).To(Equal(a)) - }) - - It("adds new entry on both sides", func() { - e1 = append(e1, "local") - e2 = append(e2, "inbound") - MustBeSuccessful(a.SetValue(e1)) - MustBeSuccessful(b.SetValue(e2)) - - MustBeSuccessful(handler.Merge(nil, a, &b, nil)) - - var r Value - MustBeSuccessful(b.GetValue(&r)) - - e2 = append(e2, "local") - Expect(r).To(DeepEqual(e2)) - }) - - It("fails for wrong type", func() { - MustBeSuccessful(b.SetValue(true)) - MustFailWithMessage(ErrorFrom(handler.Merge(nil, a, &b, nil)), "[simpleListMerge] inbound value is not valid: json: cannot unmarshal bool into Go value of type []interface {}") - MustFailWithMessage(ErrorFrom(handler.Merge(nil, b, &a, nil)), "[simpleListMerge] local value is not valid: json: cannot unmarshal bool into Go value of type []interface {}") - }) -}) diff --git a/pkg/contexts/ocm/valuemergehandler/handlers/simplemapmerge/config.go b/pkg/contexts/ocm/valuemergehandler/handlers/simplemapmerge/config.go deleted file mode 100644 index 952837003..000000000 --- a/pkg/contexts/ocm/valuemergehandler/handlers/simplemapmerge/config.go +++ /dev/null @@ -1,28 +0,0 @@ -package simplemapmerge - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/handlers/defaultmerge" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/hpi" - "github.com/open-component-model/ocm/pkg/utils" -) - -type Mode = defaultmerge.Mode - -const ( - MODE_DEFAULT = defaultmerge.MODE_DEFAULT - MODE_NONE = defaultmerge.MODE_NONE - MODE_LOCAL = defaultmerge.MODE_LOCAL - MODE_INBOUND = defaultmerge.MODE_INBOUND -) - -func NewConfig(overwrite Mode, entries ...*hpi.Specification) *Config { - return &Config{ - Config: *defaultmerge.NewConfig(overwrite), - Entries: utils.Optional(entries...), - } -} - -type Config struct { - defaultmerge.Config - Entries *hpi.Specification `json:"entries,omitempty"` -} diff --git a/pkg/contexts/ocm/valuemergehandler/handlers/simplemapmerge/handler.go b/pkg/contexts/ocm/valuemergehandler/handlers/simplemapmerge/handler.go deleted file mode 100644 index b44e147fb..000000000 --- a/pkg/contexts/ocm/valuemergehandler/handlers/simplemapmerge/handler.go +++ /dev/null @@ -1,89 +0,0 @@ -package simplemapmerge - -import ( - "fmt" - "reflect" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/hpi" -) - -const ALGORITHM = "simpleMapMerge" - -func init() { - hpi.Register(New()) -} - -type ( - // Value is the minimal structure of values usable with the merge algorithm. - Value = map[string]Entry - Entry = interface{} -) - -func New() hpi.Handler { - return hpi.New(ALGORITHM, desc, merge) -} - -var desc = ` -This handler merges simple map labels values. - -It supports the following config structure: -- *overwrite* *string* (optional) determines how to handle conflicts. - - - none (default) no change possible, if entry differs the merge is rejected. - - local the local value is preserved. - - inbound the inbound value overwrites the local one. - -- *entries *merge spec* (optional) - - The merge specification (algorithm and config) used to merge conflicting - changes in map entries. -` - -func merge(ctx hpi.Context, c *Config, lv Value, tv *Value) (bool, error) { - var err error - - subm := false - modified := false - for lk, le := range lv { - if te, ok := (*tv)[lk]; ok { - if !reflect.DeepEqual(le, te) { - switch c.Overwrite { - case MODE_DEFAULT: - if c.Entries != nil { - hpi.Log.Trace("different entry found in target -> merge it", "name", lk, "entries", c.Entries) - subm, te, err = hpi.GenericMerge(ctx, c.Entries, "", le, te) - if err != nil { - return false, errors.Wrapf(err, "map key %q", lk) - } - if subm { - (*tv)[lk] = te - modified = true - hpi.Log.Trace("entry merge result", "result", (*tv)) - } else { - hpi.Log.Trace("not modified") - } - break - } - fallthrough - case MODE_NONE: - hpi.Log.Trace("different entry found in target -> fail", "name", lk) - return false, fmt.Errorf("target value for %q changed", lk) - case MODE_LOCAL: - (*tv)[lk] = le - hpi.Log.Trace("different entry found in target -> use local", "name", lk, "result", (*tv)) - modified = true - } - } else { - hpi.Log.Trace("entry found in target", "name", lk) - } - } else { - (*tv)[lk] = le - hpi.Log.Trace("entry not found in target -> append it", "name", lk, "result", (*tv)) - modified = true - } - } - hpi.Log.Trace("merge result", "modified", modified, "result", (*tv)) - return modified, nil -} diff --git a/pkg/contexts/ocm/valuemergehandler/handlers/simplemapmerge/handler_test.go b/pkg/contexts/ocm/valuemergehandler/handlers/simplemapmerge/handler_test.go deleted file mode 100644 index f951af1ef..000000000 --- a/pkg/contexts/ocm/valuemergehandler/handlers/simplemapmerge/handler_test.go +++ /dev/null @@ -1,175 +0,0 @@ -package simplemapmerge_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/ocm" - me "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/handlers/simplemapmerge" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/hpi" -) - -type ( - Value = me.Value - Config = me.Config -) - -const ( - ALGORITHM = me.ALGORITHM - MODE_NONE = me.MODE_NONE - MODE_LOCAL = me.MODE_LOCAL - MODE_INBOUND = me.MODE_INBOUND -) - -var ( - NewConfig = me.NewConfig - New = me.New -) - -var _ = Describe("list merge", func() { - handler := New() - - var e1, e2 Value - var a, b hpi.Value - - BeforeEach(func() { - e1 = map[string]interface{}{ - "name": "name1", - "data": "entry1", - } - e2 = map[string]interface{}{ - "name": "name1", - "data": "entry1", - } - - MustBeSuccessful(a.SetValue(e1)) - b = a.Copy() - }) - - It("merges no change", func() { - MustBeSuccessful(handler.Merge(nil, a, &b, nil)) - Expect(b).To(Equal(a)) - }) - - It("adds new entry", func() { - e1["local"] = "local" - MustBeSuccessful(a.SetValue(e1)) - MustBeSuccessful(handler.Merge(nil, a, &b, nil)) - Expect(b).To(Equal(a)) - }) - - It("adds new entry on both sides", func() { - e1["local"] = "local" - e2["inbound"] = "inbound" - MustBeSuccessful(a.SetValue(e1)) - MustBeSuccessful(b.SetValue(e2)) - - MustBeSuccessful(handler.Merge(nil, a, &b, nil)) - - var r Value - MustBeSuccessful(b.GetValue(&r)) - - e2["local"] = "local" - Expect(r).To(DeepEqual(e2)) - }) - - It("updates to inbound", func() { - e2["name"] = "inbound" - MustBeSuccessful(b.SetValue(e2)) - r := b.Copy() - MustBeSuccessful(handler.Merge(nil, a, &b, NewConfig(MODE_INBOUND, nil))) - - Expect(b).To(DeepEqual(r)) - }) - - It("keeps local", func() { - e2["name"] = "inbound" - MustBeSuccessful(b.SetValue(e1)) - MustBeSuccessful(handler.Merge(nil, a, &b, NewConfig(MODE_LOCAL, nil))) - - Expect(b).To(DeepEqual(a)) - }) - - It("fails for none mode", func() { - e2["data"] = "X" - MustBeSuccessful(b.SetValue(e2)) - MustFailWithMessage(ErrorFrom(handler.Merge(nil, a, &b, NewConfig(MODE_NONE, nil))), "[simpleMapMerge]: target value for \"data\" changed") - }) - - It("fails for wrong type", func() { - MustBeSuccessful(b.SetValue(true)) - MustFailWithMessage(ErrorFrom(handler.Merge(nil, a, &b, nil)), "[simpleMapMerge] inbound value is not valid: json: cannot unmarshal bool into Go value of type map[string]interface {}") - MustFailWithMessage(ErrorFrom(handler.Merge(nil, b, &a, nil)), "[simpleMapMerge] local value is not valid: json: cannot unmarshal bool into Go value of type map[string]interface {}") - }) - - Context("cascading", func() { - var d1, d2 Value - var cfg *Config - - BeforeEach(func() { - d1 = Value{ - "k1": e1, - "k2": e2, - } - - MustBeSuccessful(a.SetValue(d1)) - b = a.Copy() - MustBeSuccessful(b.GetValue(&d2)) - cfg = NewConfig("", Must(hpi.NewSpecification(ALGORITHM, NewConfig(MODE_INBOUND)))) - }) - - It("handles equal", func() { - MustBeSuccessful(handler.Merge(ocm.DefaultContext(), a, &b, cfg)) - - Expect(b).To(DeepEqual(a)) - }) - - It("handles merge", func() { - d1["k1"].(Value)["local"] = "local" - d2["k1"].(Value)["inbound"] = "inbound" - - MustBeSuccessful(a.SetValue(d1)) - MustBeSuccessful(b.SetValue(d2)) - - d2["k1"].(Value)["local"] = "local" - - MustFailWithMessage(ErrorFrom(handler.Merge(nil, a, &b, nil)), "[simpleMapMerge]: target value for \"k1\" changed") - MustBeSuccessful(handler.Merge(ocm.DefaultContext(), a, &b, cfg)) - - var r Value - MustBeSuccessful(b.GetValue(&r)) - Expect(r).To(DeepEqual(d2)) - }) - - It("resolves to inbound", func() { - d2["k1"].(Value)["data"] = "inbound" - - MustBeSuccessful(a.SetValue(d1)) - MustBeSuccessful(b.SetValue(d2)) - - MustFailWithMessage(ErrorFrom(handler.Merge(nil, a, &b, nil)), "[simpleMapMerge]: target value for \"k1\" changed") - MustBeSuccessful(handler.Merge(ocm.DefaultContext(), a, &b, cfg)) - - var r Value - MustBeSuccessful(b.GetValue(&r)) - Expect(r).To(DeepEqual(d2)) - }) - - It("resolves to local", func() { - cfg = NewConfig("", Must(hpi.NewSpecification(ALGORITHM, NewConfig(MODE_LOCAL)))) - - d1["k1"].(Value)["data"] = "local" - - MustBeSuccessful(a.SetValue(d1)) - MustBeSuccessful(b.SetValue(d2)) - - MustFailWithMessage(ErrorFrom(handler.Merge(nil, a, &b, nil)), "[simpleMapMerge]: target value for \"k1\" changed") - MustBeSuccessful(handler.Merge(ocm.DefaultContext(), a, &b, cfg)) - - var r Value - MustBeSuccessful(b.GetValue(&r)) - Expect(r).To(DeepEqual(d1)) - }) - }) -}) diff --git a/pkg/contexts/ocm/valuemergehandler/hpi/interface.go b/pkg/contexts/ocm/valuemergehandler/hpi/interface.go deleted file mode 100644 index 5d22bc8b5..000000000 --- a/pkg/contexts/ocm/valuemergehandler/hpi/interface.go +++ /dev/null @@ -1,86 +0,0 @@ -// Package hpi contains the Handler Programming Interface for -// value merge handlers -package hpi - -import ( - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/internal" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -// resolve package cycle among default merge handler and -// labelmergehandler by separating commonly used types -// into this package - -// same problem for the embedding into the OCM environment -// required for the ocm.Context access. -// Because of this cycle, the registry implementation and the -// required types have to be placed into the internal package of -// ocm and forwarded to the cpi packages. From there it can be consumed -// here to break the dependency cycle. - -type ( - Context = internal.Context - Handler = internal.Handler - Config = internal.Config - Registry = internal.Registry - Specification = internal.Specification - Value = internal.Value - Hint = internal.Hint -) - -const KIND_VALUE_MERGE_ALGORITHM = metav1.KIND_VALUE_MERGE_ALGORITHM - -func Register(h Handler) { - internal.Register(h) -} - -func Assign(hint Hint, spec *Specification) { - internal.Assign(hint, spec) -} - -func NewSpecification(algo string, cfg ...Config) (*Specification, error) { - raw, err := runtime.AsRawMessage(utils.Optional(cfg...)) - if err != nil { - return nil, err - } - return &Specification{ - Algorithm: algo, - Config: raw, - }, nil -} - -func NewRegistry(base ...Registry) Registry { - return internal.NewRegistry(base...) -} - -func LabelHint(name string, optversion ...string) Hint { - hint := "label:" + name - v := utils.Optional(optversion...) - if v != "" { - hint += "@" + v - } - return Hint(hint) -} - -//////////////////////////////////////////////////////////////////////////////// - -const ATTR_MERGE_HANDLERS = "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandlers" - -func For(ctx cpi.ContextProvider) Registry { - if ctx == nil { - return internal.DefaultRegistry - } - return ctx.OCMContext().GetAttributes().GetOrCreateAttribute(ATTR_MERGE_HANDLERS, create).(Registry) -} - -func create(datacontext.Context) interface{} { - return NewRegistry(internal.DefaultRegistry) -} - -func SetFor(ctx datacontext.Context, registry Registry) { - ctx.GetAttributes().SetAttribute(ATTR_MERGE_HANDLERS, registry) -} diff --git a/pkg/contexts/ocm/valuemergehandler/hpi/logging.go b/pkg/contexts/ocm/valuemergehandler/hpi/logging.go deleted file mode 100644 index f56065100..000000000 --- a/pkg/contexts/ocm/valuemergehandler/hpi/logging.go +++ /dev/null @@ -1,9 +0,0 @@ -package hpi - -import ( - ocmlog "github.com/open-component-model/ocm/pkg/logging" -) - -var REALM = ocmlog.DefineSubRealm("value marge handling", "valuemerge") - -var Log = ocmlog.DynamicLogger(REALM) diff --git a/pkg/contexts/ocm/valuemergehandler/hpi/merge.go b/pkg/contexts/ocm/valuemergehandler/hpi/merge.go deleted file mode 100644 index 528d9959d..000000000 --- a/pkg/contexts/ocm/valuemergehandler/hpi/merge.go +++ /dev/null @@ -1,97 +0,0 @@ -package hpi - -import ( - "strings" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/handlers/defaultmerge" -) - -func AsValue(v interface{}) (*Value, error) { - if v == nil { - return nil, nil - } - var r Value - err := r.SetValue(v) - if err != nil { - return nil, err - } - return &r, nil -} - -func GenericMerge[T any](ctx Context, m *Specification, hint string, local T, inbound T) (bool, T, error) { - var Nil T - - l, err := AsValue(local) - if err != nil { - return false, Nil, err - } - t, err := AsValue(inbound) - if err != nil { - return false, Nil, err - } - mod, err := Merge(ctx, m, "", *l, t) - if err != nil { - return false, Nil, err - } - if mod { - inbound = Nil - err = t.GetValue(&inbound) - if err != nil { - return false, Nil, errors.Wrapf(err, "cannot value merge result") - } - } - return mod, inbound, nil -} - -// Merge merges two value using the given merge specification. -// The hint describes a merge hint if no algorithm is specified. -// It used the format [@]. If used the is looks -// for an assignment for this hint, first with version and the without version. -func Merge(ctx Context, m *Specification, hint Hint, local Value, inbound *Value) (bool, error) { - var err error - - reg := For(ctx) - - if m == nil { - m = &Specification{} - } else { - t := *m - m = &t - } - if m.Algorithm == "" && hint != "" { - spec := reg.GetAssignment(hint) - if spec == nil { - idx := strings.LastIndex(string(hint), "@") - if idx > 1 { - hint = hint[:idx] - } - spec = reg.GetAssignment(hint) - } - if spec != nil { - m = spec - } - } - if m.Algorithm == "" { - m.Algorithm = defaultmerge.ALGORITHM - } - - h := reg.GetHandler(m.Algorithm) - if h == nil { - return false, errors.ErrUnknown(KIND_VALUE_MERGE_ALGORITHM, m.Algorithm) - } - - Log.Trace("merge handler", "handler", m.Algorithm, "config", m.Config) - var cfg Config - if len(m.Config) != 0 { - cfg, err = h.DecodeConfig(m.Config) - if err == nil { - err = cfg.Complete(ctx) - } - if err != nil { - return false, errors.Wrapf(err, "invalid merge config for algorithm %q", m.Algorithm) - } - } - return h.Merge(ctx, local, inbound, cfg) -} diff --git a/pkg/contexts/ocm/valuemergehandler/hpi/setup.go b/pkg/contexts/ocm/valuemergehandler/hpi/setup.go deleted file mode 100644 index 7b1539580..000000000 --- a/pkg/contexts/ocm/valuemergehandler/hpi/setup.go +++ /dev/null @@ -1,28 +0,0 @@ -package hpi - -import ( - "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/valuemergehandler/internal" -) - -func init() { - datacontext.RegisterSetupHandler(datacontext.SetupHandlerFunction(setupContext)) -} - -func setupContext(mode datacontext.BuilderMode, ctx datacontext.Context) { - if octx, ok := ctx.(cpi.Context); ok { - switch mode { - case datacontext.MODE_SHARED: - fallthrough - case datacontext.MODE_DEFAULTED: - // do nothing, fallback to the default attribute lookup - case datacontext.MODE_EXTENDED: - SetFor(octx, NewRegistry(internal.DefaultRegistry)) - case datacontext.MODE_CONFIGURED: - SetFor(octx, internal.DefaultRegistry.Copy()) - case datacontext.MODE_INITIAL: - SetFor(octx, NewRegistry()) - } - } -} diff --git a/pkg/contexts/ocm/valuemergehandler/hpi/support.go b/pkg/contexts/ocm/valuemergehandler/hpi/support.go deleted file mode 100644 index d22f91003..000000000 --- a/pkg/contexts/ocm/valuemergehandler/hpi/support.go +++ /dev/null @@ -1,19 +0,0 @@ -package hpi - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/internal" -) - -type EmptyConfig struct{} - -var _ Config = (*EmptyConfig)(nil) - -func (c *EmptyConfig) Complete(ctx Context) error { - return nil -} - -type Merger[C, T any] func(ctx Context, cfg C, local T, target *T) (bool, error) - -func New[C any, L any, P internal.ConfigPointer[C]](algo string, desc string, merger internal.Merger[P, L]) Handler { - return internal.New[C, L, P](algo, desc, merger) -} diff --git a/pkg/contexts/ocm/valuemergehandler/interface.go b/pkg/contexts/ocm/valuemergehandler/interface.go deleted file mode 100644 index 13cd3ccda..000000000 --- a/pkg/contexts/ocm/valuemergehandler/interface.go +++ /dev/null @@ -1,42 +0,0 @@ -package valuemergehandler - -import ( - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/config" - _ "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/handlers" - - "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/valuemergehandler/hpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/internal" -) - -type ( - Context = internal.Context - Handler = internal.Handler - Handlers = internal.Handlers - Config = internal.Config - Registry = internal.Registry - Specification = internal.Specification - Value = internal.Value -) - -const ( - KIND_VALUE_MERGE_ALGORITHM = hpi.KIND_VALUE_MERGE_ALGORITHM - KIND_VALUESET = "value set" -) - -func NewSpecification(algo string, cfg Config) (*Specification, error) { - return hpi.NewSpecification(algo, cfg) -} - -func NewRegistry(base ...Registry) Registry { - return internal.NewRegistry(base...) -} - -func For(ctx cpi.ContextProvider) Registry { - return hpi.For(ctx) -} - -func SetFor(ctx datacontext.Context, registry Registry) { - hpi.SetFor(ctx, registry) -} diff --git a/pkg/contexts/ocm/valuemergehandler/internal/interface.go b/pkg/contexts/ocm/valuemergehandler/internal/interface.go deleted file mode 100644 index bf118e2c0..000000000 --- a/pkg/contexts/ocm/valuemergehandler/internal/interface.go +++ /dev/null @@ -1,33 +0,0 @@ -package internal - -import ( - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/runtime" -) - -// resolve package cycle among default merge handler and -// labelmergehandler by separating commonly used types -// into this package - -// same problem for the embedding into the OCM environment -// required for the ocm.Context access. -// Because of this cycle, the registry implementation and the -// required types have to be placed into the internal package of -// ocm and forwarded to the cpi packages. From there it can be consumed -// here to break the dependency cycle. - -type ( - Context = cpi.Context - Specification = metav1.MergeAlgorithmSpecification - Value = runtime.RawValue - Hint string -) - -func Register(h Handler) { - DefaultRegistry.RegisterHandler(h) -} - -func Assign(hint Hint, spec *Specification) { - DefaultRegistry.AssignHandler(hint, spec) -} diff --git a/pkg/contexts/ocm/valuemergehandler/internal/support.go b/pkg/contexts/ocm/valuemergehandler/internal/support.go deleted file mode 100644 index bc4dfdc49..000000000 --- a/pkg/contexts/ocm/valuemergehandler/internal/support.go +++ /dev/null @@ -1,92 +0,0 @@ -package internal - -import ( - "fmt" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/runtime" -) - -type ConfigPointer[T any] interface { - Config - *T -} - -type Merger[C, T any] func(ctx Context, cfg C, local T, target *T) (bool, error) - -func New[C any, L any, P ConfigPointer[C]](algo string, desc string, merger Merger[P, L]) Handler { - return &HandlerSupport[C, L, P]{ - algorithm: algo, - description: desc, - merger: merger, - } -} - -// HandlerSupport is a basic support for label merge handlers. -type HandlerSupport[C any, L any, P ConfigPointer[C]] struct { - algorithm string - description string - merger Merger[P, L] -} - -func (h *HandlerSupport[C, L, P]) Algorithm() string { - return h.algorithm -} - -func (h *HandlerSupport[C, L, P]) Description() string { - return h.description -} - -func (h HandlerSupport[C, L, P]) DecodeConfig(data []byte) (Config, error) { - var cfg C - err := runtime.DefaultYAMLEncoding.Unmarshal(data, &cfg) - if err != nil { - return nil, err - } - var p P = &cfg - return p, nil -} - -func (h *HandlerSupport[C, L, P]) Merge(ctx Context, local Value, inbound *Value, cfg Config) (bool, error) { - var c P - - if cfg == nil { - var zero C - c = &zero - err := c.Complete(ctx) - if err != nil { - return false, errors.Wrapf(err, "[%s] invalid initial config") - } - } else { - var ok bool - - c, ok = cfg.(P) - if !ok { - return false, errors.ErrInvalid("[%s] value merge config type", h.algorithm, fmt.Sprintf("%T", cfg)) - } - } - - var lv L - if err := local.GetValue(&lv); err != nil { - return false, errors.Wrapf(err, "[%s] local value is not valid", h.algorithm) - } - - var tv L - if err := inbound.GetValue(&tv); err != nil { - return false, errors.Wrapf(err, "[%s] inbound value is not valid", h.algorithm) - } - - modified, err := h.merger(ctx, c, lv, &tv) - if err != nil { - return false, errors.Wrapf(err, "[%s]", h.algorithm) - } - - if modified { - err := inbound.SetValue(tv) - if err != nil { - return false, err - } - } - return modified, nil -} diff --git a/pkg/contexts/ocm/valuemergehandler/merge.go b/pkg/contexts/ocm/valuemergehandler/merge.go deleted file mode 100644 index a08c57ce9..000000000 --- a/pkg/contexts/ocm/valuemergehandler/merge.go +++ /dev/null @@ -1,14 +0,0 @@ -package valuemergehandler - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/valuemergehandler/hpi" -) - -func Merge(ctx cpi.Context, m *Specification, hint hpi.Hint, local Value, inbound *Value) (bool, error) { - return hpi.Merge(ctx, m, hint, local, inbound) -} - -func LabelHint(name string, optversion ...string) hpi.Hint { - return hpi.LabelHint(name, optversion...) -} diff --git a/pkg/contexts/ocm/valuemergehandler/usage.go b/pkg/contexts/ocm/valuemergehandler/usage.go deleted file mode 100644 index b8583a2f0..000000000 --- a/pkg/contexts/ocm/valuemergehandler/usage.go +++ /dev/null @@ -1,18 +0,0 @@ -package valuemergehandler - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/listformat" -) - -func Usage(ctx ocm.Context) string { - usage := listformat.FormatMapElements("default", For(ctx).GetHandlers()) + ` -` - list := For(ctx).GetAssignments() - if len(list) > 0 { - usage += ` -The following label assignments are configured: -` + listformat.FormatMapElements("", list) - } - return usage -} diff --git a/pkg/contexts/options/utils.go b/pkg/contexts/options/utils.go deleted file mode 100644 index 96702b11f..000000000 --- a/pkg/contexts/options/utils.go +++ /dev/null @@ -1,19 +0,0 @@ -package options - -import ( - "reflect" - - "github.com/mandelsoft/goutils/generics" -) - -func FilterOptions[T any, O any](opts []O) []T { - var found []T - - t := generics.TypeOf[T]() - for _, o := range opts { - if reflect.TypeOf(o).AssignableTo(t) { - found = append(found, generics.Cast[T](o)) - } - } - return found -} diff --git a/pkg/dirtree/context.go b/pkg/dirtree/context.go deleted file mode 100644 index d31e78086..000000000 --- a/pkg/dirtree/context.go +++ /dev/null @@ -1,53 +0,0 @@ -package dirtree - -import ( - "crypto/sha1" //nolint: gosec // required - "fmt" - "hash" - "io" - - "github.com/mandelsoft/logging" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/utils" -) - -type Context interface { - logging.Context - Hasher() hash.Hash - FileMode(vfs.FileMode) Mode - DirMode(vfs.FileMode) Mode - LinkMode(vfs.FileMode) Mode - WriteHeader(w io.Writer, typ string, size int64) error -} - -// DefaultContext provides a default directory tree hashing context. -// It is based on the Git tree hash mechanism. -func DefaultContext(ctx ...logging.Context) Context { - return &defaultContext{utils.OptionalDefaulted(LogContext, ctx...)} -} - -type defaultContext struct { - logging.Context -} - -func (d defaultContext) Hasher() hash.Hash { - return sha1.New() //nolint: gosec // required -} - -func (d defaultContext) FileMode(mode vfs.FileMode) Mode { - return FileMode(mode) | ModeBlob -} - -func (d defaultContext) DirMode(mode vfs.FileMode) Mode { - return ModeDir -} - -func (d defaultContext) LinkMode(mode vfs.FileMode) Mode { - return ModeSym -} - -func (d defaultContext) WriteHeader(w io.Writer, typ string, size int64) error { - _, err := w.Write([]byte(fmt.Sprintf("%s %d\000", typ, size))) - return err -} diff --git a/pkg/docker/resolver.go b/pkg/docker/resolver.go deleted file mode 100644 index 9776a6029..000000000 --- a/pkg/docker/resolver.go +++ /dev/null @@ -1,656 +0,0 @@ -package docker - -import ( - "context" - "fmt" - "io" - "net/http" - "net/url" - "path" - "strings" - - "github.com/containerd/containerd/errdefs" - "github.com/containerd/containerd/images" - "github.com/containerd/containerd/log" - "github.com/containerd/containerd/reference" - "github.com/containerd/containerd/remotes/docker/schema1" - "github.com/containerd/containerd/version" - "github.com/opencontainers/go-digest" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "golang.org/x/net/context/ctxhttp" - - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/docker/resolve" -) - -var ( - // ErrInvalidAuthorization is used when credentials are passed to a server but - // those credentials are rejected. - ErrInvalidAuthorization = errors.New("authorization failed") - - // MaxManifestSize represents the largest size accepted from a registry - // during resolution. Larger manifests may be accepted using a - // resolution method other than the registry. - // - // NOTE: The max supported layers by some runtimes is 128 and individual - // layers will not contribute more than 256 bytes, making a - // reasonable limit for a large image manifests of 32K bytes. - // 4M bytes represents a much larger upper bound for images which may - // contain large annotations or be non-images. A proper manifest - // design puts large metadata in subobjects, as is consistent the - // intent of the manifest design. - MaxManifestSize int64 = 4 * 1048 * 1048 -) - -// Authorizer is used to authorize HTTP requests based on 401 HTTP responses. -// An Authorizer is responsible for caching tokens or credentials used by -// requests. -type Authorizer interface { - // Authorize sets the appropriate `Authorization` header on the given - // request. - // - // If no authorization is found for the request, the request remains - // unmodified. It may also add an `Authorization` header as - // "bearer " - // "basic " - Authorize(context.Context, *http.Request) error - - // AddResponses adds a 401 response for the authorizer to consider when - // authorizing requests. The last response should be unauthorized and - // the previous requests are used to consider redirects and retries - // that may have led to the 401. - // - // If response is not handled, returns `ErrNotImplemented` - AddResponses(context.Context, []*http.Response) error -} - -// ResolverOptions are used to configured a new Docker register resolver. -type ResolverOptions struct { - // Hosts returns registry host configurations for a namespace. - Hosts RegistryHosts - - // Headers are the HTTP request header fields sent by the resolver - Headers http.Header - - // Tracker is used to track uploads to the registry. This is used - // since the registry does not have upload tracking and the existing - // mechanism for getting blob upload status is expensive. - Tracker StatusTracker - - // Authorizer is used to authorize registry requests - // Deprecated: use Hosts - Authorizer Authorizer - - // Credentials provides username and secret given a host. - // If username is empty but a secret is given, that secret - // is interpreted as a long lived token. - // Deprecated: use Hosts - Credentials func(string) (string, string, error) - - // Host provides the hostname given a namespace. - // Deprecated: use Hosts - Host func(string) (string, error) - - // PlainHTTP specifies to use plain http and not https - // Deprecated: use Hosts - PlainHTTP bool - - // Client is the http client to used when making registry requests - // Deprecated: use Hosts - Client *http.Client -} - -// DefaultHost is the default host function. -func DefaultHost(ns string) (string, error) { - if ns == "docker.io" { - return "registry-1.docker.io", nil - } - return ns, nil -} - -type dockerResolver struct { - hosts RegistryHosts - header http.Header - resolveHeader http.Header - tracker StatusTracker -} - -// NewResolver returns a new resolver to a Docker registry. -func NewResolver(options ResolverOptions) resolve.Resolver { - if options.Tracker == nil { - options.Tracker = NewInMemoryTracker() - } - - if options.Headers == nil { - options.Headers = make(http.Header) - } - if _, ok := options.Headers["User-Agent"]; !ok { - options.Headers.Set("User-Agent", "containerd/"+version.Version) - } - - resolveHeader := http.Header{} - if _, ok := options.Headers["Accept"]; !ok { - // set headers for all the types we support for resolution. - resolveHeader.Set("Accept", strings.Join([]string{ - images.MediaTypeDockerSchema2Manifest, - images.MediaTypeDockerSchema2ManifestList, - ocispec.MediaTypeImageManifest, - ocispec.MediaTypeImageIndex, "*/*", - }, ", ")) - } else { - resolveHeader["Accept"] = options.Headers["Accept"] - delete(options.Headers, "Accept") - } - - if options.Hosts == nil { - opts := []RegistryOpt{} - if options.Host != nil { - opts = append(opts, WithHostTranslator(options.Host)) - } - - if options.Authorizer == nil { - options.Authorizer = NewDockerAuthorizer( - WithAuthClient(options.Client), - WithAuthHeader(options.Headers), - WithAuthCreds(options.Credentials)) - } - opts = append(opts, WithAuthorizer(options.Authorizer)) - - if options.Client != nil { - opts = append(opts, WithClient(options.Client)) - } - if options.PlainHTTP { - opts = append(opts, WithPlainHTTP(MatchAllHosts)) - } else { - opts = append(opts, WithPlainHTTP(MatchLocalhost)) - } - options.Hosts = ConfigureDefaultRegistries(opts...) - } - return &dockerResolver{ - hosts: options.Hosts, - header: options.Headers, - resolveHeader: resolveHeader, - tracker: options.Tracker, - } -} - -func getManifestMediaType(resp *http.Response) string { - // Strip encoding data (manifests should always be ascii JSON) - contentType := resp.Header.Get("Content-Type") - if sp := strings.IndexByte(contentType, ';'); sp != -1 { - contentType = contentType[0:sp] - } - - // As of Apr 30 2019 the registry.access.redhat.com registry does not specify - // the content type of any data but uses schema1 manifests. - if contentType == "text/plain" { - contentType = images.MediaTypeDockerSchema1Manifest - } - return contentType -} - -type countingReader struct { - reader io.Reader - bytesRead int64 -} - -func (r *countingReader) Read(p []byte) (int, error) { - n, err := r.reader.Read(p) - r.bytesRead += int64(n) - return n, err -} - -var _ resolve.Resolver = &dockerResolver{} - -func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocispec.Descriptor, error) { - base, err := r.resolveDockerBase(ref) - if err != nil { - return "", ocispec.Descriptor{}, err - } - refspec := base.refspec - if refspec.Object == "" { - return "", ocispec.Descriptor{}, reference.ErrObjectRequired - } - - var ( - firstErr error - paths [][]string - dgst = refspec.Digest() - caps = HostCapabilityPull - ) - - if dgst != "" { - if err := dgst.Validate(); err != nil { - // need to fail here, since we can't actually resolve the invalid - // digest. - return "", ocispec.Descriptor{}, err - } - - // turns out, we have a valid digest, make a url. - paths = append(paths, []string{"manifests", dgst.String()}) - - // fallback to blobs on not found. - paths = append(paths, []string{"blobs", dgst.String()}) - } else { - // Add - paths = append(paths, []string{"manifests", refspec.Object}) - caps |= HostCapabilityResolve - } - - hosts := base.filterHosts(caps) - if len(hosts) == 0 { - return "", ocispec.Descriptor{}, errors.Wrap(errdefs.ErrNotFound, "no resolve hosts") - } - - ctx, err = ContextWithRepositoryScope(ctx, refspec, false) - if err != nil { - return "", ocispec.Descriptor{}, err - } - - for _, u := range paths { - for _, host := range hosts { - ctxWithLogger := log.WithLogger(ctx, log.G(ctx).WithField("host", host.Host)) - - req := base.request(host, http.MethodHead, u...) - if err := req.addNamespace(base.refspec.Hostname()); err != nil { - return "", ocispec.Descriptor{}, err - } - - for key, value := range r.resolveHeader { - req.header[key] = append(req.header[key], value...) - } - - log.G(ctxWithLogger).Debug("resolving") - resp, err := req.doWithRetries(ctxWithLogger, nil) - if err != nil { - if errors.Is(err, ErrInvalidAuthorization) { - err = errors.Wrapf(err, "pull access denied, repository does not exist or may require authorization") - } else { - err = accessio.RetriableError(err) - } - // Store the error for referencing later - if firstErr == nil { - firstErr = err - } - log.G(ctxWithLogger).WithError(err).Info("trying next host") - continue // try another host - } - resp.Body.Close() // don't care about body contents. - - if resp.StatusCode > 299 { - if resp.StatusCode == http.StatusNotFound { - // log.G(ctxWithLogger).Info("trying next host - response was http.StatusNotFound") - continue - } - if resp.StatusCode > 399 { - // Set firstErr when encountering the first non-404 status code. - if firstErr == nil { - firstErr = errors.Errorf("pulling from host %s failed with status code %v: %v", host.Host, u, resp.Status) - } - continue // try another host - } - return "", ocispec.Descriptor{}, errors.Errorf("pulling from host %s failed with unexpected status code %v: %v", host.Host, u, resp.Status) - } - size := resp.ContentLength - contentType := getManifestMediaType(resp) - - // if no digest was provided, then only a resolve - // trusted registry was contacted, in this case use - // the digest header (or content from GET) - if dgst == "" { - // this is the only point at which we trust the registry. we use the - // content headers to assemble a descriptor for the name. when this becomes - // more robust, we mostly get this information from a secure trust store. - dgstHeader := digest.Digest(resp.Header.Get("Docker-Content-Digest")) - - if dgstHeader != "" && size != -1 { - if err := dgstHeader.Validate(); err != nil { - return "", ocispec.Descriptor{}, errors.Wrapf(err, "%q in header not a valid digest", dgstHeader) - } - dgst = dgstHeader - } - } - if dgst == "" || size == -1 { - log.G(ctxWithLogger).Debug("no Docker-Content-Digest header, fetching manifest instead") - - req = base.request(host, http.MethodGet, u...) - if err := req.addNamespace(base.refspec.Hostname()); err != nil { - return "", ocispec.Descriptor{}, err - } - - for key, value := range r.resolveHeader { - req.header[key] = append(req.header[key], value...) - } - - resp, err := req.doWithRetries(ctxWithLogger, nil) - if err != nil { - return "", ocispec.Descriptor{}, accessio.RetriableError(err) - } - defer resp.Body.Close() - - bodyReader := countingReader{reader: resp.Body} - - contentType = getManifestMediaType(resp) - if dgst == "" { - if contentType == images.MediaTypeDockerSchema1Manifest { - b, err := schema1.ReadStripSignature(&bodyReader) - if err != nil { - return "", ocispec.Descriptor{}, accessio.RetriableError(err) - } - - dgst = digest.FromBytes(b) - } else { - dgst, err = digest.FromReader(&bodyReader) - if err != nil { - return "", ocispec.Descriptor{}, accessio.RetriableError(err) - } - } - } else if _, err := io.Copy(io.Discard, &bodyReader); err != nil { - return "", ocispec.Descriptor{}, accessio.RetriableError(err) - } - size = bodyReader.bytesRead - } - // Prevent resolving to excessively large manifests - if size > MaxManifestSize { - if firstErr == nil { - firstErr = errors.Wrapf(errdefs.ErrNotFound, "rejecting %d byte manifest for %s", size, ref) - } - continue - } - - desc := ocispec.Descriptor{ - Digest: dgst, - MediaType: contentType, - Size: size, - } - - log.G(ctxWithLogger).WithField("desc.digest", desc.Digest).Debug("resolved") - return ref, desc, nil - } - } - - // If above loop terminates without return, then there was an error. - // "firstErr" contains the first non-404 error. That is, "firstErr == nil" - // means that either no registries were given or each registry returned 404. - - if firstErr == nil { - firstErr = errors.Wrap(errdefs.ErrNotFound, ref) - } - - return "", ocispec.Descriptor{}, firstErr -} - -func (r *dockerResolver) Fetcher(ctx context.Context, ref string) (resolve.Fetcher, error) { - base, err := r.resolveDockerBase(ref) - if err != nil { - return nil, err - } - - return dockerFetcher{ - dockerBase: base, - }, nil -} - -func (r *dockerResolver) Pusher(ctx context.Context, ref string) (resolve.Pusher, error) { - base, err := r.resolveDockerBase(ref) - if err != nil { - return nil, err - } - - return dockerPusher{ - dockerBase: base, - object: base.refspec.Object, - tracker: r.tracker, - }, nil -} - -func (r *dockerResolver) resolveDockerBase(ref string) (*dockerBase, error) { - refspec, err := reference.Parse(ref) - if err != nil { - return nil, err - } - - return r.base(refspec) -} - -type dockerBase struct { - refspec reference.Spec - repository string - hosts []RegistryHost - header http.Header -} - -func (r *dockerResolver) base(refspec reference.Spec) (*dockerBase, error) { - host := refspec.Hostname() - hosts, err := r.hosts(host) - if err != nil { - return nil, err - } - return &dockerBase{ - refspec: refspec, - repository: strings.TrimPrefix(refspec.Locator, host+"/"), - hosts: hosts, - header: r.header, - }, nil -} - -func (r *dockerBase) filterHosts(caps HostCapabilities) (hosts []RegistryHost) { - for _, host := range r.hosts { - if host.Capabilities.Has(caps) { - hosts = append(hosts, host) - } - } - return -} - -func (r *dockerBase) request(host RegistryHost, method string, ps ...string) *request { - header := r.header.Clone() - if header == nil { - header = http.Header{} - } - - for key, value := range host.Header { - header[key] = append(header[key], value...) - } - parts := append([]string{"/", host.Path, r.repository}, ps...) - p := path.Join(parts...) - // Join strips trailing slash, re-add ending "/" if included - if len(parts) > 0 && strings.HasSuffix(parts[len(parts)-1], "/") { - p += "/" - } - return &request{ - method: method, - path: p, - header: header, - host: host, - } -} - -func (r *request) authorize(ctx context.Context, req *http.Request) error { - // Check if has header for host - if r.host.Authorizer != nil { - if err := r.host.Authorizer.Authorize(ctx, req); err != nil { - return err - } - } - - return nil -} - -func (r *request) addNamespace(ns string) (err error) { - if !r.host.isProxy(ns) { - return nil - } - var q url.Values - // Parse query - if i := strings.IndexByte(r.path, '?'); i > 0 { - r.path = r.path[:i+1] - q, err = url.ParseQuery(r.path[i+1:]) - if err != nil { - return - } - } else { - r.path += "?" - q = url.Values{} - } - q.Add("ns", ns) - - r.path += q.Encode() - - return -} - -type request struct { - method string - path string - header http.Header - host RegistryHost - body func() (io.ReadCloser, error) - size int64 -} - -func (r *request) do(ctx context.Context) (*http.Response, error) { - u := r.host.Scheme + "://" + r.host.Host + r.path - req, err := http.NewRequestWithContext(ctx, r.method, u, nil) - if err != nil { - return nil, err - } - req.Header = http.Header{} // headers need to be copied to avoid concurrent map access - for k, v := range r.header { - req.Header[k] = v - } - if r.body != nil { - body, err := r.body() - if err != nil { - return nil, err - } - req.Body = body - req.GetBody = r.body - if r.size > 0 { - req.ContentLength = r.size - } - defer body.Close() - } - - ctx = log.WithLogger(ctx, log.G(ctx).WithField("url", u)) - log.G(ctx).WithFields(sanitizedRequestFields(req)).Debug("do request") - if err := r.authorize(ctx, req); err != nil { - return nil, errors.Wrap(err, "failed to authorize") - } - - client := &http.Client{} - if r.host.Client != nil { - *client = *r.host.Client - } - if client.CheckRedirect == nil { - client.CheckRedirect = func(req *http.Request, via []*http.Request) error { - if len(via) >= 10 { - return errors.New("stopped after 10 redirects") - } - return errors.Wrap(r.authorize(ctx, req), "failed to authorize redirect") - } - } - - resp, err := ctxhttp.Do(ctx, client, req) - if err != nil { - return nil, errors.Wrap(err, "failed to do request") - } - log.G(ctx).WithFields(responseFields(resp)).Debug("fetch response received") - return resp, nil -} - -func (r *request) doWithRetries(ctx context.Context, responses []*http.Response) (*http.Response, error) { - resp, err := r.do(ctx) - if err != nil { - return nil, err - } - - responses = append(responses, resp) - retry, err := r.retryRequest(ctx, responses) - if err != nil { - resp.Body.Close() - return nil, err - } - if retry { - resp.Body.Close() - return r.doWithRetries(ctx, responses) - } - return resp, err -} - -func (r *request) retryRequest(ctx context.Context, responses []*http.Response) (bool, error) { - if len(responses) > 5 { - return false, nil - } - last := responses[len(responses)-1] - switch last.StatusCode { - case http.StatusUnauthorized: - log.G(ctx).WithField("header", last.Header.Get("WWW-Authenticate")).Debug("Unauthorized") - if r.host.Authorizer != nil { - if err := r.host.Authorizer.AddResponses(ctx, responses); err == nil { - return true, nil - } else if !errdefs.IsNotImplemented(err) { - return false, err - } - } - - return false, nil - case http.StatusMethodNotAllowed: - // Support registries which have not properly implemented the HEAD method for - // manifests endpoint - if r.method == http.MethodHead && strings.Contains(r.path, "/manifests/") { - r.method = http.MethodGet - return true, nil - } - case http.StatusRequestTimeout, http.StatusTooManyRequests: - return true, nil - } - - // TODO: Handle 50x errors accounting for attempt history - return false, nil -} - -func (r *request) String() string { - return r.host.Scheme + "://" + r.host.Host + r.path -} - -func sanitizedRequestFields(req *http.Request) logrus.Fields { - fields := map[string]interface{}{ - "request.method": req.Method, - } - for k, vals := range req.Header { - k = strings.ToLower(k) - if k == "authorization" { - continue - } - for i, v := range vals { - field := "request.header." + k - if i > 0 { - field = fmt.Sprintf("%s.%d", field, i) - } - fields[field] = v - } - } - - return logrus.Fields(fields) -} - -func responseFields(resp *http.Response) logrus.Fields { - fields := map[string]interface{}{ - "response.status": resp.Status, - } - for k, vals := range resp.Header { - k = strings.ToLower(k) - for i, v := range vals { - field := "response.header." + k - if i > 0 { - field = fmt.Sprintf("%s.%d", field, i) - } - fields[field] = v - } - } - - return logrus.Fields(fields) -} diff --git a/pkg/encrypt/encrypt.go b/pkg/encrypt/encrypt.go deleted file mode 100644 index 2bbb22347..000000000 --- a/pkg/encrypt/encrypt.go +++ /dev/null @@ -1,189 +0,0 @@ -package encrypt - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "encoding/pem" - "io" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/utils" -) - -const ( - PEM_ENCRYPTION_KEY = "ENCRYPTION KEY" - PEM_ENCRYPTED_DATA = "ENCRYPTED DATA" -) - -type algorithm byte - -const ( - AES_128 = algorithm(16) - AES_192 = algorithm(24) - AES_256 = algorithm(32) -) - -const ALGO = "algorithm" - -const ( - ALGO_AES_128 = "AES-128" - ALGO_AES_192 = "AES-192" - ALGO_AES_256 = "AES-256" -) - -var algos = map[algorithm]string{ - AES_128: ALGO_AES_128, - AES_192: ALGO_AES_192, - AES_256: ALGO_AES_256, -} - -var name2algo = map[string]algorithm{ - ALGO_AES_128: AES_128, - ALGO_AES_192: AES_192, - ALGO_AES_256: AES_256, -} - -func (a algorithm) String() string { - return algos[a] -} - -func (a algorithm) KeyLength() int { - return int(a) -} - -func (a algorithm) CheckKey(key []byte) error { - if a.KeyLength() != len(key) { - return aes.KeySizeError(len(key)) - } - return nil -} - -func AlgoForKey(key []byte) (algorithm, error) { - for a := range algos { - if len(key) == a.KeyLength() { - return a, nil - } - } - return 0, aes.KeySizeError(len(key)) -} - -func NewKey(t algorithm) ([]byte, error) { - key := make([]byte, t) - if _, err := io.ReadFull(rand.Reader, key); err != nil { - return nil, err - } - return key, nil -} - -func Encrypt(key []byte, data []byte) ([]byte, error) { - block, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - - gcm, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - - nonce := make([]byte, gcm.NonceSize()) - if _, err := io.ReadFull(rand.Reader, nonce); err != nil { - return nil, err - } - - return gcm.Seal(nonce, nonce, data, nil), nil -} - -func Decrypt(key []byte, cipherText []byte) ([]byte, error) { - block, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - gcm, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - - nonce := cipherText[:gcm.NonceSize()] - cipherText = cipherText[gcm.NonceSize():] - return gcm.Open(nil, nonce, cipherText, nil) -} - -func KeyFromPem(data []byte) ([]byte, error) { - for block, rest := pem.Decode(data); block != nil; block, rest = pem.Decode(rest) { - if block.Type == PEM_ENCRYPTION_KEY { - return block.Bytes, nil - } - } - return nil, errors.ErrNotFound("pem block", PEM_ENCRYPTION_KEY) -} - -func KeyToPem(data []byte) []byte { - return pem.EncodeToMemory(&pem.Block{ - Type: PEM_ENCRYPTION_KEY, - Headers: nil, - Bytes: data, - }) -} - -func KeyFromAny(k interface{}) ([]byte, error) { - if data, ok := k.([]byte); ok { - for block, rest := pem.Decode(data); block != nil; block, rest = pem.Decode(rest) { - if block.Type == PEM_ENCRYPTION_KEY { - return block.Bytes, nil - } - } - return data, nil - } - return nil, errors.ErrUnknown(PEM_ENCRYPTION_KEY) -} - -func GetEncyptedData(data []byte) ([]byte, algorithm) { - for block, rest := pem.Decode(data); block != nil; block, rest = pem.Decode(rest) { - if block.Type == PEM_ENCRYPTED_DATA { - algo := AES_256 - if block.Headers != nil { - if name := block.Headers[ALGO]; name != "" { - algo = name2algo[name] - } - } - return block.Bytes, algo - } - } - return nil, 0 -} - -func EncryptedToPem(algo algorithm, data []byte) []byte { - return pem.EncodeToMemory(&pem.Block{ - Type: PEM_ENCRYPTED_DATA, - Headers: map[string]string{ALGO: algo.String()}, - Bytes: data, - }) -} - -func OptionalDecrypt(key []byte, data []byte) ([]byte, error) { - cipherText, algo := GetEncyptedData(data) - if cipherText != nil { - if len(key) != algo.KeyLength() { - return nil, aes.KeySizeError(len(key)) - } - return Decrypt(key, cipherText) - } - return data, nil -} - -func WriteKey(key []byte, path string, fss ...vfs.FileSystem) error { - data := KeyToPem(key) - return vfs.WriteFile(utils.FileSystem(fss...), path, data, 0o100) -} - -func ReadKey(path string, fss ...vfs.FileSystem) ([]byte, error) { - data, err := vfs.ReadFile(utils.FileSystem(fss...), path) - if err != nil { - return nil, err - } - return KeyFromPem(data) -} diff --git a/pkg/env/builder/builder.go b/pkg/env/builder/builder.go deleted file mode 100644 index daa6e4923..000000000 --- a/pkg/env/builder/builder.go +++ /dev/null @@ -1,210 +0,0 @@ -package builder - -import ( - "github.com/mandelsoft/goutils/exception" - "github.com/mandelsoft/vfs/pkg/osfs" - "github.com/modern-go/reflect2" - "github.com/onsi/ginkgo/v2" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/contexts/oci" - "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "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/env" - "github.com/open-component-model/ocm/pkg/utils" -) - -type element interface { - SetBuilder(b *Builder) - Type() string - Close() error - Set() - - Result() interface{} -} - -type State struct{} - -type base struct { - *Builder - result interface{} -} - -func (e *base) SetBuilder(b *Builder) { - e.Builder = b -} - -func (e *base) Result() interface{} { - return e.result -} - -type static struct { - def_modopts ocm.ModificationOptions -} - -type state struct { - *static - ocm_repo ocm.Repository - ocm_comp ocm.ComponentAccess - ocm_vers ocm.ComponentVersionAccess - ocm_rsc *compdesc.ResourceMeta - ocm_src *compdesc.SourceMeta - ocm_meta *compdesc.ElementMeta - ocm_labels *metav1.Labels - ocm_acc *compdesc.AccessSpec - ocm_modopts *ocm.ModificationOptions - - blob *blobaccess.BlobAccess - hint *string - - oci_repo oci.Repository - oci_nsacc oci.NamespaceAccess - oci_artacc oci.ArtifactAccess - oci_cleanuplayers bool - oci_tags *[]string - oci_artfunc func(oci.ArtifactAccess) error - oci_annofunc func(name, value string) - oci_platform *artdesc.Platform -} - -type Builder struct { - *env.Environment - stack []element - state -} - -// New creates a new composition environment -// including an own OCM context and a private -// filesystem, which can be used to compose -// OCM/OCI repositories and their content. -// It can be configured to work with dedicated -// settings, also. -func New(opts ...env.Option) *Builder { - return &Builder{Environment: env.NewEnvironment(append([]env.Option{env.FileSystem(osfs.OsFs, "/"), env.FailHandler(env.ExceptionFailHandler)}, opts...)...), state: state{static: &static{}}} -} - -// NewBuilder creates a new composition environment -// including an own OCM context and a private -// filesystem, which can be used to compose -// OCM/OCI repositories and their content. -// By default, a private environment is created based on -// a ginko fail handling intended to be used for test cases. -// But it can be configured to work as library with dedicated -// settings, also. -func NewBuilder(opts ...env.Option) *Builder { - return &Builder{Environment: env.NewEnvironment(append([]env.Option{env.FailHandler(ginkgo.Fail)}, opts...)...), state: state{static: &static{}}} -} - -var _ accessio.Option = (*Builder)(nil) - -// Build executes the given functions and returns a potential configuration -// error, instead of using the builder's env.FailHandler. -// Additionally, a build can always throw an exception using -// the exception.Throw function. -func (b *Builder) Build(funcs ...func(*Builder)) (err error) { - old := b.GetFailHandler() - defer func() { - b.SetFailHandler(old) - }() - b.SetFailHandler(env.ExceptionFailHandler) - - defer exception.PropagateException(&err) - for _, f := range funcs { - f(b) - } - return nil -} - -func (b *Builder) SetFailhandler(h ...env.FailHandler) *Builder { - b.Environment.SetFailHandler(h...) - return b -} - -// PropagateError can be used in defer to convert an composition error -// into an error return. -func (b *Builder) PropagateError(errp *error, matchers ...exception.Matcher) { - if r := recover(); r != nil { - *errp = exception.FilterException(r, matchers...) - } -} - -func (b *Builder) set() { - b.state = state{static: b.state.static} - - if len(b.stack) > 0 { - b.peek().Set() - } -} - -func (b *Builder) expect(p interface{}, msg string, tests ...func() bool) { - if reflect2.IsNil(p) { - b.fail(msg+" required", 1) - } - for _, f := range tests { - if !f() { - b.fail(msg+" required", 1) - } - } -} - -func (b *Builder) fail(msg string, callerSkip ...int) { - b.Fail(msg, utils.Optional(callerSkip...)+2) -} - -func (b *Builder) failOn(err error, callerSkip ...int) { - b.FailOnErr(err, "", utils.Optional(callerSkip...)+2) -} - -func (b *Builder) peek() element { - if len(b.stack) == 0 { - b.fail("no open frame", 2) - } - return b.stack[len(b.stack)-1] -} - -func (b *Builder) pop() element { - if len(b.stack) == 0 { - b.fail("no open frame", 2) - } - e := b.stack[len(b.stack)-1] - b.stack = b.stack[:len(b.stack)-1] - b.set() - return e -} - -func (b *Builder) push(e element) { - b.stack = append(b.stack, e) - b.set() -} - -func (b *Builder) configure(e element, funcs []func(), skip ...int) interface{} { - e.SetBuilder(b) - b.push(e) - b.Configure(funcs...) - err := b.pop().Close() - if err != nil { - b.fail(err.Error(), utils.Optional(skip...)+1) - } - return e.Result() -} - -func (b *Builder) Configure(funcs ...func()) { - for _, f := range funcs { - if f != nil { - f() - } - } -} - -//////////////////////////////////////////////////////////////////////////////// - -func (b *Builder) Hint(hint string) { - b.expect(b.hint, T_OCMACCESS) - if b.ocm_acc != nil && *b.ocm_acc != nil { - b.fail("access already set") - } - *(b.hint) = hint -} diff --git a/pkg/env/builder/oci_artifactset.go b/pkg/env/builder/oci_artifactset.go deleted file mode 100644 index 4ab3aa029..000000000 --- a/pkg/env/builder/oci_artifactset.go +++ /dev/null @@ -1,20 +0,0 @@ -package builder - -import ( - "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/oci/repositories/artifactset" -) - -const T_OCIARTIFACTSET = "artifact set" - -//////////////////////////////////////////////////////////////////////////////// - -func (b *Builder) ArtifactSet(path string, fmt accessio.FileFormat, f ...func()) { - r, err := artifactset.Open(accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, path, 0o777, fmt, accessio.PathFileSystem(b.FileSystem())) - b.failOn(err) - - b.configure(&ociNamespace{NamespaceAccess: r, kind: T_OCIARTIFACTSET, annofunc: func(name, value string) { - r.Annotate(name, value) - }}, f) -} diff --git a/pkg/env/builder/oci_ctf.go b/pkg/env/builder/oci_ctf.go deleted file mode 100644 index 86cae80b0..000000000 --- a/pkg/env/builder/oci_ctf.go +++ /dev/null @@ -1,15 +0,0 @@ -package builder - -import ( - "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/oci/repositories/ctf" -) - -const T_OCI_CTF = "oci common transport format" - -func (b *Builder) OCICommonTransport(path string, fmt accessio.FileFormat, f ...func()) { - r, err := ctf.Open(b.OCMContext().OCIContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, path, 0o777, accessio.PathFileSystem(b.FileSystem())) - b.failOn(err) - b.configure(&ociRepository{Repository: r, kind: T_OCI_CTF}, f) -} diff --git a/pkg/env/builder/ocm_comparch.go b/pkg/env/builder/ocm_comparch.go deleted file mode 100644 index 956ff7865..000000000 --- a/pkg/env/builder/ocm_comparch.go +++ /dev/null @@ -1,20 +0,0 @@ -package builder - -import ( - "github.com/open-component-model/ocm/pkg/common/accessio" - "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/repositories/comparch" -) - -const T_COMPARCH = "component archive" - -func (b *Builder) ComponentArchive(path string, fmt accessio.FileFormat, name, vers string, f ...func()) { - r, err := comparch.Open(b.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, path, 0o777, accessio.PathFileSystem(b.FileSystem())) - b.failOn(err) - r.SetName(name) - r.SetVersion(vers) - r.GetDescriptor().Provider.Name = metav1.ProviderName("ACME") - - b.configure(&ocmVersion{ComponentVersionAccess: r, kind: T_COMPARCH}, f) -} diff --git a/pkg/env/builder/ocm_ctf.go b/pkg/env/builder/ocm_ctf.go deleted file mode 100644 index dfa52a34b..000000000 --- a/pkg/env/builder/ocm_ctf.go +++ /dev/null @@ -1,15 +0,0 @@ -package builder - -import ( - "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/ocm/repositories/ctf" -) - -const T_OCM_CTF = "ocm common transport format" - -func (b *Builder) OCMCommonTransport(path string, fmt accessio.FileFormat, f ...func()) { - r, err := ctf.Open(b.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, path, 0o777, fmt, accessio.PathFileSystem(b.FileSystem())) - b.failOn(err) - b.configure(&ocmRepository{Repository: r, kind: T_OCM_CTF}, f) -} diff --git a/pkg/env/builder/ocm_repo.go b/pkg/env/builder/ocm_repo.go deleted file mode 100644 index 47f3ca60b..000000000 --- a/pkg/env/builder/ocm_repo.go +++ /dev/null @@ -1,41 +0,0 @@ -package builder - -import ( - ocm "github.com/open-component-model/ocm/pkg/contexts/ocm/context" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/genericocireg" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ocireg" -) - -const T_OCMREPOSITORY = "ocm repository" - -type ocmRepository struct { - base - kind string - cpi.Repository -} - -func (r *ocmRepository) Type() string { - if r.kind != "" { - return r.kind - } - return T_OCMREPOSITORY -} - -func (r *ocmRepository) Set() { - r.Builder.ocm_repo = r.Repository - r.Builder.oci_repo = genericocireg.GetOCIRepository(r.Repository) -} - -func (b *Builder) OCMRepository(spec ocm.RepositorySpec, f ...func()) { - repo, err := b.OCMContext().RepositoryForSpec(spec) - b.failOn(err) - b.configure(&ocmRepository{Repository: repo, kind: T_OCMREPOSITORY}, f) -} - -func (b *Builder) OCIBasedOCMRepository(url string, path string, f ...func()) { - spec := ocireg.NewRepositorySpec(url, &ocireg.ComponentRepositoryMeta{ - SubPath: path, - }) - b.OCMRepository(spec, f...) -} diff --git a/pkg/env/builder/ocm_version_test.go b/pkg/env/builder/ocm_version_test.go deleted file mode 100644 index 40ffa2ca1..000000000 --- a/pkg/env/builder/ocm_version_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package builder_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/contexts/ocm/testhelper" - . "github.com/open-component-model/ocm/pkg/env/builder" - - "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/ocm/accessmethods/localblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/compositionmodeattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - ocmutils "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" -) - -const ( - ARCH = "/tmp/ctf" - ARCH2 = "/tmp/ctf2" - PROVIDER = "open-component-model" - VERSION = "v1" - COMPONENT = "github.com/open-component-model/test" - OUT = "/tmp/res" -) - -var _ = Describe("Transfer handler", func() { - var env *Builder - - BeforeEach(func() { - env = NewBuilder() - compositionmodeattr.Set(env.OCMContext(), true) - env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { - env.Component(COMPONENT, func() { - env.Version(VERSION, func() { - env.Provider(PROVIDER) - TestDataResource(env) - }) - }) - }) - }) - - AfterEach(func() { - env.Cleanup() - }) - - It("add ocm resource", func() { - src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) - cv := Must(src.LookupComponentVersion(COMPONENT, VERSION)) - - Expect(len(cv.GetDescriptor().Resources)).To(Equal(1)) - - r := Must(cv.GetResourceByIndex(0)) - a := Must(r.Access()) - Expect(a.GetType()).To(Equal(localblob.Type)) - - data := Must(ocmutils.GetResourceData(r)) - Expect(string(data)).To(Equal(S_TESTDATA)) - }) -}) diff --git a/pkg/errors/alreadyexists.go b/pkg/errors/alreadyexists.go deleted file mode 100644 index 65aa02268..000000000 --- a/pkg/errors/alreadyexists.go +++ /dev/null @@ -1,27 +0,0 @@ -package errors - -type AlreadyExistsError struct { - errinfo -} - -var formatAlreadyExists = NewDefaultFormatter("", "already exists", "in") - -func ErrAlreadyExists(spec ...string) error { - return &AlreadyExistsError{newErrInfo(formatAlreadyExists, spec...)} -} - -func ErrAlreadyExistsWrap(err error, spec ...string) error { - return &AlreadyExistsError{wrapErrInfo(err, formatAlreadyExists, spec...)} -} - -func IsErrAlreadyExists(err error) bool { - return IsA(err, &AlreadyExistsError{}) -} - -func IsErrAlreadyExistsKind(err error, kind string) bool { - var uerr *NotFoundError - if err == nil || !As(err, &uerr) { - return false - } - return uerr.kind == kind -} diff --git a/pkg/errors/closed.go b/pkg/errors/closed.go deleted file mode 100644 index 0a9d9df21..000000000 --- a/pkg/errors/closed.go +++ /dev/null @@ -1,23 +0,0 @@ -package errors - -type ClosedError struct { - errinfo -} - -var formatClosed = NewDefaultFormatter("is", "closed", "for") - -func ErrClosed(spec ...string) error { - return &ClosedError{newErrInfo(formatClosed, spec...)} -} - -func IsErrClosed(err error) bool { - return IsA(err, &ClosedError{}) -} - -func IsErrClosedKind(err error, kind string) bool { - var uerr *ClosedError - if err == nil || !As(err, &uerr) { - return false - } - return uerr.kind == kind -} diff --git a/pkg/errors/doc.go b/pkg/errors/doc.go deleted file mode 100644 index 3e2b1b248..000000000 --- a/pkg/errors/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Deprectated: use github.com/mandelsoft/goutils/errors -package errors diff --git a/pkg/errors/error.go b/pkg/errors/error.go deleted file mode 100644 index db20051ea..000000000 --- a/pkg/errors/error.go +++ /dev/null @@ -1,236 +0,0 @@ -// //nolint: errorlint // this is the new As method, also handling error lists -package errors - -import ( - "errors" - "fmt" - "reflect" -) - -var ( - New = errors.New - Unwrap = errors.Unwrap -) - -func Newf(msg string, args ...interface{}) error { - return New(fmt.Sprintf(msg, args...)) -} - -func As(err error, target any) bool { - if errors.As(err, target) { - return true - } - - for err != nil { - if list, ok := err.(*ErrorList); ok { - for _, n := range list.errors { - if As(n, target) { - return true - } - } - } - err = Unwrap(err) - } - return false -} - -// Is checks for a concrete error object -// along the error chain. -func Is(err error, target error) bool { - if target == nil || err == nil { - return err == target - } - - if errors.Is(err, target) { - return true - } - - for err != nil { - if list, ok := err.(*ErrorList); ok { - for _, n := range list.errors { - if Is(n, target) { - return true - } - } - } - err = Unwrap(err) - } - return false -} - -// IsA checks for an error of a dedicated type -// along the error chain. -func IsA(err error, target error) bool { - if target == nil { - return err == target - } - if err == nil { - return false - } - typ := reflect.TypeOf(target) - - for err != nil { - if reflect.TypeOf(err).AssignableTo(typ) { - return true - } - if list, ok := err.(*ErrorList); ok { - for _, n := range list.errors { - if IsA(n, target) { - return true - } - } - } - err = Unwrap(err) - } - return false -} - -//////////////////////////////////////////////////////////////////////////////// - -type wrappedError struct { - wrapped error - msg string -} - -// NewEf provides an arror with an optional cause. -func NewEf(cause error, msg string, args ...interface{}) error { - if cause == nil { - return Newf(msg, args...) - } - if len(args) > 0 { - msg = fmt.Sprintf(msg, args...) - } - return &wrappedError{ - wrapped: cause, - msg: msg, - } -} - -func Wrapf(err error, msg string, args ...interface{}) error { - if err == nil || msg == "" { - return err - } - if len(args) > 0 { - msg = fmt.Sprintf(msg, args...) - } - return &wrappedError{ - wrapped: err, - msg: msg, - } -} - -func Wrap(err error, args ...interface{}) error { - if err == nil || len(args) == 0 { - return err - } - msg := fmt.Sprint(args...) - return &wrappedError{ - wrapped: err, - msg: msg, - } -} - -func (e *wrappedError) Error() string { - return fmt.Sprintf("%s: %s", e.msg, e.wrapped) -} - -func (e *wrappedError) Unwrap() error { - return e.wrapped -} - -// var errorType = reflect.TypeOf((*error)(nil)).Elem() - -//////////////////////////////////////////////////////////////////////////////// - -type errinfo struct { - wrapped error - format ErrorFormatter - kind string - elem *string - ctxkind string - ctx string -} - -func wrapErrInfo(err error, fmt ErrorFormatter, spec ...string) errinfo { - e := newErrInfo(fmt, spec...) - e.wrapped = err - return e -} - -func newErrInfo(fmt ErrorFormatter, spec ...string) errinfo { - e := errinfo{ - format: fmt, - } - - if len(spec) > 3 { - e.kind = spec[0] - e.elem = &spec[1] - e.ctxkind = spec[2] - e.ctx = spec[3] - return e - } - if len(spec) > 2 { - e.kind = spec[0] - e.elem = &spec[1] - e.ctx = spec[2] - return e - } - if len(spec) > 1 { - e.kind = spec[0] - e.elem = &spec[1] - return e - } - - if len(spec) > 0 { - e.elem = &spec[0] - } - return e -} - -func (e *errinfo) Is(o error) bool { - if oe, ok := o.(interface{ formatMessage() string }); ok { - return oe.formatMessage() == e.formatMessage() - } - return false -} - -func (e *errinfo) formatMessage() string { - return e.format.Format(e.kind, e.elem, e.ctxkind, e.ctx) -} - -func (e *errinfo) Error() string { - msg := e.formatMessage() - if e.wrapped != nil { - return msg + ": " + e.wrapped.Error() - } - return msg -} - -func (e *errinfo) Unwrap() error { - return e.wrapped -} - -func (e *errinfo) Elem() *string { - return e.elem -} - -func (e *errinfo) Kind() string { - return e.kind -} - -func (e *errinfo) CtxKind() string { - return e.ctxkind -} - -func (e *errinfo) Ctx() string { - return e.ctx -} - -type Kinded interface { - Kind() string - SetKind(string) -} - -func (e *errinfo) SetKind(kind string) { - e.kind = kind -} diff --git a/pkg/errors/error_test.go b/pkg/errors/error_test.go deleted file mode 100644 index f8399022c..000000000 --- a/pkg/errors/error_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package errors_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/mandelsoft/goutils/errors" -) - -var _ = Describe("errors", func() { - Context("ErrReadOnly", func() { - It("identifies kind error", func() { - uerr := errors.ErrReadOnly("KIND", "obj") - - Expect(errors.IsErrReadOnlyKind(uerr, "KIND")).To(BeTrue()) - Expect(errors.IsErrReadOnlyKind(uerr, "other")).To(BeFalse()) - }) - It("message with elem", func() { - uerr := errors.ErrReadOnly("KIND", "obj") - - Expect(uerr.Error()).To(Equal("KIND \"obj\" is readonly")) - }) - It("message without elem", func() { - uerr := errors.ErrReadOnly() - - Expect(uerr.Error()).To(Equal("readonly")) - }) - }) - Context("ErrUnkown", func() { - It("identifies kind error", func() { - uerr := errors.ErrUnknown("KIND", "obj") - - Expect(errors.IsErrUnknownKind(uerr, "KIND")).To(BeTrue()) - Expect(errors.IsErrUnknownKind(uerr, "other")).To(BeFalse()) - }) - It("find error in history", func() { - uerr := errors.ErrUnknown("KIND", "obj") - werr := errors.Wrapf(uerr, "wrapped") - - Expect(errors.IsErrUnknownKind(werr, "KIND")).To(BeTrue()) - Expect(errors.IsErrUnknownKind(werr, "other")).To(BeFalse()) - }) - }) -}) diff --git a/pkg/errors/errprop.go b/pkg/errors/errprop.go deleted file mode 100644 index 8046b2717..000000000 --- a/pkg/errors/errprop.go +++ /dev/null @@ -1,31 +0,0 @@ -package errors - -import ( - "github.com/mandelsoft/logging" -) - -type ErrorFunction func() error - -// PropagateError propagates a deferred error to the named return value -// whose address has to be passed as argument. -func PropagateError(errp *error, f ErrorFunction) { - PropagateErrorf(errp, f, "") -} - -// PropagateErrorf propagates an optional deferred error to the named return value -// whose address has to be passed as argument. -// All errors, including the original one, are wrapped by the given context. -func PropagateErrorf(errp *error, f ErrorFunction, msg string, args ...interface{}) { - if f == nil { - *errp = ErrListf(msg, args...).Add(*errp).Result() - } else { - *errp = ErrListf(msg, args...).Add(*errp, f()).Result() - } -} - -func LogError(log logging.Logger, f ErrorFunction, msg string, keypair ...interface{}) { - err := f() - if err != nil { - log.LogError(err, msg, keypair...) - } -} diff --git a/pkg/errors/errprop_test.go b/pkg/errors/errprop_test.go deleted file mode 100644 index 0809e0b62..000000000 --- a/pkg/errors/errprop_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package errors_test - -import ( - "fmt" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/mandelsoft/goutils/errors" -) - -func errfunc(succeed bool) func() error { - if succeed { - return func() error { return nil } - } - return func() error { return fmt.Errorf("error occurred") } -} - -func testFunc(msg string, err error, succeed bool) (efferr error) { - defer errors.PropagateErrorf(&efferr, errfunc(succeed), msg) - return err -} - -var _ = Describe("finalizer", func() { - Context("without context", func() { - It("succeeds", func() { - Expect(testFunc("", nil, true)).To(Succeed()) - }) - - It("fails ", func() { - err := testFunc("", fmt.Errorf("failed"), true) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("failed")) - }) - - It("succeeds with failing finalizer", func() { - err := testFunc("", nil, false) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("error occurred")) - }) - - It("fails with failing finalizer", func() { - err := testFunc("", fmt.Errorf("failed"), false) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("{failed, error occurred}")) - }) - }) - - Context("with context", func() { - It("succeeds", func() { - Expect(testFunc("context", nil, true)).To(Succeed()) - }) - - It("fails ", func() { - err := testFunc("context", fmt.Errorf("failed"), true) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("context: failed")) - }) - - It("succeeds with failing finalizer", func() { - err := testFunc("context", nil, false) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("context: error occurred")) - }) - - It("fails with failing finalizer", func() { - err := testFunc("context", fmt.Errorf("failed"), false) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("context: {failed, error occurred}")) - }) - }) -}) diff --git a/pkg/errors/format.go b/pkg/errors/format.go deleted file mode 100644 index b6cab0145..000000000 --- a/pkg/errors/format.go +++ /dev/null @@ -1,42 +0,0 @@ -package errors - -type ErrorFormatter interface { - Format(kind string, elem *string, ctxkind string, ctx string) string -} - -type defaultFormatter struct { - verb string - msg string - preposition string -} - -func NewDefaultFormatter(verb, msg, preposition string) ErrorFormatter { - if verb != "" { - verb += " " - } - return &defaultFormatter{ - verb: verb, - msg: msg, - preposition: preposition, - } -} - -func (f *defaultFormatter) Format(kind string, elem *string, ctxkind string, ctx string) string { - if ctx != "" { - if ctxkind != "" { - ctx = ctxkind + " " + ctx - } - ctx = " " + f.preposition + " " + ctx - } - elems := "" - if elem != nil { - elems = "\"" + *elem + "\" " - } - if kind != "" { - kind += " " - } - if kind == "" && elems == "" { - return f.msg + ctx - } - return kind + elems + f.verb + f.msg + ctx -} diff --git a/pkg/errors/invalid.go b/pkg/errors/invalid.go deleted file mode 100644 index 065fa6536..000000000 --- a/pkg/errors/invalid.go +++ /dev/null @@ -1,36 +0,0 @@ -package errors - -import ( - "fmt" -) - -type InvalidError struct { - errinfo -} - -var formatInvalid = NewDefaultFormatter("is", "invalid", "for") - -func ErrInvalid(spec ...string) error { - return &InvalidError{newErrInfo(formatInvalid, spec...)} -} - -// ErrInvalidType reports an invalid or unexpected Go type for a dedicated purpose. -func ErrInvalidType(kind string, v interface{}) error { - return &InvalidError{newErrInfo(formatUnknown, kind, fmt.Sprintf("%T", v))} -} - -func ErrInvalidWrap(err error, spec ...string) error { - return &InvalidError{wrapErrInfo(err, formatInvalid, spec...)} -} - -func IsErrInvalid(err error) bool { - return IsA(err, &InvalidError{}) -} - -func IsErrInvalidKind(err error, kind string) bool { - var uerr *InvalidError - if err == nil || !As(err, &uerr) { - return false - } - return uerr.kind == kind -} diff --git a/pkg/errors/list.go b/pkg/errors/list.go deleted file mode 100644 index 6b589684e..000000000 --- a/pkg/errors/list.go +++ /dev/null @@ -1,98 +0,0 @@ -package errors - -import ( - "fmt" - "io" -) - -// Join combines any number of errors to a single error. -// If no or only nil errors are given nil is returned. -// If only one effective error is provided, this error is returned. -func Join(errs ...error) error { - return (&ErrorList{}).Add(errs...).Result() -} - -// ErrorList is an error type with erros in it. -type ErrorList struct { //nolint: errname // Intentional naming. - msg string - errors []error -} - -func (l *ErrorList) Unwrap() []error { - return l.errors -} - -func (l *ErrorList) Error() string { - msg := "" - if l.msg != "" { - msg = fmt.Sprintf("%s: ", l.msg) - } - - if len(l.errors) == 1 { - return fmt.Sprintf("%s%s", msg, l.errors[0].Error()) - } - sep := "{" - for _, e := range l.errors { - if e != nil { - msg = fmt.Sprintf("%s%s%s", msg, sep, e) - sep = ", " - } - } - return msg + "}" -} - -func (l *ErrorList) Add(errs ...error) *ErrorList { - for _, e := range errs { - if e != nil { - l.errors = append(l.errors, e) - } - } - return l -} - -func (l *ErrorList) Addf(writer io.Writer, err error, msg string, args ...interface{}) error { - if err != nil { - if msg != "" { - err = Wrapf(err, msg, args...) - } - l.errors = append(l.errors, err) - if writer != nil { - fmt.Fprintf(writer, "Error: %s\n", err) - } - } - return err -} - -func (l *ErrorList) Len() int { - return len(l.errors) -} - -func (l *ErrorList) Entries() []error { - return l.errors -} - -func (l *ErrorList) Result() error { - if l == nil || len(l.errors) == 0 { - return nil - } - if l.msg == "" && len(l.errors) == 1 { - return l.errors[0] - } - return l -} - -func (l *ErrorList) Clear() { - l.errors = nil -} - -func ErrListf(msg string, args ...interface{}) *ErrorList { - return &ErrorList{ - msg: fmt.Sprintf(msg, args...), - } -} - -func ErrList(args ...interface{}) *ErrorList { - return &ErrorList{ - msg: fmt.Sprint(args...), - } -} diff --git a/pkg/errors/list_test.go b/pkg/errors/list_test.go deleted file mode 100644 index 0a0957d1c..000000000 --- a/pkg/errors/list_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package errors - -import ( - "fmt" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("error list", func() { - Context("without message", func() { - It("handles no error", func() { - Expect(ErrListf("").Result()).To(Succeed()) - }) - - It("handles one error", func() { - err := ErrListf("").Add(fmt.Errorf("e1")).Result() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("e1")) - }) - - It("handles two error2", func() { - err := ErrListf("").Add(fmt.Errorf("e1"), fmt.Errorf("e2")).Result() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("{e1, e2}")) - }) - }) - - Context("with message", func() { - It("handles no error", func() { - Expect(ErrListf("msg").Result()).To(Succeed()) - }) - - It("handles one error", func() { - err := ErrListf("msg").Add(fmt.Errorf("e1")).Result() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("msg: e1")) - }) - - It("handles two error2", func() { - err := ErrListf("msg").Add(fmt.Errorf("e1"), fmt.Errorf("e2")).Result() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("msg: {e1, e2}")) - }) - }) - - Context("is a", func() { - It("handles single nested", func() { - err := ErrListf("msg").Add(ErrInvalid()).Result() - Expect(err).To(HaveOccurred()) - Expect(IsA(err, ErrInvalid())).To(BeTrue()) - }) - - It("handles nested single nested", func() { - err := ErrListf("msg").Add(ErrInvalid()).Result() - Expect(err).To(HaveOccurred()) - Expect(IsA(err, ErrInvalid())).To(BeTrue()) - }) - - It("gets single nested", func() { - err := Wrapf(ErrListf("msg").Add(ErrInvalid("test")).Result(), "top") - exp := &InvalidError{} - Expect(err).To(HaveOccurred()) - Expect(As(err, &exp)).To(BeTrue()) - Expect(exp.Error()).To(Equal("\"test\" is invalid")) - }) - }) -}) diff --git a/pkg/errors/nomatch.go b/pkg/errors/nomatch.go deleted file mode 100644 index 0e16a5016..000000000 --- a/pkg/errors/nomatch.go +++ /dev/null @@ -1,27 +0,0 @@ -package errors - -type NoMatchError struct { - errinfo -} - -var formatNoMatch = NewDefaultFormatter("has", "no match", "in") - -func ErrNoMatch(spec ...string) error { - return &InvalidError{newErrInfo(formatNoMatch, spec...)} -} - -func ErrNoMatchWrap(err error, spec ...string) error { - return &InvalidError{wrapErrInfo(err, formatNoMatch, spec...)} -} - -func IsErrNoMatch(err error) bool { - return IsA(err, &InvalidError{}) -} - -func IsErrNoMatchKind(err error, kind string) bool { - var uerr *InvalidError - if err == nil || !As(err, &uerr) { - return false - } - return uerr.kind == kind -} diff --git a/pkg/errors/notfound.go b/pkg/errors/notfound.go deleted file mode 100644 index 881fd6274..000000000 --- a/pkg/errors/notfound.go +++ /dev/null @@ -1,35 +0,0 @@ -package errors - -type NotFoundError struct { - errinfo -} - -var formatNotFound = NewDefaultFormatter("", "not found", "in") - -func ErrNotFound(spec ...string) error { - return &NotFoundError{newErrInfo(formatNotFound, spec...)} -} - -func ErrNotFoundWrap(err error, spec ...string) error { - return &NotFoundError{wrapErrInfo(err, formatNotFound, spec...)} -} - -func IsErrNotFound(err error) bool { - return IsA(err, &NotFoundError{}) -} - -func IsErrNotFoundKind(err error, kind string) bool { - var uerr *NotFoundError - if err == nil || !As(err, &uerr) { - return false - } - return uerr.kind == kind -} - -func IsErrNotFoundElem(err error, kind, elem string) bool { - var uerr *NotFoundError - if err == nil || !As(err, &uerr) { - return false - } - return uerr.kind == kind && uerr.elem != nil && *uerr.elem == elem -} diff --git a/pkg/errors/notimpl.go b/pkg/errors/notimpl.go deleted file mode 100644 index a1693f2c2..000000000 --- a/pkg/errors/notimpl.go +++ /dev/null @@ -1,23 +0,0 @@ -package errors - -type NotImplementedError struct { - errinfo -} - -var formatNotImplemented = NewDefaultFormatter("", "not implemented", "by") - -func ErrNotImplemented(spec ...string) error { - return &NotImplementedError{newErrInfo(formatNotImplemented, spec...)} -} - -func IsErrNotImplemented(err error) bool { - return IsA(err, &NotImplementedError{}) -} - -func IsErrNotImplementedKind(err error, kind string) bool { - var uerr *NotImplementedError - if err == nil || !As(err, &uerr) { - return false - } - return uerr.kind == kind -} diff --git a/pkg/errors/notsupported.go b/pkg/errors/notsupported.go deleted file mode 100644 index 2c2e8402f..000000000 --- a/pkg/errors/notsupported.go +++ /dev/null @@ -1,23 +0,0 @@ -package errors - -type NotSupportedError struct { - errinfo -} - -var formatNotSupported = NewDefaultFormatter("", "not supported", "by") - -func ErrNotSupported(spec ...string) error { - return &NotSupportedError{newErrInfo(formatNotSupported, spec...)} -} - -func IsErrNotSupported(err error) bool { - return IsA(err, &NotSupportedError{}) -} - -func IsErrNotSupportedKind(err error, kind string) bool { - var uerr *NotSupportedError - if err == nil || !As(err, &uerr) { - return false - } - return uerr.kind == kind -} diff --git a/pkg/errors/readonly.go b/pkg/errors/readonly.go deleted file mode 100644 index b3ed211e6..000000000 --- a/pkg/errors/readonly.go +++ /dev/null @@ -1,23 +0,0 @@ -package errors - -type ReadOnlyError struct { - errinfo -} - -var formatReadOnly = NewDefaultFormatter("is", "readonly", "in") - -func ErrReadOnly(spec ...string) error { - return &ReadOnlyError{newErrInfo(formatReadOnly, spec...)} -} - -func IsErrReadOnly(err error) bool { - return IsA(err, &ReadOnlyError{}) -} - -func IsErrReadOnlyKind(err error, kind string) bool { - var uerr *ReadOnlyError - if err == nil || !As(err, &uerr) { - return false - } - return uerr.kind == kind -} diff --git a/pkg/errors/recursion.go b/pkg/errors/recursion.go deleted file mode 100644 index 35e5cb095..000000000 --- a/pkg/errors/recursion.go +++ /dev/null @@ -1,78 +0,0 @@ -package errors - -import ( - "fmt" - "reflect" -) - -type RecursionError struct { - wrapped error - kind string - elem interface{} - hist []interface{} -} - -// ErrRecusion describes a resursion errors caused by a dedicated element with an element history. -func ErrRecusion(kind string, elem interface{}, hist interface{}) error { - return &RecursionError{nil, kind, elem, ToInterfaceSlice(hist)} -} - -func ErrRecusionWrap(err error, kind string, elem interface{}, hist interface{}) error { - return &RecursionError{err, kind, elem, ToInterfaceSlice(hist)} -} - -func (e *RecursionError) Error() string { - msg := fmt.Sprintf("%s recursion: use of %v", e.kind, e.elem) - if len(e.hist) > 0 { - s := "" - sep := "" - for _, h := range e.hist { - s = fmt.Sprintf("%s%s%v", s, sep, h) - sep = "->" - } - msg = fmt.Sprintf("%s for %s", msg, s) - } - if e.wrapped != nil { - return msg + ": " + e.wrapped.Error() - } - return msg -} - -func (e *RecursionError) Unwrap() error { - return e.wrapped -} - -func (e *RecursionError) Elem() interface{} { - return e.elem -} - -func (e *RecursionError) Kind() string { - return e.kind -} - -func IsErrRecusion(err error) bool { - return IsA(err, &RecursionError{}) -} - -func IsErrRecursionKind(err error, kind string) bool { - var uerr *RecursionError - if err == nil || !As(err, &uerr) { - return false - } - return uerr.kind == kind -} - -func ToInterfaceSlice(list interface{}) []interface{} { - if list == nil { - return nil - } - v := reflect.ValueOf(list) - if v.Kind() != reflect.Array && v.Kind() != reflect.Slice { - panic("no array or slice") - } - r := make([]interface{}, v.Len()) - for i := 0; i < v.Len(); i++ { - r[i] = v.Index(i).Interface() - } - return r -} diff --git a/pkg/errors/required.go b/pkg/errors/required.go deleted file mode 100644 index f3c8e62c9..000000000 --- a/pkg/errors/required.go +++ /dev/null @@ -1,35 +0,0 @@ -package errors - -type RequiredError struct { - errinfo -} - -var formatRequired = NewDefaultFormatter("", "required", "for") - -func ErrRequired(spec ...string) error { - return &RequiredError{newErrInfo(formatRequired, spec...)} -} - -func ErrRequiredWrap(err error, spec ...string) error { - return &RequiredError{wrapErrInfo(err, formatRequired, spec...)} -} - -func IsErrNRequired(err error) bool { - return IsA(err, &RequiredError{}) -} - -func IsErrRequiredKind(err error, kind string) bool { - var uerr *RequiredError - if err == nil || !As(err, &uerr) { - return false - } - return uerr.kind == kind -} - -func IsErrRequiredElem(err error, kind, elem string) bool { - var uerr *RequiredError - if err == nil || !As(err, &uerr) { - return false - } - return uerr.kind == kind && uerr.elem != nil && *uerr.elem == elem -} diff --git a/pkg/errors/stillinuse.go b/pkg/errors/stillinuse.go deleted file mode 100644 index 589b09802..000000000 --- a/pkg/errors/stillinuse.go +++ /dev/null @@ -1,27 +0,0 @@ -package errors - -type StillInUseError struct { - errinfo -} - -var formatStillInUseError = NewDefaultFormatter("is", "still in use", "for") - -func ErrStillInUse(spec ...string) error { - return &StillInUseError{newErrInfo(formatStillInUseError, spec...)} -} - -func ErrStillInUseWrap(err error, spec ...string) error { - return &StillInUseError{wrapErrInfo(err, formatStillInUseError, spec...)} -} - -func IsErrStillInUse(err error) bool { - return IsA(err, &StillInUseError{}) -} - -func IsErrStillInUseKind(err error, kind string) bool { - var uerr *StillInUseError - if err == nil || !As(err, &uerr) { - return false - } - return uerr.kind == kind -} diff --git a/pkg/errors/suite_test.go b/pkg/errors/suite_test.go deleted file mode 100644 index 981f6d448..000000000 --- a/pkg/errors/suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package errors_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestConfig(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "oci Test Suite") -} diff --git a/pkg/errors/unknown.go b/pkg/errors/unknown.go deleted file mode 100644 index 324d997ef..000000000 --- a/pkg/errors/unknown.go +++ /dev/null @@ -1,23 +0,0 @@ -package errors - -type UnknownError struct { - errinfo -} - -var formatUnknown = NewDefaultFormatter("is", "unknown", "for") - -func ErrUnknown(spec ...string) error { - return &UnknownError{newErrInfo(formatUnknown, spec...)} -} - -func IsErrUnknown(err error) bool { - return IsA(err, &UnknownError{}) -} - -func IsErrUnknownKind(err error, kind string) bool { - var uerr *UnknownError - if err == nil || !As(err, &uerr) { - return false - } - return uerr.kind == kind -} diff --git a/pkg/exception/doc.go b/pkg/exception/doc.go deleted file mode 100644 index 8f4d87c31..000000000 --- a/pkg/exception/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Deprecated: use github.com/mandelsoft/goutils/exception -package exception diff --git a/pkg/exception/exception.go b/pkg/exception/exception.go deleted file mode 100644 index 92b911288..000000000 --- a/pkg/exception/exception.go +++ /dev/null @@ -1,350 +0,0 @@ -// Package exception provides a simple exception mechanism -// to reduce boilerplate for trivial error forwarding in -// a function. -// Example: -// -// func f0() error { -// return nil -// } -// -// func f1() (int, error) { -// return 1, nil -// } -// -// func MyFunc() (err error) { -// defer PropagateException(&err) -// -// Mustf(f0(),"f0 failed") -// i:=Must1f(R1(f1()), "f1 failed") -// fmt.Printf("got %d\n", i) -// return nil -// } -package exception - -import ( - "github.com/mandelsoft/goutils/errors" -) - -type Matcher func(err error) bool - -// PropagateException catches an exception provided by variations of -// MustX functions and forwards the error to its argument. -// It must be called by defer. -// Cannot reuse PropagateExceptionf, because recover MUST be called -// at TOP level defer function to recover the panic. -func PropagateException(errp *error, matchers ...Matcher) { - if r := recover(); r != nil { - *errp = FilterException(r, matchers...) - } -} - -// FilterException can be used in a own defer function -// to handle exceptions. -// In Go it is not possible to provide the complete catch in -// a function, because recover works on top-level functions, only. -// Therefore. the recover call has to be placed directly in the -// deferred wrapper function, which can then use this function to catch -// an exception and convert it to an error code. -func FilterException(r interface{}, matchers ...Matcher) error { - if e, ok := r.(*exception); ok && match(e.err, matchers...) { - return e.err - } else { - panic(r) - } -} - -// CatchError calls the given function with the error of -// a catched exception, if it is deferred. -func CatchError(f func(err error), matchers ...Matcher) { - if r := recover(); r != nil { - err := FilterException(r, matchers...) - if err != nil { - f(err) - } - } -} - -func match(err error, matchers ...Matcher) bool { - if len(matchers) == 0 { - return true - } - for _, m := range matchers { - if m(err) { - return true - } - } - return false -} - -var All = func(_ error) bool { - return true -} - -var None = func(_ error) bool { - return false -} - -func ByPrototypes(protos ...error) Matcher { - return func(err error) bool { - if len(protos) == 0 { - return true - } - for _, p := range protos { - if errors.IsA(err, p) { - return true - } - } - return false - } -} - -func Or(matchers ...Matcher) Matcher { - return func(err error) bool { - for _, m := range matchers { - if m(err) { - return true - } - } - return false - } -} - -func And(matchers ...Matcher) Matcher { - return func(err error) bool { - for _, m := range matchers { - if !m(err) { - return false - } - } - return true - } -} - -// Exception provie the error object from an exception object. -func Exception(r interface{}) error { - if e, ok := r.(*exception); ok { - return e.err - } - return nil -} - -func Catch(funcs ...func()) (err error) { - PropagateException(&err) - for _, f := range funcs { - f() - } - return nil -} - -// PropagateExceptionf catches an exception provided by variations of -// MustX functions and forwards the error to its argument. -// It must be called by defer. -// If an error context is given (msg!=""), it is added to the propagated error. -func PropagateExceptionf(errp *error, msg string, args ...interface{}) { - if r := recover(); r != nil { - if e, ok := r.(*exception); ok { - if msg != "" { - *errp = errors.Wrapf(e.err, msg, args...) - } else { - *errp = e.err - } - } else { - panic(r) - } - } -} - -func PropagateMatchedExceptionf(errp *error, m Matcher, msg string, args ...interface{}) { - if r := recover(); r != nil { - if e, ok := r.(*exception); ok && (m == nil || m(e.err)) { - if msg != "" { - *errp = errors.Wrapf(e.err, msg, args...) - } else { - *errp = e.err - } - } else { - panic(r) - } - } -} - -// ForwardExceptionf add an error context to a forwarded exception. -// Usage: defer ForwardExceptionf("error context"). -func ForwardExceptionf(msg string, args ...interface{}) { - if r := recover(); r != nil { - if e, ok := r.(*exception); ok { - if msg != "" { - e.err = errors.Wrapf(e.err, msg, args...) - } - panic(e) - } else { - panic(r) - } - } -} - -type exception struct { - err error -} - -func (e *exception) Unwrap() error { - return e.err -} - -func (e *exception) Error() string { - return e.err.Error() -} - -// Throw throws an exception if err !=nil. -func Throw(err error) { - if err == nil { - return - } - panic(&exception{err}) -} - -// Throwf throws an exception if err !=nil. -// A given error context is given, it wraps the -// error by the context. -func Throwf(err error, msg string, args ...interface{}) { - if err == nil { - return - } - if msg == "" { - panic(&exception{err}) - } - panic(&exception{errors.Wrapf(err, msg, args...)}) -} - -// Must converts an error (e.g. provided by a nested function call) into an -// exception. It provides no regular result. -func Must(err error) { - Throw(err) -} - -// Must converts an error (e.g. provided by a nested function call) into an -// exception. It provides no regular result. -// The given error context is used to wrap the error. -func Mustf(err error, msg string, args ...interface{}) { - Throwf(err, msg, args...) -} - -//////////////////////////////////////////////////////////////////////////////// - -type result1[A any] struct { - r1 A - err error -} - -// R1 bundles the result of an error function with one additional -// argument to be passed to Must1f. -func R1[A any](r1 A, err error) result1[A] { return result1[A]{r1, err} } - -// Must1 converts an error into an exception. It provides one regular -// result. -// -// Usage: Must1(ErrorFunctionWithOneRegularResult()). -func Must1[A any](r1 A, err error) A { - Throw(err) - return r1 -} - -// Must1f converts an error into an exception. It provides one regular -// result, which has to be provided by method R1. -// Optionally an error context can be given. -// -// Usage: Must1f(R1(ErrorFunctionWithOneRegularResult()), "context"). -// -// The intermediate function R1 is required , because GO does not -// allow to compose arguments provided by a function with multiple -// return values with additional arguments. -func Must1f[A any](r result1[A], msg string, args ...interface{}) A { - Throwf(r.err, msg, args...) - return r.r1 -} - -//////////////////////////////////////////////////////////////////////////////// - -type result2[A, B any] struct { - r1 A - r2 B - err error -} - -// R2 bundles the result of an error function with two additional -// arguments to be passed to Must1. -func R2[A, B any](r1 A, r2 B, err error) result2[A, B] { return result2[A, B]{r1, r2, err} } - -// Must2 converts an error into an exception. It provides two regular -// results. -func Must2[A, B any](r1 A, r2 B, err error) (A, B) { - Throw(err) - return r1, r2 -} - -// Must2f like Must1f, but for two regular -// results, which has to be provided by method R2. -// The error is wrapped by the given error context. -func Must2f[A, B any](r result2[A, B], msg string, args ...interface{}) (A, B) { - Throwf(r.err, msg, args...) - return r.r1, r.r2 -} - -//////////////////////////////////////////////////////////////////////////////// - -type result3[A, B, C any] struct { - r1 A - r2 B - r3 C - err error -} - -// R3 bundles the result of an error function with three additional -// arguments to be passed to Must3. -func R3[A, B, C any](r1 A, r2 B, r3 C, err error) result3[A, B, C] { - return result3[A, B, C]{r1, r2, r3, err} -} - -// Must3 converts an error into an exception. It provides three regular -// results. -func Must3[A, B, C any](r1 A, r2 B, r3 C, err error) (A, B, C) { - Throw(err) - return r1, r2, r3 -} - -// Must3f like Must1f, but for three regular -// results, which has to be provided by method R3. -func Must3f[A, B, C any](r result3[A, B, C], msg string, args ...interface{}) (A, B, C) { - Throwf(r.err, msg, args...) - return r.r1, r.r2, r.r3 -} - -//////////////////////////////////////////////////////////////////////////////// - -type result4[A, B, C, D any] struct { - r1 A - r2 B - r3 C - r4 D - err error -} - -// R4 bundles the result of an error function with four additional -// arguments to be passed to Must4. -func R4[A, B, C, D any](r1 A, r2 B, r3 C, r4 D, err error) result4[A, B, C, D] { - return result4[A, B, C, D]{r1, r2, r3, r4, err} -} - -// Must4 converts an error into an exception. It provides four regular -// results. -func Must4[A, B, C, D any](r1 A, r2 B, r3 C, r4 D, err error) (A, B, C, D) { - Throw(err) - return r1, r2, r3, r4 -} - -// Must4f like Must1f, but for four regular -// results, which has to be provided by method R4. -func Must4f[A, B, C, D any](r result4[A, B, C, D], msg string, args ...interface{}) (A, B, C, D) { - Throwf(r.err, msg, args...) - return r.r1, r.r2, r.r3, r.r4 -} diff --git a/pkg/exception/exception_test.go b/pkg/exception/exception_test.go deleted file mode 100644 index c8b291607..000000000 --- a/pkg/exception/exception_test.go +++ /dev/null @@ -1,115 +0,0 @@ -package exception_test - -import ( - "fmt" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/exception" -) - -var dump = fmt.Errorf("dump") - -func callee(e error) (int, error) { - if e == dump { - a := 0 - _ = 1 / a - } - return 0, e -} - -func _caller(e error, args ...interface{}) { - if len(args) == 0 { - exception.Must1(callee(e)) - } else { - exception.Must1f(exception.R1(callee(e)), args[0].(string), args[1:]...) - } -} - -func caller(e error, args ...interface{}) (err error) { - defer exception.PropagateException(&err) - _caller(e, args...) - return nil -} - -var _ = Describe("exceptions", func() { - It("succeeds", func() { - Expect(caller(nil)).To(BeNil()) - }) - - It("propagates", func() { - err := fmt.Errorf("test error") - Expect(caller(err)).To(Equal(err)) - }) - - It("dumps", func() { - defer func() { - r := recover() - Expect(r).NotTo(BeNil()) - Expect(fmt.Sprintf("%s", r)).To(Equal("runtime error: integer divide by zero")) - }() - caller(dump) - }) - - It("propagates with context", func() { - err := fmt.Errorf("test error") - - prop := caller(err, "test") - Expect(prop).NotTo(BeNil()) - Expect(errors.Unwrap(prop)).To(Equal(err)) - Expect(prop.Error()).To(Equal("test: test error")) - }) - - It("propagates with outer context", func() { - caller := func(e error, args ...interface{}) (err error) { - defer exception.PropagateExceptionf(&err, "outer") - _caller(e, args...) - return nil - } - - err := fmt.Errorf("test error") - - prop := caller(err) - Expect(prop).NotTo(BeNil()) - Expect(errors.Unwrap(prop)).To(Equal(err)) - Expect(prop.Error()).To(Equal("outer: test error")) - - prop = caller(err, "test") - Expect(prop).NotTo(BeNil()) - Expect(errors.Unwrap(errors.Unwrap(prop))).To(Equal(err)) - Expect(prop.Error()).To(Equal("outer: test: test error")) - }) - - Context("with matchers", func() { - caller := func(e error, args ...interface{}) (err error) { - defer exception.PropagateException(&err, exception.ByPrototypes(MyException{})) - _caller(e, args...) - return nil - } - - It("catches matched exception", func() { - err := caller(MyException{}) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("MyException")) - }) - - It("passes unmatched exception", func() { - defer func() { - r := recover() - Expect(r).NotTo(BeNil()) - Expect(fmt.Sprintf("%s", r)).To(Equal("test")) - }() - err := caller(fmt.Errorf("test")) - Expect(err).To(Succeed()) - }) - }) -}) - -type MyException struct{} - -func (_ MyException) Error() string { - return "MyException" -} diff --git a/pkg/exception/suite_test.go b/pkg/exception/suite_test.go deleted file mode 100644 index 32398a975..000000000 --- a/pkg/exception/suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package exception_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestConfig(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "oci Test Suite") -} diff --git a/pkg/finalizer/closer.go b/pkg/finalizer/closer.go deleted file mode 100644 index bab54cfdf..000000000 --- a/pkg/finalizer/closer.go +++ /dev/null @@ -1,41 +0,0 @@ -package finalizer - -import ( - "io" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/common" -) - -type readcloser = io.ReadCloser - -type finalizingCloser struct { - readcloser - msg []string - finalizer *Finalizer -} - -var _ io.ReadCloser = (*finalizingCloser)(nil) - -func addToCloser(reader io.ReadCloser, f *Finalizer, msg ...string) io.ReadCloser { - return &finalizingCloser{ - readcloser: reader, - msg: msg, - finalizer: f, - } -} - -func (c *finalizingCloser) Close() error { - var list *errors.ErrorList - if len(c.msg) == 0 { - list = errors.ErrListf("close") - } else { - list = errors.ErrListf(c.msg[0], common.IterfaceSlice(c.msg[1:])...) - } - list.Add(c.readcloser.Close()) - if c.finalizer != nil { - list.Add(c.finalizer.Finalize()) - } - return list.Result() -} diff --git a/pkg/finalizer/doc.go b/pkg/finalizer/doc.go deleted file mode 100644 index 7d460fc2d..000000000 --- a/pkg/finalizer/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Deprecated: use github.com/mandelsoft/goutils/finalizer -package finalizer diff --git a/pkg/finalizer/finalizer.go b/pkg/finalizer/finalizer.go deleted file mode 100644 index 89e054d29..000000000 --- a/pkg/finalizer/finalizer.go +++ /dev/null @@ -1,339 +0,0 @@ -package finalizer - -import ( - "fmt" - "io" - "strings" - "sync" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/exception" -) - -type Finalizable interface { - Finalize() error -} - -// Finalizer gathers finalization functions and calls -// them by calling the Finalize method(s). -// Add and Finalize may be called in any sequence and number. -// Finalize just calls the aggregated functions between its -// last and the actual call. -// This way it can be used together with defer to clean up -// stuff when leaving a function and combine it with -// controlled intermediate cleanup needed, for example as part of -// a loop block. -type Finalizer struct { - lock sync.Mutex - catch exception.Matcher - pending []func() error - nested *Finalizer - index int -} - -// BindToReader moves the pending finalizations to the close action of a reader closer. -func (f *Finalizer) BindToReader(r io.ReadCloser, msg ...string) io.ReadCloser { - f.lock.Lock() - defer f.lock.Unlock() - - if len(f.pending) == 0 { - addToCloser(r, nil, msg...) - } - n := &Finalizer{ - pending: f.pending, - } - f.pending = nil - f.nested = nil - f.index = 0 - return addToCloser(r, n, msg...) -} - -// CatchException marks the finalizer to catch exceptions. -// This must be combined with error propagating defers. -func (f *Finalizer) CatchException(matchers ...exception.Matcher) *Finalizer { - if len(matchers) > 0 { - f.catch = exception.Or(matchers...) - } else { - f.catch = exception.All - } - return f -} - -// Lock locks a given Locker and unlocks it again -// during finalization. -func (f *Finalizer) Lock(locker sync.Locker, msg ...string) *Finalizer { - locker.Lock() - return f.WithVoid(locker.Unlock, msg...) -} - -// WithVoid registers a simple function to be -// called on finalization. -func (f *Finalizer) WithVoid(fi func(), msg ...string) *Finalizer { - return f.With(CallingV(fi), msg...) -} - -func (f *Finalizer) With(fi func() error, msg ...string) *Finalizer { - if fi != nil { - f.lock.Lock() - defer f.lock.Unlock() - - if len(msg) > 0 { - ofi := fi - fi = func() error { - err := ofi() - if err == nil { - return nil - } - return errors.Wrapf(err, "%s", strings.Join(msg, " ")) - } - } - f.pending = append(f.pending, fi) - } - return f -} - -// Calling1 can be used with Finalizer.With, to call an error providing -// function with one argument. -func Calling1[T any](f func(arg T) error, arg T) func() error { - return func() error { - return f(arg) - } -} - -func Calling2[T, U any](f func(arg1 T, arg2 U) error, arg1 T, arg2 U) func() error { - return func() error { - return f(arg1, arg2) - } -} - -func Calling3[T, U, V any](f func(arg1 T, arg2 U, arg3 V) error, arg1 T, arg2 U, arg3 V) func() error { - return func() error { - return f(arg1, arg2, arg3) - } -} - -func CallingV(f func()) func() error { - return func() error { - f() - return nil - } -} - -// Calling1V can be used with Finalizer.With, to call a void -// function with one argument. -func Calling1V[T any](f func(arg T), arg T) func() error { - return func() error { - f(arg) - return nil - } -} - -func Calling2V[T, U any](f func(arg1 T, arg2 U), arg1 T, arg2 U) func() error { - return func() error { - f(arg1, arg2) - return nil - } -} - -func Calling3V[T, U, V any](f func(arg1 T, arg2 U, arg3 V), arg1 T, arg2 U, arg3 V) func() error { - return func() error { - f(arg1, arg2, arg3) - return nil - } -} - -// Close will finalize the given object by calling -// its Close function when the finalizer is finalized. -func (f *Finalizer) Close(c io.Closer, msg ...string) *Finalizer { - if c != nil { - f.With(c.Close, msg...) - } - return f -} - -// Closef will finalize the given object by calling -// its Close function when the finalizer is finalized -// and annotates an error with the given formatted message. -func (f *Finalizer) Closef(c io.Closer, msg string, args ...interface{}) *Finalizer { - if c != nil { - f.With(c.Close, fmt.Sprintf(msg, args...)) - } - return f -} - -// ClosingWith can be used add a close request to -// finalizer in a chained call. -// Unfortunately it is not possible in Go -// to define parameterized methods, therefore -// we cannot directly add this function to the -// Finalizer type. -func ClosingWith[T io.Closer](f *Finalizer, o T) T { - f.Close(o) - return o -} - -// Include includes the finalization of a given -// finalizer. -func (f *Finalizer) Include(fi *Finalizer) *Finalizer { - if fi != nil { - f.With(fi.Finalize) - } - return f -} - -// New return a new finalizer included in the actual one. -func (f *Finalizer) New() *Finalizer { - n := &Finalizer{} - f.Include(n) - return n -} - -// Nested returns a linked finalizer usable in a nested block, -// which can be separately finalized. It is intended for sequential -// use, for example in a for loop. Successive calls -// will provide the same finalizer. The nested finalizer -// SHOULD be finalized at the end of its scope before -// it is requested, again, for the next nested usage. -func (f *Finalizer) Nested() *Finalizer { - f.lock.Lock() - defer f.lock.Unlock() - - if f.nested == nil || f.nested.Length() > 0 { - f.nested = &Finalizer{} - } else { - f.pending = append(f.pending[:f.index], f.pending[f.index+1:]...) - } - f.index = len(f.pending) - f.pending = append(f.pending, f.nested.Finalize) - return f.nested -} - -func (f *Finalizer) Length() int { - f.lock.Lock() - defer f.lock.Unlock() - return len(f.pending) -} - -// FinalizeWithErrorPropagation calls all finalizations in the reverse order of -// their registration and propagates a potential error to the given error -// variable incorporating an already existing error. -// This is especially intended to be used in a deferred mode to adapt -// the error code of a function to incorporate finalization errors. -func (f *Finalizer) FinalizeWithErrorPropagation(efferr *error) { - f.lock.Lock() - defer f.lock.Unlock() - - errors.PropagateError(efferr, f.finalize) - if f.catch == nil { - return - } - if r := recover(); r != nil { - if e := exception.Exception(r); e != nil && f.catch(e) { - *efferr = errors.ErrList().Add(e).Add(*efferr).Result() - } else { - panic(r) - } - } -} - -// FinalizeWithErrorPropagationf calls all finalizations in the reverse order of -// their registration and propagates a potential error to the given error -// variable incorporating an already existing error. -// This is especially intended to be used in a deferred mode to adapt -// the error code of a function to incorporate finalization errors. -// The final error will be wrapped by the given common context. -func (f *Finalizer) FinalizeWithErrorPropagationf(efferr *error, msg string, args ...interface{}) { - f.lock.Lock() - defer f.lock.Unlock() - - errors.PropagateErrorf(efferr, f.finalize, msg, args...) - if f.catch == nil { - return - } - if r := recover(); r != nil { - if e := exception.Exception(r); e != nil && f.catch(e) { - *efferr = errors.ErrList().Add(e).Add(*efferr).Result() - } else { - panic(r) - } - } -} - -// Finalize calls all finalizations in the reverse order of -// their registration and incorporates catched exceptions. -func (f *Finalizer) Finalize() (err error) { - f.lock.Lock() - defer f.lock.Unlock() - - err = f.finalize() - if f.catch == nil { - return err - } - if r := recover(); r != nil { - if e := exception.Exception(r); e != nil && f.catch(e) { - err = errors.ErrList().Add(e).Add(err).Result() - } else { - panic(r) - } - } - return err -} - -// ThrowFinalize executes the finalization and in case -// of an error it throws the error to be catched by an outer -// finalize or other error handling with the exception package. -// It is explicitly useful fo finalize nested finalizers in loops. -func (f *Finalizer) ThrowFinalize() { - f.lock.Lock() - defer f.lock.Unlock() - - err := f.finalize() - if f.catch == nil { - throwFinalizationError(err) - } - if r := recover(); r != nil { - if e := exception.Exception(r); e != nil && f.catch(e) { - err = errors.ErrList().Add(e).Add(err).Result() - } else { - panic(r) - } - } - throwFinalizationError(err) -} - -func (f *Finalizer) finalize() (err error) { - list := errors.ErrList() - l := len(f.pending) - for i := range f.pending { - list.Add(f.pending[l-i-1]()) - } - f.pending = nil - // just forget nested ones. They are finalized here, but are then invalid. - // Adding entries after the parent has been finalized is not supported, because there is no valid determinable order. - f.nested = nil - return list.Result() -} - -type FinalizationError struct { - error -} - -func (e FinalizationError) Unwrap() error { - return e.error -} - -// FinalizeException is an exception matcher for nested finalization exceptions. -func FinalizeException(err error) bool { - if err == nil { - return false - } - _, ok := err.(FinalizationError) //nolint:errorlint // only for unwrapped error intended - return ok -} - -func throwFinalizationError(err error) { - if err == nil { - return - } - exception.Throw(FinalizationError{err}) -} diff --git a/pkg/finalizer/finalizer_test.go b/pkg/finalizer/finalizer_test.go deleted file mode 100644 index 78e3bf2cf..000000000 --- a/pkg/finalizer/finalizer_test.go +++ /dev/null @@ -1,365 +0,0 @@ -package finalizer_test - -import ( - "fmt" - "io" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/mandelsoft/goutils/exception" - - "github.com/open-component-model/ocm/pkg/finalizer" -) - -type Order []string - -func F(n string, order *Order) func() error { - return func() error { - return A(n, order) - } -} - -func A(n string, order *Order) error { - *order = append(*order, n) - return nil -} - -type closer struct { - io.ReadCloser - name string - order *Order -} - -func Closer(n string, order *Order) io.ReadCloser { - return &closer{nil, n, order} -} - -func (c *closer) Close() error { - return A(c.name, c.order) -} - -var _ = Describe("finalizer", func() { - It("finalize with arbitrary method", func() { - var finalize finalizer.Finalizer - var order Order - - finalize.With(finalizer.Calling2(A, "A", &order)) - Expect(order).To(BeNil()) - - finalize.Finalize() - - Expect(order).To(Equal(Order{"A"})) - Expect(finalize.Length()).To(Equal(0)) - }) - - It("finalize in reversed order", func() { - var finalize finalizer.Finalizer - var order Order - - finalize.With(F("A", &order)) - finalize.With(F("B", &order)) - Expect(order).To(BeNil()) - - finalize.Finalize() - - Expect(order).To(Equal(Order{"B", "A"})) - Expect(finalize.Length()).To(Equal(0)) - }) - - It("is reusable after calling Finalize", func() { - var finalize finalizer.Finalizer - var order Order - - finalize.With(F("A", &order)) - finalize.With(F("B", &order)) - - finalize.Finalize() - order = nil - - finalize.With(F("C", &order)) - finalize.With(F("D", &order)) - - finalize.Finalize() - - Expect(order).To(Equal(Order{"D", "C"})) - Expect(finalize.Length()).To(Equal(0)) - }) - - It("separately finalizes a Nested finalizer", func() { - var finalize finalizer.Finalizer - var order Order - - finalize.With(F("A", &order)) - finalize.With(F("B", &order)) - - { - finalize := finalize.Nested() - finalize.With(F("C", &order)) - finalize.Finalize() - Expect(order).To(Equal(Order{"C"})) - } - - { - finalize := finalize.Nested() - finalize.With(F("D", &order)) - finalize.Finalize() - Expect(order).To(Equal(Order{"C", "D"})) - } - - { - finalize := finalize.Nested() - finalize.With(F("E", &order)) - } - - finalize.Finalize() - Expect(order).To(Equal(Order{"C", "D", "E", "B", "A"})) - Expect(finalize.Length()).To(Equal(0)) - }) - - It("nested finalizers are handled correctly when Finalizing", func() { - var nested finalizer.Finalizer - var order Order - nested.Nested().New().Nested().With(F("A", &order)) - nested.Finalize() - nested.Nested() - Expect(order).To(Equal(Order{"A"})) - Expect(nested.Length()).To(Equal(1)) - }) - - It("separately finalizes new finalizers", func() { - var finalize finalizer.Finalizer - var order Order - - finalize.With(F("A", &order)) - finalize.With(F("B", &order)) - - { - finalize := finalize.New() - finalize.With(F("C", &order)) - } - - { - finalize := finalize.Nested() - finalize.With(F("D", &order)) - finalize.Finalize() - Expect(order).To(Equal(Order{"D"})) - } - - { - finalize := finalize.New() - finalize.With(F("E", &order)) - } - - finalize.Finalize() - Expect(order).To(Equal(Order{"D", "E", "C", "B", "A"})) - Expect(finalize.Length()).To(Equal(0)) - }) - - Context("with error propagation", func() { - Context("without context", func() { - It("succeeds", func() { - Expect(testFunc("", nil, true)).To(Succeed()) - }) - - It("fails ", func() { - err := testFunc("", fmt.Errorf("failed"), true) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("failed")) - }) - - It("succeeds with failing finalizer", func() { - err := testFunc("", nil, false) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("error occurred")) - }) - - It("fails with failing finalizer", func() { - err := testFunc("", fmt.Errorf("failed"), false) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("{failed, error occurred}")) - }) - }) - - Context("with context", func() { - It("succeeds", func() { - Expect(testFunc("context", nil, true)).To(Succeed()) - }) - - It("fails ", func() { - err := testFunc("context", fmt.Errorf("failed"), true) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("context: failed")) - }) - - It("succeeds with failing finalizer", func() { - err := testFunc("context", nil, false) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("context: error occurred")) - }) - - It("fails with failing finalizer", func() { - err := testFunc("context", fmt.Errorf("failed"), false) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("context: {failed, error occurred}")) - }) - }) - }) - - Context("with exceptions", func() { - callee := func() { - exception.Throw(fmt.Errorf("exception")) - } - caller := func() (err error) { - var finalize finalizer.Finalizer - - defer finalize.CatchException().FinalizeWithErrorPropagation(&err) - callee() - return nil - } - - It("catches exception from exception package", func() { - err := caller() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("exception")) - }) - }) - - Context("transfering", func() { - It("transfers actions", func() { - var f finalizer.Finalizer - var order Order - - f.With(F("first", &order)) - c := Closer("closer", &order) - - b := f.BindToReader(c, "bound") - - f.With(F("second", &order)) - - MustBeSuccessful(f.Finalize()) - MustBeSuccessful(b.Close()) - - Expect(order).To(Equal(Order{"second", "closer", "first"})) - }) - - It("transfers nested actions", func() { - var f finalizer.Finalizer - var order Order - - f.With(F("first", &order)) - n := f.Nested() - n.With(F("nested", &order)) - - c := Closer("closer", &order) - - b := n.BindToReader(c, "bound") - n.With(F("next", &order)) - - f.With(F("second", &order)) - - MustBeSuccessful(f.Finalize()) - MustBeSuccessful(b.Close()) - - Expect(order).To(Equal(Order{"second", "next", "first", "closer", "nested"})) - }) - }) - - Context("finalize ordering of parent and nested finalizers", func() { - It("handles empty", func() { - var f finalizer.Finalizer - f.Nested() - f.Nested() - f.Nested() - }) - It("handles mixed nested and finalize", func() { - var f finalizer.Finalizer - var order Order - - f.With(F("A", &order)) - n := f.Nested() - n.With(F("B", &order)) - f.Finalize() - f.With(F("C", &order)) - - // this should never be done, because it would cause a strange - // execution order, but it should basically not crash. - n.With(F("D", &order)) - - n2 := f.Nested() - n2.With(F("E", &order)) - f.With(F("F", &order)) - f.Finalize() - Expect(order).To(Equal(Order{"B", "A", "F", "E", "C"})) - }) - It("handles nested in order", func() { - var f finalizer.Finalizer - var order Order - - f.With(F("A", &order)) - n := f.Nested() - n.With(F("B", &order)) - f.With(F("C", &order)) - - f.Finalize() - Expect(order).To(Equal(Order{"C", "B", "A"})) - }) - It("handles omitted nested in order", func() { - var f finalizer.Finalizer - var order Order - - f.With(F("A", &order)) - n := f.Nested() - n.With(F("B", &order)) - f.With(F("C", &order)) - n2 := f.Nested() - n2.With(F("D", &order)) - f.With(F("E", &order)) - - f.Finalize() - Expect(order).To(Equal(Order{"E", "D", "C", "B", "A"})) - }) - }) - - Context("loops", func() { - It("handles exceptions", func() { - err := loopFunc() - Expect(err).NotTo(BeNil()) - Expect(err.Error()).To(Equal("finalizing nested failed")) - }) - }) -}) - -func errfunc(succeed bool) func() error { - if succeed { - return func() error { return nil } - } - return func() error { return fmt.Errorf("error occurred") } -} - -func testFunc(msg string, err error, succeed bool) (efferr error) { - var finalize finalizer.Finalizer - - defer finalize.FinalizeWithErrorPropagationf(&efferr, msg) - finalize.With(errfunc(succeed)) - return err -} - -func loopFunc() (err error) { - var finalize finalizer.Finalizer - finalize.CatchException(finalizer.FinalizeException) - - defer finalize.FinalizeWithErrorPropagation(&err) - - for i := 0; i < 3; i++ { - loop := finalize.Nested() - if i == 1 { - loop.With(func() error { return fmt.Errorf("finalizing nested failed") }) - } else { - loop.With(func() error { return nil }) - } - loop.ThrowFinalize() - } - return nil -} diff --git a/pkg/finalizer/suite_test.go b/pkg/finalizer/suite_test.go deleted file mode 100644 index c9a2d906d..000000000 --- a/pkg/finalizer/suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package finalizer_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestConfig(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Utils Test Suite") -} diff --git a/pkg/generics/cast.go b/pkg/generics/cast.go deleted file mode 100644 index 6a1c023e6..000000000 --- a/pkg/generics/cast.go +++ /dev/null @@ -1,80 +0,0 @@ -package generics - -import ( - "fmt" - "reflect" - - "github.com/mandelsoft/goutils/errors" -) - -// TypeOf returns the reflect.Type object for a formal Go type -// given by type parameter. -func TypeOf[T any]() reflect.Type { - var ifce T - return reflect.TypeOf(&ifce).Elem() -} - -// As casts an element typed by a type parameter -// to a subtype, given by the type parameter T. -func As[T any](o interface{}) T { - var _nil T - if o == nil { - return _nil - } - return o.(T) -} - -// AsE casts a pointer/error result to an interface/error -// result. -// In Go this cannot be done directly, because returning a nil pinter -// for an interface return type, would result is a typed nil value for -// the interface, and not nil, if the pointer is nil. -// Unfortunately, the relation of the pointer (even the fact, that a pointer is -// expected)to the interface (even the fact, that an interface is expected) -// cannot be expressed with Go generics. -func AsE[T any](o interface{}, err error) (T, error) { - var _nil T - if o == nil { - return _nil, err - } - return o.(T), err -} - -// AsI converts a struct pointer to an appropriate interface type -// given as type parameter. -// In Go this cannot be done directly, because returning a nil pinter -// for an interface return type, would result is a typed nil value for -// the interface, and not nil, if the pointer is nil. -// Unfortunately, the relation of the pointer (even the fact, that a pointer is -// expected)to the interface (even the fact, that an interface is expected) -// cannot be expressed with Go generics. -func AsI[T any](p any) T { - var _nil T - if p == nil { - return _nil - } - return p.(T) -} - -// Cast casts one type parameter to another type parameter, -// which have a sub type relation. -// This cannot be described by type parameter constraints in Go, because -// constraints may not be type parameters again. -func Cast[S any](o any) (S, error) { - var _nil S - var t any = o - if s, ok := t.(S); ok { - return s, nil - } - return _nil, errors.ErrInvalid("type", fmt.Sprintf("%T", o)) -} - -// TryCast tries a type cast usable for variable with a parametric type. -func TryCast[T any](p any) (T, bool) { - var _nil T - if p == nil { - return _nil, false - } - t, ok := p.(T) - return t, ok -} diff --git a/pkg/generics/conditional.go b/pkg/generics/conditional.go deleted file mode 100644 index 502761d24..000000000 --- a/pkg/generics/conditional.go +++ /dev/null @@ -1,8 +0,0 @@ -package generics - -func Conditional[T any](cond bool, a, b T) T { - if cond { - return a - } - return b -} diff --git a/pkg/generics/convert.go b/pkg/generics/convert.go deleted file mode 100644 index 20dcc6c7d..000000000 --- a/pkg/generics/convert.go +++ /dev/null @@ -1,39 +0,0 @@ -package generics - -// ConvertSlice Converts the element typ of a slice. -// hereby, Type S must be a subtype of type T. -func ConvertSlice[S, T any](in ...S) []T { - return ConvertSliceTo[T, S](in) -} - -// ConvertSliceTo Converts the element typ of a slice. -// hereby, Type S must be a subtype of type T. -// typically, S can be omitted when used, because it can be -// derived from the argument. -func ConvertSliceTo[T, S any](in []S) []T { - if in == nil { - return nil - } - r := make([]T, len(in)) - for i := range in { - var s any = in[i] - r[i] = As[T](s) - } - return r -} - -// ConvertSliceWith converts the element type of a slice -// using a converter function. -// Unfortunately this cannot be expressed in a type-safe way in Go. -// I MUST follow the type constraint I super S, which cannot be expressed in Go. -func ConvertSliceWith[S, T, I any](c func(I) T, in []S) []T { - if in == nil { - return nil - } - r := make([]T, len(in)) - for i := range in { - var s any = in[i] - r[i] = c(As[I](s)) - } - return r -} diff --git a/pkg/generics/doc.go b/pkg/generics/doc.go deleted file mode 100644 index 1ccf181c0..000000000 --- a/pkg/generics/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Deprecated: use appropriate sub packages in github.com/mandelsoft/goutils -package generics diff --git a/pkg/generics/map.go b/pkg/generics/map.go deleted file mode 100644 index b80852b46..000000000 --- a/pkg/generics/map.go +++ /dev/null @@ -1,29 +0,0 @@ -package generics - -func MapKeys[K comparable, V any](m map[K]V) Set[K] { - s := Set[K]{} - for k := range m { - s.Add(k) - } - return s -} - -func MapKeyArray[K comparable, V any](m map[K]V) []K { - a := make([]K, len(m)) - i := 0 - for k := range m { - a[i] = k - i++ - } - return a -} - -func MapValues[K comparable, V any](m map[K]V) []V { - a := make([]V, len(m)) - i := 0 - for _, v := range m { - a[i] = v - i++ - } - return a -} diff --git a/pkg/generics/set.go b/pkg/generics/set.go deleted file mode 100644 index 6c827d7a4..000000000 --- a/pkg/generics/set.go +++ /dev/null @@ -1,62 +0,0 @@ -package generics - -import ( - "golang.org/x/exp/maps" - "golang.org/x/exp/slices" -) - -type Set[K comparable] map[K]struct{} - -func NewSet[K comparable](keys ...K) Set[K] { - return Set[K]{}.Add(keys...) -} - -func (s Set[K]) Add(keys ...K) Set[K] { - for _, k := range keys { - s[k] = struct{}{} - } - return s -} - -func (s Set[K]) Delete(keys ...K) Set[K] { - for _, k := range keys { - delete(s, k) - } - return s -} - -func (s Set[K]) Contains(keys ...K) bool { - for _, k := range keys { - if _, ok := s[k]; !ok { - return false - } - } - return true -} - -func (s Set[K]) AsArray() []K { - keys := []K{} - for k := range s { - keys = append(keys, k) - } - return keys -} - -func KeySet[K comparable, V any](m map[K]V) Set[K] { - s := Set[K]{} - for k := range m { - s.Add(k) - } - return s -} - -type Comparable[K any] interface { - comparable - Compare(o K) int -} - -func KeyList[K Comparable[K], V any](m map[K]V) []K { - s := maps.Keys(m) - slices.SortFunc(s, func(a, b K) int { return a.Compare(b) }) - return s -} diff --git a/pkg/generics/slice.go b/pkg/generics/slice.go deleted file mode 100644 index 26ebc8d0d..000000000 --- a/pkg/generics/slice.go +++ /dev/null @@ -1,9 +0,0 @@ -package generics - -import ( - "golang.org/x/exp/slices" -) - -func AppendedSlice[E any](slice []E, elems ...E) []E { - return append(slices.Clone(slice), elems...) -} diff --git a/pkg/generics/utils.go b/pkg/generics/utils.go deleted file mode 100644 index 74a45bb66..000000000 --- a/pkg/generics/utils.go +++ /dev/null @@ -1,13 +0,0 @@ -package generics - -func Pointer[T any](t T) *T { - return &t -} - -func Value[T any](t *T) T { - if t != nil { - return *t - } - var v T - return v -} diff --git a/pkg/helm/downloader.go b/pkg/helm/downloader.go deleted file mode 100644 index 75db7e749..000000000 --- a/pkg/helm/downloader.go +++ /dev/null @@ -1,198 +0,0 @@ -package helm - -import ( - "fmt" - "strings" - - "github.com/mandelsoft/filepath/pkg/filepath" - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/vfs/pkg/osfs" - "github.com/mandelsoft/vfs/pkg/vfs" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/downloader" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/repo" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/helm/identity" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/directcreds" - "github.com/open-component-model/ocm/pkg/contexts/oci" - ocihelm "github.com/open-component-model/ocm/pkg/contexts/ocm/download/handlers/helm" - "github.com/open-component-model/ocm/pkg/runtime" -) - -type chartDownloader struct { - *downloader.ChartDownloader - *chartAccess - creds common.Properties - keyring []byte -} - -func DownloadChart(out common.Printer, ctx oci.ContextProvider, ref, version, repourl string, opts ...Option) (ChartAccess, error) { - if version == "" { - return nil, fmt.Errorf("version required") - } - repourl = strings.TrimSuffix(repourl, "/") - - acc, err := newTempChartAccess(osfs.New()) - if err != nil { - return nil, err - } - - defer func() { - if err != nil { - acc.Close() - } - }() - - s := cli.EnvSettings{} - - dl := &chartDownloader{ - ChartDownloader: &downloader.ChartDownloader{ - Out: out, - Getters: getter.All(&s), - }, - chartAccess: acc, - } - for _, o := range opts { - err = o.apply(dl) - if err != nil { - return nil, err - } - } - - err = dl.complete(ctx, ref, repourl) - if err != nil { - return nil, err - } - - chart := "" - prov := "" - aset := "" - if registry.IsOCI(repourl) { - fs := osfs.New() - chart = vfs.Join(fs, dl.root, filepath.Base(ref)+".tgz") - - var creds credentials.CredentialsSource - if dl.creds != nil { - creds = directcreds.NewCredentials(dl.creds) - } - - chart, prov, aset, err = ocihelm.Download2(out, ctx.OCIContext(), identity.OCIRepoURL(repourl, ref)+":"+version, chart, osfs.New(), true, creds) - if prov != "" && dl.Verify > downloader.VerifyNever && dl.Verify != downloader.VerifyLater { - _, err = downloader.VerifyChart(chart, dl.Keyring) - if err != nil { - // Fail always in this case, since it means the verification step - // failed. - return nil, err - } - } - } else { - chart, _, err = dl.DownloadTo("repo/"+ref, version, dl.root) - prov = chart + ".prov" - } - if err != nil { - return nil, err - } - if prov != "" && filepath.Exists(prov) { - dl.prov = prov - } - dl.chart = chart - dl.aset = aset - return dl.chartAccess, nil -} - -func (d *chartDownloader) complete(ctx oci.ContextProvider, ref, repourl string) error { - rf := repo.NewFile() - - if d.creds == nil { - d.creds = identity.GetCredentials(ctx.OCIContext(), repourl, ref) - } - creds := d.creds - if creds == nil { - creds = common.Properties{} - } - - config := vfs.Join(d.fs, d.root, ".config") - err := d.fs.MkdirAll(config, 0o700) - if err != nil { - return err - } - if len(d.keyring) != 0 { - err = d.writeFile("keyring", config, &d.Keyring, d.keyring, "keyring file") - if err != nil { - return err - } - d.Verify = downloader.VerifyIfPossible - } - - if registry.IsOCI(repourl) { - return nil - } - entry := repo.Entry{ - Name: "repo", - URL: repourl, - Username: creds[identity.ATTR_USERNAME], - Password: creds[identity.ATTR_PASSWORD], - } - - cache := vfs.Join(d.fs, d.root, ".cache") - err = d.fs.MkdirAll(cache, 0o700) - if err != nil { - return err - } - - if len(creds[identity.ATTR_CERTIFICATE_AUTHORITY]) != 0 { - err = d.writeFile("cacert", config, &entry.CAFile, []byte(creds[identity.ATTR_CERTIFICATE_AUTHORITY]), "CA file") - if err != nil { - return err - } - } - if len(creds[identity.ATTR_CERTIFICATE]) != 0 { - err = d.writeFile("cert", config, &entry.CertFile, []byte(creds[identity.ATTR_CERTIFICATE]), "certificate file") - if err != nil { - return err - } - } - if len(creds[identity.ATTR_PRIVATE_KEY]) != 0 { - err = d.writeFile("private-key", config, &entry.KeyFile, []byte(creds[identity.ATTR_PRIVATE_KEY]), "private key file") - if err != nil { - return err - } - } - rf.Add(&entry) - - cr, err := repo.NewChartRepository(&entry, d.Getters) - if err != nil { - return errors.Wrapf(err, "cannot get chart repository %q", repourl) - } - - d.RepositoryCache, cr.CachePath = cache, cache - - _, err = cr.DownloadIndexFile() - if err != nil { - return errors.Wrapf(err, "cannot download repository index for %q", repourl) - } - - data, err := runtime.DefaultYAMLEncoding.Marshal(rf) - if err != nil { - return errors.Wrapf(err, "cannot marshal repository file") - } - err = d.writeFile("repository", config, &d.RepositoryConfig, data, "repository config") - if err != nil { - return err - } - - return nil -} - -func (d *chartDownloader) writeFile(name, root string, path *string, data []byte, desc string) error { - *path = vfs.Join(d.fs, root, name) - err := vfs.WriteFile(d.fs, *path, data, 0o600) - if err != nil { - return errors.Wrapf(err, "cannot write %s %q", desc, *path) - } - return nil -} diff --git a/pkg/helm/loader/access.go b/pkg/helm/loader/access.go deleted file mode 100644 index 71d00b9b8..000000000 --- a/pkg/helm/loader/access.go +++ /dev/null @@ -1,52 +0,0 @@ -package loader - -import ( - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/helm" -) - -type accessLoader struct { - access helm.ChartAccess -} - -func AccessLoader(acc helm.ChartAccess) Loader { - return &accessLoader{access: acc} -} - -func (l *accessLoader) Close() error { - return l.access.Close() -} - -func (l *accessLoader) ChartArchive() (blobaccess.BlobAccess, error) { - return l.access.Chart() -} - -func (l *accessLoader) ChartArtefactSet() (blobaccess.BlobAccess, error) { - return l.access.ArtefactSet() -} - -func (l *accessLoader) Chart() (*chart.Chart, error) { - acc, err := l.access.Chart() - if err != nil { - return nil, err - } - defer acc.Close() - r, err := acc.Reader() - if err != nil { - return nil, err - } - defer r.Close() - return loader.LoadArchive(r) -} - -func (l *accessLoader) Provenance() ([]byte, error) { - prov, err := l.access.Prov() - if prov == nil || err != nil { - return nil, err - } - defer prov.Close() - return prov.Get() -} diff --git a/pkg/helm/options.go b/pkg/helm/options.go deleted file mode 100644 index c6bd5a019..000000000 --- a/pkg/helm/options.go +++ /dev/null @@ -1,105 +0,0 @@ -package helm - -import ( - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/helm/identity" -) - -type Option interface { - apply(dl *chartDownloader) error -} - -//////////////////////////////////////////////////////////////////////////////// - -type credOption struct { - creds common.Properties -} - -func (c *credOption) apply(dl *chartDownloader) error { - if c.creds != nil { - dl.creds = c.creds - } - return nil -} - -func WithCredentials(creds common.Properties) Option { - return &credOption{creds} -} - -//////////////////////////////////////////////////////////////////////////////// - -type authOption struct { - user, password string -} - -func (c *authOption) apply(dl *chartDownloader) error { - if dl.creds == nil { - dl.creds = common.Properties{} - } - dl.creds[identity.ATTR_USERNAME] = c.user - dl.creds[identity.ATTR_PASSWORD] = c.password - return nil -} - -func WithBasicAuth(user, password string) Option { - return &authOption{user, password} -} - -//////////////////////////////////////////////////////////////////////////////// - -type certOption struct { - cert []byte - privkey []byte -} - -func (c *certOption) apply(dl *chartDownloader) error { - if len(c.privkey) != 0 { - if dl.creds == nil { - dl.creds = common.Properties{} - } - dl.creds[identity.ATTR_CERTIFICATE] = string(c.cert) - dl.creds[identity.ATTR_PRIVATE_KEY] = string(c.privkey) - } - return nil -} - -func WithCert(cert []byte, privkey []byte) Option { - return &certOption{cert, privkey} -} - -//////////////////////////////////////////////////////////////////////////////// - -type cacertOption struct { - data []byte -} - -func (c *cacertOption) apply(dl *chartDownloader) error { - if len(c.data) > 0 { - if dl.creds == nil { - dl.creds = common.Properties{} - } - dl.creds[identity.ATTR_CERTIFICATE_AUTHORITY] = string(c.data) - } - return nil -} - -func WithRootCert(data []byte) Option { - return &cacertOption{data} -} - -//////////////////////////////////////////////////////////////////////////////// - -type keyringOption struct { - data []byte -} - -func (c *keyringOption) apply(dl *chartDownloader) error { - if len(c.data) > 0 { - dl.keyring = c.data - } - return nil -} - -func WithKeyring(data []byte) Option { - return &keyringOption{data} -} diff --git a/pkg/logging/config_test.go b/pkg/logging/config_test.go deleted file mode 100644 index 062a95d56..000000000 --- a/pkg/logging/config_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package logging_test - -import ( - "bytes" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/logging/testhelper" - - "github.com/mandelsoft/logging" - logcfg "github.com/mandelsoft/logging/config" - "github.com/tonglil/buflogr" - - local "github.com/open-component-model/ocm/pkg/logging" -) - -//////////////////////////////////////////////////////////////////////////////// - -var _ = Describe("logging configuration", func() { - var buf bytes.Buffer - var ctx logging.Context - - BeforeEach(func() { - local.SetContext(logging.NewDefault()) - buf.Reset() - def := buflogr.NewWithBuffer(&buf) - ctx = local.Context() - ctx.SetBaseLogger(def) - }) - - It("just logs with defaults", func() { - LogTest(ctx) - Expect(buf.String()).To(StringEqualTrimmedWithContext(` -V[3] info realm ocm -V[2] warn realm ocm -ERROR error realm ocm -`)) - }) - It("just logs with config", func() { - r := logcfg.ConditionalRule("debug") - cfg := &logcfg.Config{ - Rules: []logcfg.Rule{r}, - } - - Expect(local.Configure(cfg)).To(Succeed()) - LogTest(ctx) - Expect(buf.String()).To(StringEqualTrimmedWithContext(` -V[4] debug realm ocm -V[3] info realm ocm -V[2] warn realm ocm -ERROR error realm ocm -`)) - }) -}) diff --git a/pkg/maven/access.go b/pkg/maven/access.go deleted file mode 100644 index 17c2513dd..000000000 --- a/pkg/maven/access.go +++ /dev/null @@ -1,411 +0,0 @@ -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/goutils/ioutils" - "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.ParseRequestURI(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 ioutils.DupReadCloser, 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 - } - reader, err := reader.Dup() - if rerr != nil { - return err - } - 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 deleted file mode 100644 index 816e467ff..000000000 --- a/pkg/maven/access_test.go +++ /dev/null @@ -1,132 +0,0 @@ -package maven_test - -import ( - "crypto" - "io" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/env/builder" - - "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/logging.go b/pkg/maven/logging.go deleted file mode 100644 index 17bfd5df5..000000000 --- a/pkg/maven/logging.go +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index efd26ff62..000000000 --- a/pkg/maven/maventest/testdata.go +++ /dev/null @@ -1,13 +0,0 @@ -package maventest - -import ( - "github.com/open-component-model/ocm/pkg/env" -) - -func TestData(dest ...string) env.Option { - return env.ProjectTestDataForCaller(dest...) -} - -func ModifiableTestData(dest ...string) env.Option { - return env.ModifiableProjectTestDataForCaller(dest...) -} diff --git a/pkg/mime/types.go b/pkg/mime/types.go deleted file mode 100644 index 715f6845b..000000000 --- a/pkg/mime/types.go +++ /dev/null @@ -1,50 +0,0 @@ -package mime - -import ( - "mime" - - "github.com/open-component-model/ocm/pkg/logging" -) - -const ( - MIME_TEXT = "text/plain" - MIME_OCTET = "application/octet-stream" - - MIME_JSON = "application/x-json" - MIME_JSON_ALT = "text/json" // no utf8 - MIME_JSON_OFFICIAL = "application/json" - MIME_XML = "application/xml" - MIME_YAML = "application/x-yaml" - MIME_YAML_ALT = "text/yaml" // no utf8 - MIME_YAML_OFFICIAL = "application/yaml" - - MIME_GZIP = "application/gzip" - MIME_TAR = "application/x-tar" - MIME_TGZ = "application/x-tgz" - MIME_TGZ_ALT = MIME_TAR + "+gzip" - - MIME_JAR = "application/x-jar" -) - -func init() { - ocmTypes := map[string]string{ - // added entries - ".txt": MIME_TEXT, - ".yaml": MIME_YAML_OFFICIAL, - ".gzip": MIME_GZIP, - ".tar": MIME_TAR, - ".tgz": MIME_TGZ, - ".tar.gz": MIME_TGZ, - ".pom": MIME_XML, - ".zip": MIME_GZIP, - ".jar": MIME_JAR, - ".module": MIME_JSON, // gradle module metadata - } - - for k, v := range ocmTypes { - err := mime.AddExtensionType(k, v) - if err != nil { - logging.DynamicLogger(logging.DefineSubRealm("mimeutils")).Error("failed to add extension type", "extension", k, "type", v, "error", err) - } - } -} diff --git a/pkg/optionutils/doc.go b/pkg/optionutils/doc.go deleted file mode 100644 index 25ffab3e7..000000000 --- a/pkg/optionutils/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Deprecated: use github.com/mandelsoft/goutils/optionutils -package optionutils diff --git a/pkg/optionutils/nested.go b/pkg/optionutils/nested.go deleted file mode 100644 index f10b2083b..000000000 --- a/pkg/optionutils/nested.go +++ /dev/null @@ -1,66 +0,0 @@ -package optionutils - -// NestedOptionsProvider is the interface for a -// more specific options object to provide -// access to a nested options object of type T. -// T must be a pointer type. -type NestedOptionsProvider[T any] interface { - NestedOptions() T -} - -// OptionTargetProvider is helper interface -// to declare a pointer type (*O) for an options -// object providing access to a nested -// options object of type N (must be a pointer type). -type OptionTargetProvider[N, O any] interface { - NestedOptionsProvider[N] - *O -} - -// OptionWrapper genericly wraps a nested option of type Option[N] to -// an option of type Option[*O], assuming that the nested option source -// N implements NestedOptionsProvider[N]. -// P is only a helper type parameter for Go and doesn't need to be given. -// It is the pointer type for O (P = *O). -// -// create a wrap option function for all wrappable options with -// -// func WrapXXX[O any, P optionutils.OptionTargetProvider[*Options, O]](args...any) optionutils.Option[P] { -// return optionutils.OptionWrapper[*Options, O, P](WithXXX(args...)) -// } -// -// where *Options is the type of the pointer type to the options object to be nested. -// -// The outer option functions wrapping the nested one can then be defined as -// -// func WithXXX(h string) Option { -// return optionutils.WrapXXX[Options](h) -// } -// -// For an example see package github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes/rpi. -func OptionWrapper[N, O any, P OptionTargetProvider[N, O]](o Option[N]) Option[P] { - return optionWrapper[N, O, P]{o} -} - -type optionWrapper[N, O any, P OptionTargetProvider[N, O]] struct { - opt Option[N] -} - -func (w optionWrapper[N, O, P]) ApplyTo(opts P) { - w.opt.ApplyTo(opts.NestedOptions()) -} - -/////////////////////////////////////////////////////////////////////////////(// - -func OptionWrapperFunc[N, O any](o Option[N], nested func(outer O) N) Option[O] { - return optionWrapperFunc[N, O]{o, nested} -} - -type optionWrapperFunc[N, O any] struct { - opt Option[N] - nested func(O) N -} - -func (w optionWrapperFunc[N, O]) ApplyTo(opts O) { - w.opt.ApplyTo(w.nested(opts)) -} diff --git a/pkg/optionutils/options.go b/pkg/optionutils/options.go deleted file mode 100644 index e079a390b..000000000 --- a/pkg/optionutils/options.go +++ /dev/null @@ -1,26 +0,0 @@ -package optionutils - -type Option[T any] interface { - ApplyTo(T) -} - -// EvalOptions applies options to a new options object -// and returns this object. -// O must be a struct type. -func EvalOptions[O any](opts ...Option[*O]) *O { - var eff O - ApplyOptions(&eff, opts...) - return &eff -} - -// ApplyOptions applies options to -// an option target O. O must either -// be a target interface type or a target struct -// pointer type. -func ApplyOptions[O any](opts O, list ...Option[O]) { - for _, opt := range list { - if opt != nil { - opt.ApplyTo(opts) - } - } -} diff --git a/pkg/optionutils/pointer.go b/pkg/optionutils/pointer.go deleted file mode 100644 index 0c43c9c4c..000000000 --- a/pkg/optionutils/pointer.go +++ /dev/null @@ -1,35 +0,0 @@ -package optionutils - -import ( - "github.com/open-component-model/ocm/pkg/utils" -) - -func PointerTo[T any](v T) *T { - temp := v - return &temp -} - -func AsValue[T any](p *T) T { - var r T - if p != nil { - r = *p - } - return r -} - -func AsBool(b *bool, def ...bool) bool { - return utils.AsBool(b, def...) -} - -func ApplyOption[T any](opt *T, tgt **T) { - if opt != nil { - *tgt = opt - } -} - -// GetOptionFlag returns the flag value used to set a bool option -// based on optionally specified explicit value(s). -// The default value is to enable the option (true). -func GetOptionFlag(list ...bool) bool { - return utils.OptionalDefaultedBool(true, list...) -} diff --git a/pkg/optionutils/target.go b/pkg/optionutils/target.go deleted file mode 100644 index 8f025358f..000000000 --- a/pkg/optionutils/target.go +++ /dev/null @@ -1,35 +0,0 @@ -package optionutils - -import ( - "github.com/mandelsoft/goutils/generics" -) - -/////////////////////////////////////////////////////////////////////////////(// -// if the option target is an interface, it is easily possible to -// provide new targets with more options just by extending the -// target interface. The option consumer the accepts options for -// the target interface. -// To be able to reuse options from the base target interface -// a wrapper option implementation is required which implements -// the extended option interface and maps it to the base option -// interface. -// The following mechanism requires option targets W and B to be -// interface types. - -type targetInterfaceWrapper[B any, W any /*B*/] struct { - option Option[B] -} - -func (w *targetInterfaceWrapper[B, W]) ApplyTo(opts W) { - w.option.ApplyTo(generics.Cast[B](opts)) -} - -// MapOptionTarget maps the option target interface from -// B to W, hereby, W must be a subtype of B, which cannot be -// expressed with Go generics (Type constraint should be W B). -// If this constraint is not met, there will be a runtime error. -func MapOptionTarget[W, B any](opt Option[B]) Option[W] { - return &targetInterfaceWrapper[B, W]{ - opt, - } -} diff --git a/pkg/optionutils/utils.go b/pkg/optionutils/utils.go deleted file mode 100644 index 52479a708..000000000 --- a/pkg/optionutils/utils.go +++ /dev/null @@ -1,7 +0,0 @@ -package optionutils - -import "github.com/mandelsoft/goutils/sliceutils" - -func WithDefaults[O any](opts []O, defaults ...O) []O { - return sliceutils.CopyAppend(defaults, opts...) -} diff --git a/pkg/refmgmt/refmgmt.go b/pkg/refmgmt/refmgmt.go deleted file mode 100644 index c62e60af4..000000000 --- a/pkg/refmgmt/refmgmt.go +++ /dev/null @@ -1,160 +0,0 @@ -package refmgmt - -import ( - "fmt" - "sync" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/logging" -) - -var ALLOC_REALM = logging.DefineSubRealm("reference counting", "refcnt") - -var AllocLog = logging.DynamicLogger(ALLOC_REALM) - -type Allocatable interface { - Ref() error - Unref() error -} - -type CleanupHandler interface { - Cleanup() -} - -type CleanupHandlerFunc func() - -func (f CleanupHandlerFunc) Cleanup() { - f() -} - -type ExtendedAllocatable interface { - BeforeCleanup(f CleanupHandler) - Ref() error - Unref() error -} - -type RefMgmt interface { - UnrefLast() error - ExtendedAllocatable - IsClosed() bool - RefCount() int - - WithName(name string) RefMgmt -} - -type refMgmt struct { - lock sync.Mutex - refcount int - closed bool - before []CleanupHandler - cleanup func() error - name string -} - -func NewAllocatable(cleanup func() error, unused ...bool) RefMgmt { - n := 1 - for _, b := range unused { - if b { - n = 0 - } - } - return &refMgmt{refcount: n, cleanup: cleanup, name: "object"} -} - -func (c *refMgmt) WithName(name string) RefMgmt { - c.name = name - return c -} - -func (c *refMgmt) IsClosed() bool { - c.lock.Lock() - defer c.lock.Unlock() - return c.closed -} - -func (c *refMgmt) Ref() error { - c.lock.Lock() - defer c.lock.Unlock() - if c.closed { - return ErrClosed - } - c.refcount++ - AllocLog.Trace("ref", "name", c.name, "refcnt", c.refcount) - return nil -} - -func (c *refMgmt) Unref() error { - c.lock.Lock() - defer c.lock.Unlock() - if c.closed { - return ErrClosed - } - - var err error - - c.refcount-- - AllocLog.Trace("unref", "name", c.name, "refcnt", c.refcount) - if c.refcount <= 0 { - for _, f := range c.before { - f.Cleanup() - } - if c.cleanup != nil { - err = c.cleanup() - } - c.closed = true - } - - if err != nil { - return fmt.Errorf("unable to unref %s: %w", c.name, err) - } - - return nil -} - -func (c *refMgmt) RefCount() int { - c.lock.Lock() - defer c.lock.Unlock() - return c.refcount -} - -func (c *refMgmt) BeforeCleanup(f CleanupHandler) { - c.lock.Lock() - defer c.lock.Unlock() - c.before = append(c.before, f) -} - -func (c *refMgmt) UnrefLast() error { - c.lock.Lock() - defer c.lock.Unlock() - if c.closed { - return ErrClosed - } - - if c.refcount > 1 { - AllocLog.Trace("unref last failed", "name", c.name, "pending", c.refcount) - return errors.ErrStillInUseWrap(errors.Newf("%d reference(s) pending", c.refcount), c.name) - } - - var err error - - c.refcount-- - AllocLog.Trace("unref last", "name", c.name, "refcnt", c.refcount) - if c.refcount <= 0 { - for _, f := range c.before { - f.Cleanup() - } - if c.cleanup != nil { - err = c.cleanup() - } - - c.closed = true - } - - if err != nil { - AllocLog.Trace("cleanup last failed", "name", c.name, "error", err.Error()) - return errors.Wrapf(err, "unable to cleanup %s while unref last", c.name) - } - - return nil -} diff --git a/pkg/refmgmt/resource/resource.go b/pkg/refmgmt/resource/resource.go deleted file mode 100644 index 1fb81dd2f..000000000 --- a/pkg/refmgmt/resource/resource.go +++ /dev/null @@ -1,275 +0,0 @@ -package resource - -import ( - "io" - - "github.com/open-component-model/ocm/pkg/common/accessio" - "github.com/open-component-model/ocm/pkg/refmgmt" -) - -type CloserView interface { - Close() error - IsClosed() bool - Execute(func() error) error - Allocatable() refmgmt.ExtendedAllocatable - refmgmt.LazyMode - refmgmt.RefCountProvider -} - -var _ CloserView = refmgmt.CloserView(nil) - -var ErrClosed = accessio.ErrClosed - -// resourceViewInterface is a helper type used to implement parameter type -// recursion for ResourceView[T ResourceView[T]], which is not allowed in Go. -type resourceViewInterface[T any] interface { - io.Closer - IsClosed() bool - Dup[T] -} - -// ResourceView is the view related part of a resource interface T. -// T must incorporate ResourceView[T], which cannot directly be expressed -// in go, but with the helper interface defining the API. -type ResourceView[T resourceViewInterface[T]] interface { - resourceViewInterface[T] -} - -// ResourceViewInt can be used to execute an operation on a non-closed -// view. -// Execute call a synchronized function on a non-closed view. -type ResourceViewInt[T resourceViewInterface[T]] interface { - refmgmt.LazyMode - refmgmt.RefCountProvider - resourceViewInterface[T] - - Execute(func() error) error - Allocatable() refmgmt.ExtendedAllocatable -} - -type Dup[T any] interface { - Dup() (T, error) -} - -//////////////////////////////////////////////////////////////////////////////// - -// ViewManager is the interface of the reference manager, which -// can be used to gain new views to a managed resource. -type ViewManager[T any] interface { - RefCount() int - Allocatable() refmgmt.ExtendedAllocatable - View(main ...bool) (T, error) - IsClosed() bool -} - -// ResourceViewCreator is a function which must be provided by the resource provider -// to map an implementation to the resource interface T. -// It must use NewView to create the view related part of a resource. -type ResourceViewCreator[T any, I io.Closer] func(I, CloserView, ViewManager[T]) T - -type viewManager[T any, I ResourceImplementation[T]] struct { - refs refmgmt.ReferencableCloser - creator ResourceViewCreator[T, I] - impl I -} - -// ResourceImplementation is the minimal interface for an implementation -// a resource with managed views. -type ResourceImplementation[T any] interface { - io.Closer - SetViewManager(m ViewManager[T]) - ViewManager[T] -} - -// NewResource creates a resource based on an implementation and a ResourceViewCreator. -// function. -func NewResource[T any, I ResourceImplementation[T]](impl I, c ResourceViewCreator[T, I], name string, main ...bool) T { - i := &viewManager[T, I]{ - refs: refmgmt.NewRefCloser(impl, true).WithName(name), - creator: c, - impl: impl, - } - impl.SetViewManager(i) - t, _ := i.View(main...) - return t -} - -func (i *viewManager[T, I]) RefCount() int { - return i.refs.RefCount() -} - -func (i *viewManager[T, I]) Allocatable() refmgmt.ExtendedAllocatable { - return i.refs -} - -func (i *viewManager[T, I]) View(main ...bool) (T, error) { - var _nil T - - v, err := i.refs.View(main...) - if err != nil { - return _nil, err - } - return i.creator(i.impl, v, i), nil -} - -func (i *viewManager[T, I]) IsClosed() bool { - return i.refs.IsClosed() -} - -//////////////////////////////////////////////////////////////////////////////// - -// noneRefCloser is used to compose a non-referencing -// view, which does not forward the close operation -// to the view manager. Its state directly reflects -// the state of the view manager. -type noneRefCloser[T io.Closer] struct { - mgr ViewManager[T] -} - -var _ CloserView = (*noneRefCloser[io.Closer])(nil) - -func (n *noneRefCloser[T]) Close() error { - if n.mgr.IsClosed() { - return ErrClosed - } - return nil -} - -func (n *noneRefCloser[T]) IsClosed() bool { - return n.mgr.IsClosed() -} - -func (n *noneRefCloser[T]) Execute(f func() error) error { - v, err := n.mgr.View() - if err != nil { - return err - } - defer v.Close() - return f() -} - -func (n *noneRefCloser[T]) Lazy() { -} - -func (n *noneRefCloser[T]) RefCount() int { - return n.mgr.RefCount() -} - -func (n *noneRefCloser[T]) Allocatable() refmgmt.ExtendedAllocatable { - return n.mgr.Allocatable() -} - -//////////////////////////////////////////////////////////////////////////////// - -type resourceView[T any] struct { - view CloserView - mgr ViewManager[T] -} - -// NewView is to be called by a resource view creator to map -// the given resource implementation to complete resource interface. -// It should create an object with two local embedded fields: -// - the returned ResourceView and the -// - given resource implementation. -func NewView[T resourceViewInterface[T]](v CloserView, d ViewManager[T]) ResourceViewInt[T] { - return &resourceView[T]{v, d} -} - -// NewNonRefView provides a reference-less view directly for the reference manager. -// It is valid as long as the reference manager is not closed with the last regular -// reference. -func NewNonRefView[T resourceViewInterface[T]](d ViewManager[T]) ResourceViewInt[T] { - return &resourceView[T]{&noneRefCloser[T]{d}, d} -} - -func NoneRefCloserView[T io.Closer](d ViewManager[T]) CloserView { - return &noneRefCloser[T]{d} -} - -func (v *resourceView[T]) IsClosed() bool { - return v.view.IsClosed() -} - -func (v *resourceView[T]) Close() error { - return v.view.Close() -} - -func (v *resourceView[T]) Execute(f func() error) error { - return v.view.Execute(f) -} - -func (v *resourceView[T]) Allocatable() refmgmt.ExtendedAllocatable { - return v.view.Allocatable() -} - -func (v *resourceView[T]) Dup() (t T, err error) { - err = v.Execute(func() error { - t, err = v.mgr.View() - return err - }) - return t, err -} - -func (v *resourceView[T]) Lazy() { - v.view.Lazy() -} - -func (v *resourceView[T]) RefCount() int { - return v.view.RefCount() -} - -//////////////////////////////////////////////////////////////////////////////// - -type ResourceImplBase[T any] struct { - refs ViewManager[T] - closer []io.Closer -} - -// NewResourceImplBase creates an implementation base for a resource T -// referencing another resource M. -func NewResourceImplBase[T any, M io.Closer](m ViewManager[M], closer ...io.Closer) (*ResourceImplBase[T], error) { - if m != nil { - ref, err := m.View() - if err != nil { - return nil, err - } - closer = append(closer, ref) - } - return &ResourceImplBase[T]{ - closer: closer, - }, nil -} - -func NewSimpleResourceImplBase[T any](closer ...io.Closer) *ResourceImplBase[T] { - return &ResourceImplBase[T]{ - closer: closer, - } -} - -func (b *ResourceImplBase[T]) SetViewManager(m ViewManager[T]) { - b.refs = m -} - -func (b *ResourceImplBase[T]) RefCount() int { - return b.refs.RefCount() -} - -func (b *ResourceImplBase[T]) Allocatable() refmgmt.ExtendedAllocatable { - return b.refs.Allocatable() -} - -func (b *ResourceImplBase[T]) ViewManager() ViewManager[T] { - return b.refs -} - -func (b *ResourceImplBase[T]) View(main ...bool) (T, error) { - return b.refs.View(main...) -} - -func (b *ResourceImplBase[T]) IsClosed() bool { - return b.refs.IsClosed() -} - -func (b *ResourceImplBase[T]) Close() error { - return accessio.Close(b.closer...) -} diff --git a/pkg/refmgmt/resource/resource_test.go b/pkg/refmgmt/resource/resource_test.go deleted file mode 100644 index e6a3fc4a1..000000000 --- a/pkg/refmgmt/resource/resource_test.go +++ /dev/null @@ -1,131 +0,0 @@ -package resource_test - -import ( - "fmt" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/refmgmt/resource" -) - -// Resource is the intended resource interface. -// It must incorporate the resource.ResourceView interface -// providing the view related part of the interface. -type Resource interface { - resource.ResourceView[Resource] - - Name() string - Operation(err error) error -} - -type ( - // ResourceImpl implements io.Closer to finally release allocated resources - // and the additional non-view-related part of the resource interface. - ResourceImpl struct { - resource.ResourceImplBase[Resource] - name string - } - _resourceImpl = *ResourceImpl -) - -var _ resource.ResourceImplementation[Resource] = (*ResourceImpl)(nil) - -func (r *ResourceImpl) Name() string { - return r.name -} - -func (r *ResourceImpl) Operation(err error) error { - return err -} - -// Close is called for the last closed view and -// may handle the release of allocated sub resources. -func (r *ResourceImpl) Close() error { - if r.name == "" { - return fmt.Errorf("oops") - } - r.name = "" - return nil -} - -type _resourceView = resource.ResourceViewInt[Resource] - -// resourceView implementation the mapping of a ResourceImpl -// to a fully-fledged Resource implementation including -// the view-related part. -type resourceView struct { - _resourceView - _resourceImpl -} - -var _ Resource = (*resourceView)(nil) - -// Close must be implemented to resolve the two provided Close -// methods to the one of the view-related part. -func (r *resourceView) Close() error { - return r._resourceView.Close() -} - -func (r *resourceView) Operation(err error) error { - return r.Execute(func() error { return r._resourceImpl.Operation(err) }) -} - -func resourceViewCreator(impl *ResourceImpl, v resource.CloserView, d resource.ViewManager[Resource]) Resource { - return &resourceView{ - _resourceView: resource.NewView(v, d), - _resourceImpl: impl, - } -} - -// New create a new Resource by creating a ResourceImpl -// and adding the reference management by calling resource.NewResource, -// which internally will call the resourceViewCreator function to create -// the first view. -func New(name string) Resource { - i := &ResourceImpl{name: name} - return resource.NewResource(i, resourceViewCreator, name) -} - -var _ = Describe("ref test", func() { - It("handles main ref", func() { - r := New("alice") - - Expect(r.IsClosed()).To(BeFalse()) - - MustBeSuccessful(r.Close()) - Expect(r.IsClosed()).To(BeTrue()) - Expect(r.Close()).To(Equal(resource.ErrClosed)) - Expect(r.Name()).To(Equal("")) - }) - - It("handle last closed view", func() { - r := New("alice") - Expect(r.IsClosed()).To(BeFalse()) - v := Must(r.Dup()) - Expect(v.IsClosed()).To(BeFalse()) - - MustBeSuccessful(r.Close()) - Expect(r.IsClosed()).To(BeTrue()) - Expect(v.IsClosed()).To(BeFalse()) - - Expect(r.Close()).To(Equal(resource.ErrClosed)) - Expect(r.Name()).To(Equal("alice")) - - MustBeSuccessful(v.Close()) - Expect(v.IsClosed()).To(BeTrue()) - Expect(v.Close()).To(Equal(resource.ErrClosed)) - Expect(v.Name()).To(Equal("")) - }) - - It("executes operation", func() { - r := New("alice") - Expect(r.IsClosed()).To(BeFalse()) - - Expect(r.Operation(nil)).To(Succeed()) - Expect(r.Operation(fmt.Errorf("fail"))).To(MatchError("fail")) - MustBeSuccessful(r.Close()) - Expect(r.Operation(nil)).To(Equal(resource.ErrClosed)) - }) -}) diff --git a/pkg/regex/regex.go b/pkg/regex/regex.go deleted file mode 100644 index 180e6bbcd..000000000 --- a/pkg/regex/regex.go +++ /dev/null @@ -1,118 +0,0 @@ -package regex - -import ( - "fmt" - "regexp" - "strings" -) - -var ( - // Alpha defines the alpha atom. - // This only allows upper and lower case characters. - Alpha = Match(`[A-Za-z]+`) - - // Numeric defines the alpha atom. - // This only allows a non-empty sequence of digits. - Numeric = Match(`[0-9]+`) - - // AlphaNumeric defines the alpha numeric atom, typically a - // component of names. This only allows upper and lower case characters and digits. - AlphaNumeric = Match(`[A-Za-z0-9]+`) - - // Identifier is an AlphaNumeric regexp starting with an Alpha regexp. - Identifier = Sequence(Alpha, Match(`[A-Za-z0-9]`), Optional(Literal("+"), Alpha)) -) - -// Match compiles the string to a regular expression. -var Match = regexp.MustCompile - -// Literal compiles s into a literal regular expression, escaping any regexp -// reserved characters. -func Literal(s string) *regexp.Regexp { - re := Match(regexp.QuoteMeta(s)) - - if _, complete := re.LiteralPrefix(); !complete { - panic("must be a literal") - } - - return re -} - -const classBytes = `]\` - -func quoteCharClass(s string) string { - res := "" - for _, r := range s { - if strings.Contains(classBytes, string(r)) { - res += "\\" - } - res += string(r) - } - return res -} - -// CharSet compiles a set of matching charaters. -func CharSet(s string) *regexp.Regexp { - return Match("[" + quoteCharClass(s) + "]") -} - -// Sequence defines a full expression, where each regular expression must -// follow the previous. -func Sequence(res ...*regexp.Regexp) *regexp.Regexp { - var s string - for _, re := range res { - s += re.String() - } - - return Match(s) -} - -// Optional wraps the expression in a non-capturing group and makes the -// production optional. -func Optional(res ...*regexp.Regexp) *regexp.Regexp { - return Match(Group(res...).String() + `?`) -} - -// Repetition wraps the regexp in a non-capturing group to get a range of -// matches. -func Repetition(min, max int, res ...*regexp.Regexp) *regexp.Regexp { - return Match(Group(res...).String() + fmt.Sprintf(`{%d,%d}`, min, max)) -} - -// Repeated wraps the regexp in a non-capturing group to get one or more -// matches. -func Repeated(res ...*regexp.Regexp) *regexp.Regexp { - return Match(Group(res...).String() + `+`) -} - -// OptionalRepeated wraps the regexp in a non-capturing group to get any -// number of matches. -func OptionalRepeated(res ...*regexp.Regexp) *regexp.Regexp { - return Match(Group(res...).String() + `*`) -} - -// Group wraps the regexp in a non-capturing group. -func Group(res ...*regexp.Regexp) *regexp.Regexp { - return Match(`(?:` + Sequence(res...).String() + `)`) -} - -// Or wraps alternative regexps. -func Or(res ...*regexp.Regexp) *regexp.Regexp { - var s string - sep := "" - for _, re := range res { - s += sep + Group(re).String() - sep = "|" - } - return Match(`(?:` + s + `)`) -} - -// Capture wraps the expression in a capturing group. -func Capture(res ...*regexp.Regexp) *regexp.Regexp { - return Match(`(` + Sequence(res...).String() + `)`) -} - -// Anchored anchors the regular expression by adding start and end delimiters. -func Anchored(res ...*regexp.Regexp) *regexp.Regexp { - return Match(`^` + Sequence(res...).String() + `$`) -} diff --git a/pkg/registrations/info.go b/pkg/registrations/info.go deleted file mode 100644 index 601d52364..000000000 --- a/pkg/registrations/info.go +++ /dev/null @@ -1,58 +0,0 @@ -package registrations - -import ( - "strings" - - "github.com/mandelsoft/goutils/general" - - "github.com/open-component-model/ocm/pkg/listformat" -) - -type HandlerInfos []HandlerInfo - -var _ listformat.ListElements = HandlerInfos(nil) - -func (h HandlerInfos) Size() int { - return len(h) -} - -func (h HandlerInfos) Key(i int) string { - return h[i].Name -} - -func (h HandlerInfos) Description(i int) string { - var desc string - - if h[i].Node { - desc = "[" + general.Conditional(h[i].ShortDesc == "", "intermediate", strings.Trim(h[i].ShortDesc, "\n")) + "]" - } else { - desc = h[i].ShortDesc - } - return desc + general.Conditional(h[i].Description == "", "", "\n\n"+strings.Trim(h[i].Description, "\n")) -} - -type HandlerInfo struct { - Name string - ShortDesc string - Description string - Node bool -} - -func NewLeafHandlerInfo(short, desc string) HandlerInfos { - return HandlerInfos{ - { - ShortDesc: short, - Description: desc, - }, - } -} - -func NewNodeHandlerInfo(short, desc string) HandlerInfos { - return HandlerInfos{ - { - ShortDesc: short, - Description: desc, - Node: true, - }, - } -} diff --git a/pkg/registrations/utils.go b/pkg/registrations/utils.go deleted file mode 100644 index 0daf51ab1..000000000 --- a/pkg/registrations/utils.go +++ /dev/null @@ -1,94 +0,0 @@ -package registrations - -import ( - "encoding/json" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -type Decoder func(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) - -func DecodeDefaultedConfig[T any](config interface{}, d ...Decoder) (*T, error) { - if config == nil { - var cfg T - return &cfg, nil - } - return DecodeConfig[T](config, d...) -} - -func DecodeConfig[T any](config interface{}, d ...Decoder) (*T, error) { - var err error - - if config == nil { - return nil, nil - } - - var cfg *T - switch a := config.(type) { - case string: - cfg, err = decodeConfig[T]([]byte(a), d...) - case json.RawMessage: - cfg, err = decodeConfig[T](a, d...) - case []byte: - cfg, err = decodeConfig[T](a, d...) - case *T: - cfg = a - case T: - cfg = &a - default: - var data []byte - data, err = json.Marshal(a) - if err != nil { - return nil, err - } - cfg, err = decodeConfig[T](data, d...) - } - if err != nil { - return nil, errors.Wrapf(err, "cannot unmarshal config") - } - return cfg, nil -} - -func decodeConfig[T any](data []byte, dec ...Decoder) (*T, error) { - if d := utils.Optional(dec...); d != nil { - r, err := d(data, runtime.DefaultYAMLEncoding) - if err != nil { - return nil, err - } - if eff, ok := r.(*T); ok { - return eff, nil - } - return nil, errors.Newf("invalid decoded type %T ", r) - } - - var c T - err := runtime.DefaultYAMLEncoding.Unmarshal(data, &c) - if err != nil { - return nil, err - } - return &c, nil -} - -func DecodeAnyConfig(config interface{}) (json.RawMessage, error) { - var attr json.RawMessage - switch a := config.(type) { - case json.RawMessage: - attr = a - case []byte: - err := runtime.DefaultYAMLEncoding.Unmarshal(a, &attr) - if err != nil { - return nil, errors.Wrapf(err, "invalid target specification") - } - attr = a - default: - data, err := json.Marshal(config) - if err != nil { - return nil, errors.Wrapf(err, "invalid target specification") - } - attr = data - } - return attr, nil -} diff --git a/pkg/runtime/descriptivetype/options.go b/pkg/runtime/descriptivetype/options.go deleted file mode 100644 index 42a8c637b..000000000 --- a/pkg/runtime/descriptivetype/options.go +++ /dev/null @@ -1,65 +0,0 @@ -package descriptivetype - -import ( - "github.com/mandelsoft/goutils/optionutils" - - "github.com/open-component-model/ocm/pkg/runtime" -) - -//////////////////////////////////////////////////////////////////////////////// - -// TypeObjectTarget is used as target for option functions, it provides -// setters for fields, which should not be modifiable for a final type object. -type TypeObjectTarget[E runtime.VersionedTypedObject] struct { - target *TypedObjectTypeObject[E] -} - -func NewTypeObjectTarget[E runtime.VersionedTypedObject](target *TypedObjectTypeObject[E]) *TypeObjectTarget[E] { - return &TypeObjectTarget[E]{target} -} - -func (t *TypeObjectTarget[E]) SetDescription(value string) { - t.target.description = value -} - -func (t *TypeObjectTarget[E]) SetFormat(value string) { - t.target.format = value -} - -//////////////////////////////////////////////////////////////////////////////// -// Descriptive Type Options - -type OptionTarget interface { - SetFormat(string) - SetDescription(string) -} - -type Option = optionutils.Option[OptionTarget] - -//////////////////////////////////////////////////////////////////////////////// - -type formatOption struct { - value string -} - -func WithFormatSpec(value string) Option { - return formatOption{value} -} - -func (o formatOption) ApplyTo(t OptionTarget) { - t.SetFormat(o.value) -} - -//////////////////////////////////////////////////////////////////////////////// - -type descriptionOption struct { - value string -} - -func WithDescription(value string) Option { - return descriptionOption{value} -} - -func (o descriptionOption) ApplyTo(t OptionTarget) { - t.SetDescription(o.value) -} diff --git a/pkg/runtime/descriptivetype/type.go b/pkg/runtime/descriptivetype/type.go deleted file mode 100644 index a1771e2f3..000000000 --- a/pkg/runtime/descriptivetype/type.go +++ /dev/null @@ -1,180 +0,0 @@ -package descriptivetype - -import ( - "fmt" - "strings" - - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -// DescriptionExtender provides an additional descrition for a type object -// which is appended to the format description in the schmeme descrition -// for the type in question. -type DescriptionExtender[T any] func(t T) string - -// TypedObjectType is the appropriately extended type interface -// based on runtime.VersionTypedObjectType providing support for a functional and -// format description. -type TypedObjectType[T runtime.VersionedTypedObject] interface { - runtime.VersionedTypedObjectType[T] - - Description() string - Format() string -} - -//////////////////////////////////////////////////////////////////////////////// - -// TypeScheme is the appropriately extended scheme interface based on -// runtime.TypeScheme. Based on the additional type info a complete -// scheme description can be created calling the Describe method. -type TypeScheme[T runtime.VersionedTypedObject, R TypedObjectType[T]] interface { - runtime.TypeScheme[T, R] - - Describe() string -} - -type _typeScheme[T runtime.VersionedTypedObject, R TypedObjectType[T]] interface { - runtime.TypeScheme[T, R] // for goland to be able to accept extender argument type -} - -type typeScheme[T runtime.VersionedTypedObject, R TypedObjectType[T], S TypeScheme[T, R]] struct { - name string - extender DescriptionExtender[R] - _typeScheme[T, R] -} - -func MustNewDefaultTypeScheme[T runtime.VersionedTypedObject, R TypedObjectType[T], S TypeScheme[T, R]](name string, extender DescriptionExtender[R], unknown runtime.Unstructured, acceptUnknown bool, defaultdecoder runtime.TypedObjectDecoder[T], base ...TypeScheme[T, R]) TypeScheme[T, R] { - scheme := runtime.MustNewDefaultTypeScheme[T, R](unknown, acceptUnknown, defaultdecoder, utils.Optional(base...)) - return &typeScheme[T, R, S]{ - name: name, - extender: extender, - _typeScheme: scheme, - } -} - -// NewTypeScheme provides an TypeScheme implementation based on the interfaces -// and the default runtime.TypeScheme implementation. -func NewTypeScheme[T runtime.VersionedTypedObject, R TypedObjectType[T], S TypeScheme[T, R]](name string, extender DescriptionExtender[R], unknown runtime.Unstructured, acceptUnknown bool, base ...S) TypeScheme[T, R] { - scheme := runtime.MustNewDefaultTypeScheme[T, R](unknown, acceptUnknown, nil, utils.Optional(base...)) - return &typeScheme[T, R, S]{ - name: name, - extender: extender, - _typeScheme: scheme, - } -} - -func (t *typeScheme[T, R, S]) KnownTypes() runtime.KnownTypes[T, R] { - return t._typeScheme.KnownTypes() // Goland -} - -//////////////////////////////////////////////////////////////////////////////// - -func (t *typeScheme[T, R, S]) Describe() string { - s := "" - type method struct { - desc string - versions map[string]string - more string - } - - descs := map[string]*method{} - - // gather info for kinds and versions - for _, n := range t.KnownTypeNames() { - kind, vers := runtime.KindVersion(n) - - info := descs[kind] - if info == nil { - info = &method{versions: map[string]string{}} - descs[kind] = info - } - - if vers == "" { - vers = "v1" - } - if _, ok := info.versions[vers]; !ok { - info.versions[vers] = "" - } - - ty := t.GetType(n) - - if t.extender != nil { - more := t.extender(ty) - if more != "" { - info.more = more - } - } - desc := ty.Description() - if desc != "" { - info.desc = desc - } - - desc = ty.Format() - if desc != "" { - info.versions[vers] = desc - } - } - - for _, tn := range utils.StringMapKeys(descs) { - info := descs[tn] - desc := strings.Trim(info.desc, "\n") - if desc != "" { - s = fmt.Sprintf("%s\n- %s %s\n\n%s\n\n", s, t.name, tn, utils.IndentLines(desc, " ")) - - format := "" - for _, f := range utils.StringMapKeys(info.versions) { - desc = strings.Trim(info.versions[f], "\n") - if desc != "" { - format = fmt.Sprintf("%s\n- Version %s\n\n%s\n", format, f, utils.IndentLines(desc, " ")) - } - } - if format != "" { - s += fmt.Sprintf(" The following versions are supported:\n%s\n", strings.Trim(utils.IndentLines(format, " "), "\n")) - } - } - s += info.more - } - return s -} - -//////////////////////////////////////////////////////////////////////////////// - -type descriptiveTypeInfo interface { - Description() string - Format() string -} - -type TypedObjectTypeObject[T runtime.VersionedTypedObject] struct { - runtime.VersionedTypedObjectType[T] - description string - format string - validator func(T) error -} - -var _ descriptiveTypeInfo = (*TypedObjectTypeObject[runtime.VersionedTypedObject])(nil) - -func NewTypedObjectTypeObject[E runtime.VersionedTypedObject](vt runtime.VersionedTypedObjectType[E], opts ...Option) *TypedObjectTypeObject[E] { - t := NewTypeObjectTarget[E](&TypedObjectTypeObject[E]{ - VersionedTypedObjectType: vt, - }) - for _, o := range opts { - o.ApplyTo(t) - } - return t.target -} - -func (t *TypedObjectTypeObject[T]) Description() string { - return t.description -} - -func (t *TypedObjectTypeObject[T]) Format() string { - return t.format -} - -func (t *TypedObjectTypeObject[T]) Validate(e T) error { - if t.validator == nil { - return nil - } - return t.validator(e) -} diff --git a/pkg/runtime/object_test.go b/pkg/runtime/object_test.go deleted file mode 100644 index 1f70eaf2f..000000000 --- a/pkg/runtime/object_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package runtime_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/runtime" -) - -var _ = Describe("*** basic types", func() { - Context("type name", func() { - It("one arg", func() { - t := runtime.TypeName("test") - Expect(t).To(Equal("test")) - }) - It("two arg", func() { - t := runtime.TypeName("test", "v1") - Expect(t).To(Equal("test" + runtime.VersionSeparator + "v1")) - }) - It("two arg empty", func() { - t := runtime.TypeName("test", "") - Expect(t).To(Equal("test")) - }) - It("two arg", func() { - defer func() { - e := recover() - Expect(e).NotTo(BeNil()) - }() - runtime.TypeName("test", "v1", "v3") - Fail("no panic") - }) - }) - Context("object type", func() { - It("gets the type", func() { - t := runtime.NewObjectType("test") - Expect(t.GetType()).To(Equal("test")) - }) - It("sets the type", func() { - t := runtime.NewObjectType("test") - t.SetType("other") - Expect(t.GetType()).To(Equal("other")) - }) - }) - - Context("versioned object type", func() { - It("get type and version of unversioned type", func() { - t := runtime.NewVersionedTypedObject("test", "") - Expect(t.GetType()).To(Equal("test")) - Expect(t.GetKind()).To(Equal("test")) - Expect(t.GetVersion()).To(Equal("v1")) - }) - It("get type and version of versioned type", func() { - t := runtime.NewVersionedTypedObject("test", "v2") - Expect(t.GetType()).To(Equal(runtime.TypeName("test", "v2"))) - Expect(t.GetKind()).To(Equal("test")) - Expect(t.GetVersion()).To(Equal("v2")) - }) - - It("set type", func() { - t := runtime.NewVersionedTypedObject("test", "v2") - t.SetType(runtime.TypeName("other", "v3")) - Expect(t.GetType()).To(Equal(runtime.TypeName("other", "v3"))) - Expect(t.GetKind()).To(Equal("other")) - Expect(t.GetVersion()).To(Equal("v3")) - }) - - It("set kind on unversioned", func() { - t := runtime.NewVersionedTypedObject("test") - t.SetKind(runtime.TypeName("other")) - Expect(t.GetType()).To(Equal(runtime.TypeName("other"))) - Expect(t.GetKind()).To(Equal("other")) - Expect(t.GetVersion()).To(Equal("v1")) - }) - It("set version on unversioned", func() { - t := runtime.NewVersionedTypedObject("test") - t.SetVersion(runtime.TypeName("v3")) - Expect(t.GetType()).To(Equal(runtime.TypeName("test", "v3"))) - Expect(t.GetKind()).To(Equal("test")) - Expect(t.GetVersion()).To(Equal("v3")) - }) - - It("set kind on versioned", func() { - t := runtime.NewVersionedTypedObject("test", "v2") - t.SetKind(runtime.TypeName("other")) - Expect(t.GetType()).To(Equal(runtime.TypeName("other", "v2"))) - Expect(t.GetKind()).To(Equal("other")) - Expect(t.GetVersion()).To(Equal("v2")) - }) - It("set version on unversioned", func() { - t := runtime.NewVersionedTypedObject("test", "v2") - t.SetVersion(runtime.TypeName("v3")) - Expect(t.GetType()).To(Equal(runtime.TypeName("test", "v3"))) - Expect(t.GetKind()).To(Equal("test")) - Expect(t.GetVersion()).To(Equal("v3")) - }) - }) -}) diff --git a/pkg/runtime/scheme.go b/pkg/runtime/scheme.go deleted file mode 100644 index fbe1fb4f3..000000000 --- a/pkg/runtime/scheme.go +++ /dev/null @@ -1,469 +0,0 @@ -package runtime - -import ( - "encoding/json" - "fmt" - "reflect" - "sort" - "sync" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/general" - "github.com/mandelsoft/goutils/generics" - "github.com/modern-go/reflect2" - - "github.com/open-component-model/ocm/pkg/errkind" - "github.com/open-component-model/ocm/pkg/utils" -) - -var ( - typeTypedObject = reflect.TypeOf((*TypedObject)(nil)).Elem() - typeUnknown = reflect.TypeOf((*Unknown)(nil)).Elem() -) - -type ( - // TypedObjectDecoder is able to provide an effective typed object for some - // serilaized form. The technical deserialization is done by an Unmarshaler. - TypedObjectDecoder[T TypedObject] interface { - Decode(data []byte, unmarshaler Unmarshaler) (T, error) - } - _TypedObjectDecoder[T TypedObject] interface { - TypedObjectDecoder[T] - } -) - -// TypedObjectEncoder is able to provide a versioned representation of -// an effective TypedObject. -type TypedObjectEncoder[T TypedObject] interface { - Encode(T, Marshaler) ([]byte, error) -} - -type DirectDecoder[T TypedObject] struct { - proto reflect.Type -} - -var _ TypedObjectDecoder[TypedObject] = &DirectDecoder[TypedObject]{} - -func MustNewDirectDecoder[T TypedObject](proto T) *DirectDecoder[T] { - d, err := NewDirectDecoder[T](proto) - if err != nil { - panic(err) - } - return d -} - -func NewDirectDecoder[T TypedObject](proto T) (*DirectDecoder[T], error) { - t := MustProtoType(proto) - if !reflect.PtrTo(t).Implements(typeTypedObject) { - return nil, errors.Newf("object interface %T: must implement TypedObject", proto) - } - if t.Kind() != reflect.Struct { - return nil, errors.Newf("prototype %q must be a struct", t) - } - return &DirectDecoder[T]{ - proto: t, - }, nil -} - -func (d *DirectDecoder[T]) CreateInstance() T { - return reflect.New(d.proto).Interface().(T) -} - -func (d *DirectDecoder[T]) Decode(data []byte, unmarshaler Unmarshaler) (T, error) { - var zero T - inst := d.CreateInstance() - err := unmarshaler.Unmarshal(data, inst) - if err != nil { - return zero, err - } - - return inst, nil -} - -func (d *DirectDecoder[T]) Encode(obj T, marshaler Marshaler) ([]byte, error) { - return marshaler.Marshal(obj) -} - -// KnownTypes is a set of known type names mapped to appropriate object decoders. -type KnownTypes[T TypedObject, R TypedObjectDecoder[T]] map[string]R - -// Copy provides a copy of the actually known types. -func (t KnownTypes[T, R]) Copy() KnownTypes[T, R] { - n := KnownTypes[T, R]{} - for k, v := range t { - n[k] = v - } - return n -} - -// TypeNames return a sorted list of known type names. -func (t KnownTypes[T, R]) TypeNames() []string { - types := make([]string, 0, len(t)) - for t := range t { - types = append(types, t) - } - sort.Strings(types) - return types -} - -// Unknown is the interface to be implemented by -// representations on an unknown, but nevertheless decoded specification -// of a typed object. -type Unknown interface { - IsUnknown() bool -} - -func IsUnknown(o TypedObject) bool { - if reflect2.IsNil(o) { - return true - } - if u, ok := o.(Unknown); ok { - return u.IsUnknown() - } - return false -} - -type ( - // Scheme is the interface to describe a set of object types - // that implement a dedicated interface. - // As such it knows about the desired interface of the instances - // and can validate it. Additionally, it provides an implementation - // for generic unstructured objects that can be used to decode - // any serialized from of object candidates and provide the - // effective type. - Scheme[T TypedObject, R TypedObjectDecoder[T]] interface { - SchemeCommon - KnownTypesProvider[T, R] - TypedObjectEncoder[T] - TypedObjectDecoder[T] - - BaseScheme() Scheme[T, R] // Go does not support an additional type parameter S Scheme[T,S] to return the correct type here - - AddKnownTypes(scheme KnownTypesProvider[T, R]) - RegisterByDecoder(typ string, decoder R) error - - ValidateInterface(object T) error - CreateUnstructured() T - Convert(object TypedObject) (T, error) - GetDecoder(otype string) R - EnforceDecode(data []byte, unmarshaler Unmarshaler) (T, error) - } - _Scheme[T TypedObject, R TypedObjectDecoder[T]] interface { // cannot omit nesting, because Goland does not accept it - Scheme[T, R] - } -) - -type KnownTypesProvider[T TypedObject, R TypedObjectDecoder[T]] interface { - KnownTypes() KnownTypes[T, R] -} - -type SchemeCommon interface { - KnownTypeNames() []string -} - -type defaultScheme[T TypedObject, R TypedObjectDecoder[T]] struct { - lock sync.RWMutex - base Scheme[T, R] - instance reflect.Type - unstructured reflect.Type - defaultdecoder TypedObjectDecoder[T] - acceptUnknown bool - types KnownTypes[T, R] -} - -var _ Scheme[VersionedTypedObject, TypedObjectDecoder[VersionedTypedObject]] = (*defaultScheme[VersionedTypedObject, TypedObjectDecoder[VersionedTypedObject]])(nil) - -func MustNewDefaultScheme[T TypedObject, R TypedObjectDecoder[T]](protoUnstr Unstructured, acceptUnknown bool, defaultdecoder TypedObjectDecoder[T], base ...Scheme[T, R]) Scheme[T, R] { - return utils.Must(NewDefaultScheme[T](protoUnstr, acceptUnknown, defaultdecoder, base...)) -} - -func NewScheme[T TypedObject, R TypedObjectDecoder[T]](base ...Scheme[T, R]) Scheme[T, R] { - s, _ := NewDefaultScheme[T](nil, false, nil, base...) - return s -} - -func NewDefaultScheme[T TypedObject, R TypedObjectDecoder[T]](protoUnstr Unstructured, acceptUnknown bool, defaultdecoder TypedObjectDecoder[T], base ...Scheme[T, R]) (Scheme[T, R], error) { - var err error - - var protoIfce T - it := reflect.TypeOf(&protoIfce) - for it.Kind() == reflect.Ptr { - it = it.Elem() - } - - var ut reflect.Type - if acceptUnknown { - ut, err = ProtoType(protoUnstr) - if err != nil { - return nil, errors.Wrapf(err, "unstructured prototype %T", protoUnstr) - } - if !reflect.PtrTo(ut).Implements(it) { - return nil, fmt.Errorf("unstructured type %T must implement %T to be acceptale as unknown result", protoUnstr, &protoIfce) - } - if !reflect.PtrTo(ut).Implements(typeUnknown) { - return nil, fmt.Errorf("unstructured type %T must implement Unknown to be acceptable as unknown result", protoUnstr) - } - } - - return &defaultScheme[T, R]{ - base: utils.Optional(base...), - instance: it, - unstructured: ut, - defaultdecoder: defaultdecoder, - types: KnownTypes[T, R]{}, - acceptUnknown: acceptUnknown, - }, nil -} - -func (d *defaultScheme[T, R]) BaseScheme() Scheme[T, R] { - return d.base -} - -func (d *defaultScheme[T, R]) AddKnownTypes(s KnownTypesProvider[T, R]) { - d.lock.Lock() - defer d.lock.Unlock() - for k, v := range s.KnownTypes() { - d.types[k] = v - } -} - -func (d *defaultScheme[T, R]) KnownTypes() KnownTypes[T, R] { - d.lock.RLock() - defer d.lock.RUnlock() - if d.base == nil { - return d.types.Copy() - } - kt := d.base.KnownTypes() - for n, t := range d.types { - kt[n] = t - } - return kt -} - -// KnownTypeNames return a sorted list of known type names. -func (d *defaultScheme[T, R]) KnownTypeNames() []string { - d.lock.RLock() - defer d.lock.RUnlock() - - types := make([]string, 0, len(d.types)) - for t := range d.types { - types = append(types, t) - } - if d.base != nil { - types = append(types, d.base.KnownTypeNames()...) - } - sort.Strings(types) - return types -} - -func (d *defaultScheme[T, R]) RegisterByDecoder(typ string, decoder R) error { - if reflect2.IsNil(decoder) { - return errors.Newf("decoder must be given") - } - d.lock.Lock() - defer d.lock.Unlock() - d.types[typ] = decoder - return nil -} - -func (d *defaultScheme[T, R]) ValidateInterface(object T) error { - t := reflect.TypeOf(object) - if !t.Implements(d.instance) { - return errors.Newf("object type %q does not implement required instance interface %q", t, d.instance) - } - return nil -} - -func (d *defaultScheme[T, R]) GetDecoder(typ string) R { - d.lock.RLock() - defer d.lock.RUnlock() - decoder := d.types[typ] - if reflect2.IsNil(decoder) && d.base != nil { - decoder = d.base.GetDecoder(typ) - } - return decoder -} - -func (d *defaultScheme[T, R]) CreateUnstructured() T { - var _nil T - if d.unstructured == nil { - return _nil - } - return reflect.New(d.unstructured).Interface().(T) -} - -func (d *defaultScheme[T, R]) Encode(obj T, marshaler Marshaler) ([]byte, error) { - if marshaler == nil { - marshaler = DefaultYAMLEncoding - } - decoder := d.GetDecoder(obj.GetType()) - if encoder, ok := generics.TryCast[TypedObjectEncoder[T]](decoder); ok { - return encoder.Encode(obj, marshaler) - } - return marshaler.Marshal(obj) -} - -func (d *defaultScheme[T, R]) Decode(data []byte, unmarshal Unmarshaler) (T, error) { - var _nil T - - var to TypedObject - un := d.CreateUnstructured() - if reflect2.IsNil(un) { - to = &UnstructuredTypedObject{} - } else { - to = un - } - if unmarshal == nil { - unmarshal = DefaultYAMLEncoding - } - err := unmarshal.Unmarshal(data, to) - if err != nil { - return _nil, errors.Wrapf(err, "cannot unmarshal unstructured") - } - if to.GetType() == "" { - return _nil, errors.Newf("no type found") - } - decoder := d.GetDecoder(to.GetType()) - if reflect2.IsNil(decoder) { - if d.defaultdecoder != nil { - o, err := d.defaultdecoder.Decode(data, unmarshal) - if err == nil { - if !reflect2.IsNil(o) { - return o, nil - } - } else if !errors.IsErrUnknownKind(err, errkind.KIND_OBJECTTYPE) { - return _nil, err - } - } - if d.acceptUnknown { - return un, nil - } - return _nil, errors.ErrUnknown(errkind.KIND_OBJECTTYPE, to.GetType()) - } - return decoder.Decode(data, unmarshal) -} - -func (d *defaultScheme[T, R]) EnforceDecode(data []byte, unmarshal Unmarshaler) (T, error) { - var _nil T - - un := d.CreateUnstructured() - if unmarshal == nil { - unmarshal = DefaultYAMLEncoding.Unmarshaler - } - err := unmarshal.Unmarshal(data, un) - if err != nil { - return _nil, errors.Wrapf(err, "cannot unmarshal unstructured") - } - if un.GetType() == "" { - if d.acceptUnknown { - return un, nil - } - return un, errors.Newf("no type found") - } - decoder := d.GetDecoder(un.GetType()) - if reflect2.IsNil(decoder) { - if d.defaultdecoder != nil { - o, err := d.defaultdecoder.Decode(data, unmarshal) - if err == nil { - return o, nil - } - if !errors.IsErrUnknownKind(err, errkind.KIND_OBJECTTYPE) { - return un, err - } - } - if d.acceptUnknown { - return un, nil - } - return un, errors.ErrUnknown(errkind.KIND_OBJECTTYPE, un.GetType()) - } - o, err := decoder.Decode(data, unmarshal) - if err != nil { - return un, err - } - return o, err -} - -func (d *defaultScheme[T, R]) Convert(o TypedObject) (T, error) { - var _nil T - - if o.GetType() == "" { - return _nil, errors.Newf("no type found") - } - - if u, ok := o.(T); ok { - return u, nil - } - - if u, ok := o.(Unstructured); ok { - raw, err := u.GetRaw() - if err != nil { - return _nil, err - } - return d.Decode(raw, DefaultJSONEncoding) - } - - data, err := json.Marshal(o) - if err != nil { - return _nil, err - } - decoder := d.GetDecoder(o.GetType()) - if reflect2.IsNil(decoder) { - if d.defaultdecoder != nil { - object, err := d.defaultdecoder.Decode(data, DefaultJSONEncoding) - if err == nil { - return object, nil - } - if !errors.IsErrUnknownKind(err, errkind.KIND_OBJECTTYPE) { - return _nil, err - } - } - return _nil, errors.ErrUnknown(errkind.KIND_OBJECTTYPE, o.GetType()) - } - r, err := decoder.Decode(data, DefaultJSONEncoding) - if err != nil { - return _nil, err - } - if reflect.TypeOf(r) == reflect.TypeOf(o) { - return o.(T), nil - } - return r, nil -} - -//////////////////////////////////////////////////////////////////////////////// - -// TypeScheme is a scheme based on Types instead of decoders. -type TypeScheme[T TypedObject, R TypedObjectType[T]] interface { - Scheme[T, R] - - Register(typ R) - GetType(name string) R -} - -type defaultTypeScheme[T TypedObject, R TypedObjectType[T]] struct { - _Scheme[T, R] -} - -func MustNewDefaultTypeScheme[T TypedObject, R TypedObjectType[T]](protoUnstr Unstructured, acceptUnknown bool, defaultdecoder TypedObjectDecoder[T], base ...TypeScheme[T, R]) TypeScheme[T, R] { - return utils.Must(NewDefaultTypeScheme[T, R](protoUnstr, acceptUnknown, defaultdecoder, base...)) -} - -func NewTypeScheme[T TypedObject, R TypedObjectType[T]](base ...TypeScheme[T, R]) TypeScheme[T, R] { - s, _ := NewDefaultTypeScheme[T](nil, false, nil, base...) - return s -} - -func NewDefaultTypeScheme[T TypedObject, R TypedObjectType[T]](protoUnstr Unstructured, acceptUnknown bool, defaultdecoder TypedObjectDecoder[T], base ...TypeScheme[T, R]) (TypeScheme[T, R], error) { - s, err := NewDefaultScheme[T](protoUnstr, acceptUnknown, defaultdecoder, generics.Cast[Scheme[T, R]](general.Optional(base...))) - if err != nil { - return nil, err - } - return &defaultTypeScheme[T, R]{s}, nil -} - -func (s *defaultTypeScheme[T, R]) Register(t R) { - s.RegisterByDecoder(t.GetType(), t) -} - -func (s *defaultTypeScheme[T, R]) GetType(name string) R { - return generics.Cast[R](s.GetDecoder(name)) -} diff --git a/pkg/runtime/version_test.go b/pkg/runtime/version_test.go deleted file mode 100644 index 3f29a6217..000000000 --- a/pkg/runtime/version_test.go +++ /dev/null @@ -1,170 +0,0 @@ -package runtime_test - -import ( - "encoding/json" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - Type1 = "testType1" - Type1V1 = Type1 + "/v1" - Type1V2 = Type1 + "/v2" - - Type2 = "testType2" - Type2V1 = Type2 + "/v1" -) - -var versions runtime.Scheme[TestSpecRealm, TestType] - -func init() { - versions = runtime.MustNewDefaultScheme[TestSpecRealm, TestType](nil, false, nil) - - versions.RegisterByDecoder(Type1, runtime.NewVersionedTypedObjectTypeByConverter[TestSpecRealm, *TestSpec1, *Spec1V1](Type1, &converterSpec1V1{})) - versions.RegisterByDecoder(Type1V1, runtime.NewVersionedTypedObjectTypeByConverter[TestSpecRealm, *TestSpec1, *Spec1V1](Type1V1, &converterSpec1V1{})) - - versions.RegisterByDecoder(Type2, runtime.NewVersionedTypedObjectType[TestSpecRealm, *TestSpec2](Type2)) - versions.RegisterByDecoder(Type2V1, runtime.NewVersionedTypedObjectType[TestSpecRealm, *TestSpec2](Type2V1)) -} - -type TestType runtime.TypedObjectDecoder[TestSpecRealm] - -// TestSpec is the realm. -type TestSpecRealm interface { - runtime.VersionedTypedObject - TestFunction() -} - -// TestSpec1 is a first implementation of the realm TestSpec. -// It is used as internal version. -type TestSpec1 struct { - runtime.InternalVersionedTypedObject[TestSpecRealm] - Field string `json:"field"` -} - -func (a TestSpec1) MarshalJSON() ([]byte, error) { - return runtime.MarshalVersionedTypedObject(&a) -} - -func (a *TestSpec2) TestFunction() {} - -func NewTestSpec1(field string) *TestSpec1 { - return &TestSpec1{ - InternalVersionedTypedObject: runtime.NewInternalVersionedTypedObject[TestSpecRealm](versions, Type1), - Field: field, - } -} - -// Spec1V1 is an old v1 version of a TestSpec1. -type Spec1V1 struct { - runtime.ObjectVersionedType - OldField string `json:"oldField"` -} - -type converterSpec1V1 struct{} - -var _ runtime.Converter[*TestSpec1, *Spec1V1] = (*converterSpec1V1)(nil) - -func (_ converterSpec1V1) ConvertFrom(in *TestSpec1) (*Spec1V1, error) { - return &Spec1V1{ - ObjectVersionedType: runtime.NewVersionedObjectType(in.Type), - OldField: in.Field, - }, nil -} - -func (_ converterSpec1V1) ConvertTo(in *Spec1V1) (*TestSpec1, error) { - return &TestSpec1{ - InternalVersionedTypedObject: runtime.NewInternalVersionedTypedObject[TestSpecRealm](versions, in.Type), - Field: in.OldField, - }, nil -} - -// Spec1V2 is an old v1 version of a TestSpec1. -type Spec1V2 struct { - runtime.ObjectVersionedType - Field string `json:"field"` -} - -type converterSpec1V2 struct{} - -var _ runtime.Converter[*TestSpec1, *Spec1V2] = (*converterSpec1V2)(nil) - -func (_ converterSpec1V2) ConvertFrom(in *TestSpec1) (*Spec1V2, error) { - return &Spec1V2{ - ObjectVersionedType: runtime.NewVersionedObjectType(in.Type), - Field: in.Field, - }, nil -} - -func (_ converterSpec1V2) ConvertTo(in *Spec1V2) (*TestSpec1, error) { - return &TestSpec1{ - InternalVersionedTypedObject: runtime.NewInternalVersionedTypedObject[TestSpecRealm](versions, in.Type), - Field: in.Field, - }, nil -} - -//////////////////////////////////////////////////////////////////////////////// - -// TestSpec2 is a second implementation of the realm TestSpec. -// It is used a internal version and v2. -type TestSpec2 struct { - runtime.VersionedObjectType - Field string `json:"field"` -} - -func (a *TestSpec1) TestFunction() {} - -func NewTestSpec2(field string) *TestSpec2 { - return &TestSpec2{ - VersionedObjectType: runtime.VersionedObjectType{Type2}, - Field: field, - } -} - -//////////////////////////////////////////////////////////////////////////////// - -type encoder interface { - getEncoder() int -} - -type object struct{} - -func (_ *object) getEncoder() int { - return 1 -} - -var _ = Describe("versioned types", func() { - var versions runtime.Scheme[TestSpecRealm, TestType] - - versions = runtime.MustNewDefaultScheme[TestSpecRealm, TestType](nil, false, nil) - - versions.RegisterByDecoder(Type1, runtime.NewVersionedTypedObjectTypeByConverter[TestSpecRealm, *TestSpec1, *Spec1V1](Type1, &converterSpec1V1{})) - versions.RegisterByDecoder(Type1V1, runtime.NewVersionedTypedObjectTypeByConverter[TestSpecRealm, *TestSpec1, *Spec1V1](Type1V1, &converterSpec1V1{})) - - versions.RegisterByDecoder(Type2, runtime.NewVersionedTypedObjectType[TestSpecRealm, *TestSpec2](Type2)) - versions.RegisterByDecoder(Type2V1, runtime.NewVersionedTypedObjectType[TestSpecRealm, *TestSpec2](Type2V1)) - - It("marshals version for TestSpec1", func() { - s1 := NewTestSpec1("value") - - data := Must(json.Marshal(s1)) - Expect(string(data)).To(StringEqualWithContext(`{"type":"testType1","oldField":"value"}`)) - - spec := Must(versions.Decode(data, runtime.DefaultJSONEncoding)) - Expect(spec).To(Equal(s1)) - }) - - It("unmarshal version for TestSpec2", func() { - s1 := NewTestSpec2("value") - - data := Must(json.Marshal(s1)) - Expect(string(data)).To(StringEqualWithContext(`{"type":"testType2","field":"value"}`)) - - spec := Must(versions.Decode(data, runtime.DefaultJSONEncoding)) - Expect(spec).To(Equal(s1)) - }) -}) diff --git a/pkg/runtimefinalizer/object_test.go b/pkg/runtimefinalizer/object_test.go deleted file mode 100644 index 9e3cdc09d..000000000 --- a/pkg/runtimefinalizer/object_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package runtimefinalizer_test - -import ( - "fmt" - "runtime" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/runtimefinalizer" -) - -type ObjectType struct { - kind string - id runtimefinalizer.ObjectIdentity - fi *runtimefinalizer.RuntimeFinalizer -} - -func NewOType(kind string, r *runtimefinalizer.RuntimeFinalizationRecoder) *ObjectType { - id := runtimefinalizer.NewObjectIdentity(kind) - o := &ObjectType{ - kind: kind, - id: id, - fi: runtimefinalizer.NewRuntimeFinalizer(id, r), - } - return o -} - -func (o *ObjectType) Id() runtimefinalizer.ObjectIdentity { - return o.id -} - -var _ = Describe("runtime finalizer", func() { - It("finalize with arbitrary method", func() { - r := &runtimefinalizer.RuntimeFinalizationRecoder{} - - o1 := NewOType("test1", r) - o2 := NewOType("test1", r) - - id1 := o1.Id() - id2 := o2.Id() - - runtime.GC() - time.Sleep(time.Second) - - fmt.Printf("still used (%s,%s)\n", o1.Id(), o2.Id()) - Expect(len(r.Get())).To(Equal(0)) - - o1 = nil - runtime.GC() - time.Sleep(time.Second) - fmt.Printf("still used (%s)\n", o2.Id()) - Expect(r.Get()).To(Equal([]runtimefinalizer.ObjectIdentity{id1})) - - o2 = nil - runtime.GC() - time.Sleep(time.Second) - Expect(r.Get()).To(Equal([]runtimefinalizer.ObjectIdentity{id1, id2})) - }) -}) diff --git a/pkg/signing/deprecated.go b/pkg/signing/deprecated.go deleted file mode 100644 index 5972ebb8a..000000000 --- a/pkg/signing/deprecated.go +++ /dev/null @@ -1,53 +0,0 @@ -package signing - -import ( - "crypto/x509" - "crypto/x509/pkix" - "time" - - parse "github.com/mandelsoft/spiff/dynaml/x509" - - "github.com/open-component-model/ocm/pkg/signing/signutils" -) - -// Deprecated: use signutils.GetCertificate. -func GetCertificate(in interface{}) (*x509.Certificate, error) { - c, _, err := signutils.GetCertificate(in, false) - return c, err -} - -// Deprecated: use signutils.ParsePublicKey. -func ParsePublicKey(data []byte) (interface{}, error) { - return parse.ParsePublicKey(string(data)) -} - -// Deprecated: use signutils.ParsePrivateKey. -func ParsePrivateKey(data []byte) (interface{}, error) { - return parse.ParsePrivateKey(string(data)) -} - -// Deprecated: use signutils.SystemCertPool. -func BaseRootPool() (*x509.CertPool, error) { - return signutils.SystemCertPool() -} - -// Deprecated: use signutils.CreateCertificate. -func CreateCertificate(subject pkix.Name, validFrom *time.Time, - validity time.Duration, pub interface{}, - ca *x509.Certificate, priv interface{}, isCA bool, names ...string, -) ([]byte, error) { - spec := &signutils.Specification{ - RootCAs: ca, - IsCA: isCA, - PublicKey: pub, - CAPrivateKey: priv, - CAChain: ca, - Subject: subject, - Usages: signutils.Usages{x509.ExtKeyUsageCodeSigning}, - Validity: validity, - NotBefore: validFrom, - Hosts: names, - } - _, data, err := signutils.CreateCertificate(spec) - return data, err -} diff --git a/pkg/signing/encrypt.go b/pkg/signing/encrypt.go deleted file mode 100644 index d6da62cd7..000000000 --- a/pkg/signing/encrypt.go +++ /dev/null @@ -1,59 +0,0 @@ -package signing - -import ( - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/encrypt" -) - -const DECRYPTION_PREFIX = "decrypt:" - -const KIND_DECRYPTION_KEY = "decryption key" - -func DecryptionKeyName(name string) string { - return DECRYPTION_PREFIX + name -} - -func ResolvePrivateKey(reg KeyRegistryFuncs, name string) (interface{}, error) { - key := reg.GetPrivateKey(name) - if key == nil { - return nil, nil - } - - data, ok := key.([]byte) - if !ok { - if str, ok := key.(string); ok { - data = []byte(str) - } - } - if data == nil { - return key, nil - } - - data, algo := encrypt.GetEncyptedData(data) - if data == nil { - return key, nil - } - - encryptionKey, err := ResolvePrivateKey(reg, DecryptionKeyName(name)) - if err != nil { - return nil, err - } - - if encryptionKey == nil { - return nil, errors.ErrNotFound(KIND_DECRYPTION_KEY, DecryptionKeyName(name)) - } - var keyData []byte - if raw, ok := encryptionKey.([]byte); ok { - keyData, err = encrypt.KeyFromPem(raw) - if err != nil { - keyData = raw - } - } else { - return nil, errors.ErrInvalid(KIND_DECRYPTION_KEY, DecryptionKeyName(name)) - } - if err := algo.CheckKey(keyData); err != nil { - return nil, err - } - return encrypt.Decrypt(keyData, data) -} diff --git a/pkg/signing/handlers/init.go b/pkg/signing/handlers/init.go deleted file mode 100644 index e9042f9a7..000000000 --- a/pkg/signing/handlers/init.go +++ /dev/null @@ -1,10 +0,0 @@ -package handlers - -import ( - _ "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" - _ "github.com/open-component-model/ocm/pkg/signing/handlers/rsa-pss" - _ "github.com/open-component-model/ocm/pkg/signing/handlers/rsa-pss-signingservice" - _ "github.com/open-component-model/ocm/pkg/signing/handlers/rsa-signingservice" - _ "github.com/open-component-model/ocm/pkg/signing/handlers/sigstore" - _ "github.com/sigstore/cosign/v2/pkg/providers/all" -) diff --git a/pkg/signing/handlers/rsa-pss-signingservice/handler.go b/pkg/signing/handlers/rsa-pss-signingservice/handler.go deleted file mode 100644 index 0225ad257..000000000 --- a/pkg/signing/handlers/rsa-pss-signingservice/handler.go +++ /dev/null @@ -1,30 +0,0 @@ -package rsa_pss_signingservice - -import ( - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa-pss" - rsa_signingservice "github.com/open-component-model/ocm/pkg/signing/handlers/rsa-signingservice" -) - -// Algorithm defines the type for the RSA PKCS #1 v1.5 signature algorithm. -const ( - Algorithm = rsa_pss.Algorithm - Name = "rsapss-signingservice" -) - -// SignaturePEMBlockAlgorithmHeader defines the header in a signature pem block where the signature algorithm is defined. -const SignaturePEMBlockAlgorithmHeader = rsa_signingservice.SignaturePEMBlockAlgorithmHeader - -func init() { - signing.DefaultHandlerRegistry().RegisterSigner(Name, NewHandler()) -} - -func NewHandler() signing.Signer { - return rsa_signingservice.NewHandlerFor(Algorithm) -} - -type Key = rsa_signingservice.Key - -func PrivateKey(k interface{}) (*Key, error) { - return rsa_signingservice.PrivateKey(k) -} diff --git a/pkg/signing/handlers/rsa-pss/handler.go b/pkg/signing/handlers/rsa-pss/handler.go deleted file mode 100644 index a4e6ddd0d..000000000 --- a/pkg/signing/handlers/rsa-pss/handler.go +++ /dev/null @@ -1,43 +0,0 @@ -package rsa_pss - -import ( - "crypto" - "crypto/rsa" - "io" - - "github.com/open-component-model/ocm/pkg/signing" - rsahandler "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" - "github.com/open-component-model/ocm/pkg/signing/signutils" -) - -// Algorithm defines the type for the RSA PKCS #1 v1.5 signature algorithm. -const Algorithm = "RSASSA-PSS" - -// MediaType defines the media type for a plain RSA-PSS signature. -const MediaType = "application/vnd.ocm.signature.rsa.pss" - -// MediaTypePEM is used if the signature contains the public key certificate chain. -const MediaTypePEM = signutils.MediaTypePEM - -func init() { - signing.DefaultHandlerRegistry().RegisterSigner(Algorithm, NewHandler()) -} - -func NewHandler() signing.SignatureHandler { - return rsahandler.NewHandlerFor(RSASSA_PSS) -} - -var RSASSA_PSS = &rsahandler.Method{ - Algorithm: Algorithm, - MediaType: MediaType, - Sign: sign, - Verify: verify, -} - -func sign(rand io.Reader, priv *rsa.PrivateKey, hash crypto.Hash, digest []byte) ([]byte, error) { - return rsa.SignPSS(rand, priv, hash, digest, nil) -} - -func verify(pub *rsa.PublicKey, hash crypto.Hash, digest []byte, sig []byte) error { - return rsa.VerifyPSS(pub, hash, digest, sig, nil) -} diff --git a/pkg/signing/handlers/rsa-signingservice/credentials.go b/pkg/signing/handlers/rsa-signingservice/credentials.go deleted file mode 100644 index 5faf0ab93..000000000 --- a/pkg/signing/handlers/rsa-signingservice/credentials.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2022 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the LICENSE file -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package rsa_signingservice - -import ( - "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/listformat" -) - -const ( - CONSUMER_TYPE = "Signingserver.gardener.cloud" - - ID_HOSTNAME = hostpath.ID_HOSTNAME - ID_PORT = hostpath.ID_PORT - ID_PATHPREFIX = hostpath.ID_PATHPREFIX - ID_SCHEME = hostpath.ID_SCHEME - - ATTR_CLIENT_CERT = "clientCert" - ATTR_PRIVATE_KEY = "privateKey" - ATTR_CA_CERTS = "caCerts" -) - -var identityMatcher = hostpath.IdentityMatcher(CONSUMER_TYPE) - -func init() { - attrs := listformat.FormatListElements("", listformat.StringElementDescriptionList{ - ATTR_CLIENT_CERT, "client certificate for authentication", - ATTR_PRIVATE_KEY, "private key for client certificate", - ATTR_CA_CERTS, "root certificate for signing server", - }) - ids := listformat.FormatListElements("", listformat.StringElementDescriptionList{ - ID_HOSTNAME, "signing server host", - ID_SCHEME, "(optional) URL scheme", - ID_PORT, "(optional) server port", - ID_PATHPREFIX, "path prefix for the server URL", - }) - cpi.RegisterStandardIdentity(CONSUMER_TYPE, identityMatcher, - `signing service credential matcher - -This matcher matches credentials for a Signing Service instance. -It uses the following identity attributes: -`+ids, - attrs) -} diff --git a/pkg/signing/handlers/rsa-signingservice/handler.go b/pkg/signing/handlers/rsa-signingservice/handler.go deleted file mode 100644 index 8e2ae87c6..000000000 --- a/pkg/signing/handlers/rsa-signingservice/handler.go +++ /dev/null @@ -1,75 +0,0 @@ -package rsa_signingservice - -import ( - "fmt" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" -) - -// Algorithm defines the type for the RSA PKCS #1 v1.5 signature algorithm. -const ( - Algorithm = rsa.Algorithm - Name = "rsa-signingservice" -) - -type Key struct { - URL string `json:"url"` -} - -// SignaturePEMBlockAlgorithmHeader defines the header in a signature pem block where the signature algorithm is defined. -const SignaturePEMBlockAlgorithmHeader = "Algorithm" - -func init() { - signing.DefaultHandlerRegistry().RegisterSigner(Name, NewHandler()) -} - -// Handler is a signatures.Signer compatible struct to sign with RSASSA-PKCS1-V1_5. -// using a signature service. -type Handler struct { - algo string -} - -func NewHandlerFor(algo string) signing.Signer { - return &Handler{algo} -} - -func NewHandler() signing.Signer { - return &Handler{Algorithm} -} - -func (h *Handler) Algorithm() string { - return h.algo -} - -func (h *Handler) Sign(cctx credentials.Context, digest string, sctx signing.SigningContext) (signature *signing.Signature, err error) { - privateKey, err := PrivateKey(sctx.GetPrivateKey()) - if err != nil { - return nil, errors.Wrapf(err, "invalid signing server access configuration") - } - server, err := NewSigningClient(privateKey.URL) - if err != nil { - return nil, err - } - return server.Sign(cctx, h.Algorithm(), sctx.GetHash(), digest, sctx) -} - -func PrivateKey(k interface{}) (*Key, error) { - switch t := k.(type) { - case *Key: - return t, nil - case []byte: - key := &Key{} - err := runtime.DefaultYAMLEncoding.Unmarshal(t, key) - if err != nil { - return nil, err - } - return key, err - default: - return nil, fmt.Errorf("unknown key specification %T", k) - } -} diff --git a/pkg/signing/handlers/rsa/handler.go b/pkg/signing/handlers/rsa/handler.go deleted file mode 100644 index 6b925096a..000000000 --- a/pkg/signing/handlers/rsa/handler.go +++ /dev/null @@ -1,182 +0,0 @@ -package rsa - -import ( - "crypto" - "crypto/rand" - "crypto/rsa" - "encoding/hex" - "fmt" - "io" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/signutils" -) - -// Algorithm defines the type for the RSA PKCS #1 v1.5 signature algorithm. -const Algorithm = "RSASSA-PKCS1-V1_5" - -// MediaType defines the media type for a plain RSA signature. -const MediaType = "application/vnd.ocm.signature.rsa" - -// MediaTypePEM is used if the signature contains the public key certificate chain. -const MediaTypePEM = signutils.MediaTypePEM - -func init() { - signing.DefaultHandlerRegistry().RegisterSigner(Algorithm, NewHandler()) -} - -type ( - PrivateKey = rsa.PrivateKey - PublicKey = rsa.PublicKey -) - -type Method struct { - Algorithm string - MediaType string - Sign func(random io.Reader, priv *PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) - Verify func(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte) error -} - -// Handler is a signatures.Signer compatible struct to sign with RSASSA-PKCS1-V1_5. -// and a signatures.Verifier compatible struct to verify RSASSA-PKCS1-V1_5 signatures. -type Handler struct { - method *Method -} - -func NewHandler() signing.SignatureHandler { - return NewHandlerFor(PKCS1v15) -} - -func NewHandlerFor(m *Method) signing.SignatureHandler { - return &Handler{method: m} -} - -var PKCS1v15 = &Method{ - Algorithm: Algorithm, - MediaType: MediaType, - Sign: rsa.SignPKCS1v15, - Verify: rsa.VerifyPKCS1v15, -} - -func (h *Handler) Algorithm() string { - return h.method.Algorithm -} - -func (h *Handler) Sign(cctx credentials.Context, digest string, sctx signing.SigningContext) (signature *signing.Signature, err error) { - privateKey, err := GetPrivateKey(sctx.GetPrivateKey()) - if err != nil { - return nil, errors.Wrapf(err, "invalid rsa private key") - } - decodedHash, err := hex.DecodeString(digest) - if err != nil { - return nil, fmt.Errorf("failed decoding hash to bytes") - } - sig, err := h.method.Sign(rand.Reader, privateKey, sctx.GetHash(), decodedHash) - if err != nil { - return nil, fmt.Errorf("failed signing hash, %w", err) - } - - media := h.method.MediaType - value := hex.EncodeToString(sig) - - var iss string - pub := sctx.GetPublicKey() - if pub != nil { - var pubKey signutils.GenericPublicKey - certs, err := signutils.GetCertificateChain(pub, false) - if err == nil && len(certs) > 0 { - pubKey, _, err = GetPublicKey(certs[0].PublicKey) - if err != nil { - return nil, errors.ErrInvalidWrap(err, "public key") - } - err = signutils.VerifyCertificate(certs[0], certs[1:], sctx.GetRootCerts(), sctx.GetIssuer()) - if err != nil { - return nil, errors.Wrapf(err, "public key certificate") - } - media = MediaTypePEM - value = string(signutils.SignatureBytesToPem(h.Algorithm(), sig, certs...)) - iss = certs[0].Subject.String() - } else { - pubKey, _, err = GetPublicKey(pub) - if err != nil { - return nil, errors.ErrInvalidWrap(err, "public key") - } - } - if !privateKey.PublicKey.Equal(pubKey) { - return nil, fmt.Errorf("invalid public key for private key") - } - } - - return &signing.Signature{ - Value: value, - MediaType: media, - Algorithm: h.Algorithm(), - Issuer: iss, - }, nil -} - -// Verify checks the signature, returns an error on verification failure. -func (h *Handler) Verify(digest string, signature *signing.Signature, sctx signing.SigningContext) (err error) { - var signatureBytes []byte - - publicKey, name, err := GetPublicKey(sctx.GetPublicKey()) - if err != nil { - return fmt.Errorf("failed to get public key: %w", err) - } - - switch signature.MediaType { - case MediaType: - signatureBytes, err = hex.DecodeString(signature.Value) - if err != nil { - return fmt.Errorf("unable to get signature value: failed decoding hash %s: %w", digest, err) - } - case signutils.MediaTypePEM: - sig, algo, _, err := signutils.GetSignatureFromPem([]byte(signature.Value)) - if err != nil { - return fmt.Errorf("unable to get signature from pem: %w", err) - } - if algo != "" && algo != h.Algorithm() { - return errors.ErrInvalid(signutils.KIND_SIGN_ALGORITHM, algo) - } - signatureBytes = sig - default: - return fmt.Errorf("invalid signature mediaType %s", signature.MediaType) - } - - decodedHash, err := hex.DecodeString(digest) - if err != nil { - return fmt.Errorf("failed decoding hash %s: %w", digest, err) - } - - if name != nil { - if signature.Issuer != "" { - iss, err := signutils.ParseDN(signature.Issuer) - if err != nil { - return errors.Wrapf(err, "signature issuer") - } - if signutils.MatchDN(*iss, *name) != nil { - return fmt.Errorf("issuer %s does not match %s", signature.Issuer, name) - } - } - } - if err := h.method.Verify(publicKey, sctx.GetHash(), decodedHash, signatureBytes); err != nil { - return fmt.Errorf("signature verification failed, %w", err) - } - - return nil -} - -func (_ Handler) CreateKeyPair() (priv signutils.GenericPrivateKey, pub signutils.GenericPublicKey, err error) { - return CreateKeyPair() -} - -func CreateKeyPair() (priv signutils.GenericPrivateKey, pub signutils.GenericPublicKey, err error) { - key, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return nil, nil, err - } - return key, &key.PublicKey, nil -} diff --git a/pkg/signing/handlers/sigstore/attr/attr.go b/pkg/signing/handlers/sigstore/attr/attr.go deleted file mode 100644 index a65cbc721..000000000 --- a/pkg/signing/handlers/sigstore/attr/attr.go +++ /dev/null @@ -1,101 +0,0 @@ -package attr - -import ( - "errors" - "fmt" - - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/runtime" -) - -const ( - ATTR_KEY = "ocm.software/signing/sigstore" - ATTR_SHORT = "sigstore" -) - -var defaultAttr = Attribute{ - FulcioURL: "https://v1.fulcio.sigstore.dev", - RekorURL: "https://rekor.sigstore.dev", - OIDCIssuer: "https://oauth2.sigstore.dev/auth", - OIDCClientID: "sigstore", -} - -func init() { - datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) -} - -// AttributeType represents the attribute functionality. -type AttributeType struct{} - -// Attribute represents the attribute data. -type Attribute struct { - FulcioURL string `json:"fulcioURL"` - RekorURL string `json:"rekorURL"` - OIDCIssuer string `json:"OIDCIssuer"` - OIDCClientID string `json:"OIDCClientID"` -} - -// Name returns the attribute name. -func (a AttributeType) Name() string { - return ATTR_KEY -} - -// Description returns a description of the attribute. -func (a AttributeType) Description() string { - return ` -*sigstore config* Configuration to use for sigstore based signing. -The following fields are used. -- *fulcioURL* *string* default is https://v1.fulcio.sigstore.dev -- *rekorURL* *string* default is https://rekor.sigstore.dev -- *OIDCIssuer* *string* default is https://oauth2.sigstore.dev/auth -- *OIDCClientID* *string* default is sigstore -` -} - -// Encode marshals an attribute. -func (AttributeType) Encode(v interface{}, marshaler runtime.Marshaler) ([]byte, error) { - if marshaler == nil { - marshaler = runtime.DefaultJSONEncoding - } - - result, ok := v.(*Attribute) - if !ok { - return nil, errors.New("sigstore attribute required") - } - - return marshaler.Marshal(result) -} - -// Decode unmarshals an attribute. -func (a AttributeType) Decode(data []byte, unmarshaler runtime.Unmarshaler) (interface{}, error) { - if unmarshaler == nil { - unmarshaler = runtime.DefaultJSONEncoding - } - - attr := &Attribute{} - err := unmarshaler.Unmarshal(data, attr) - if err != nil { - return nil, fmt.Errorf("invalud attribute value for %s: %w", ATTR_KEY, err) - } - - return attr, nil -} - -// Get returns the attributes. -func Get(ctx datacontext.Context) *Attribute { - v := ctx.GetAttributes().GetAttribute(ATTR_KEY) - if v == nil { - return &defaultAttr - } - a, ok := v.(*Attribute) - if !ok { - return &defaultAttr - } - return a -} - -// Set sets the attributes. -func Set(ctx datacontext.Context, a *Attribute) error { - attrs := ctx.GetAttributes() - return attrs.SetAttribute(ATTR_KEY, a) -} diff --git a/pkg/signing/handlers/sigstore/handler.go b/pkg/signing/handlers/sigstore/handler.go deleted file mode 100644 index 0ed7e9165..000000000 --- a/pkg/signing/handlers/sigstore/handler.go +++ /dev/null @@ -1,297 +0,0 @@ -package sigstore - -import ( - "context" - "crypto" - "crypto/ecdsa" - "crypto/x509" - "encoding/base64" - "encoding/hex" - "encoding/json" - "encoding/pem" - "fmt" - - "github.com/go-openapi/strfmt" - "github.com/go-openapi/swag" - "github.com/mandelsoft/goutils/errors" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/fulcio" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" - "github.com/sigstore/cosign/v2/pkg/cosign" - "github.com/sigstore/rekor/pkg/client" - "github.com/sigstore/rekor/pkg/generated/client/entries" - "github.com/sigstore/rekor/pkg/generated/models" - hashedrekord_v001 "github.com/sigstore/rekor/pkg/types/hashedrekord/v0.0.1" - "github.com/sigstore/rekor/pkg/verify" - "github.com/sigstore/sigstore/pkg/signature" - signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" - - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/handlers/sigstore/attr" -) - -// Algorithm defines the type for the RSA PKCS #1 v1.5 signature algorithm. -const Algorithm = "sigstore" - -// MediaType defines the media type for a plain RSA signature. -const MediaType = "application/vnd.ocm.signature.sigstore" - -// SignaturePEMBlockAlgorithmHeader defines the header in a signature pem block where the signature algorithm is defined. -const SignaturePEMBlockAlgorithmHeader = "Algorithm" - -func init() { - signing.DefaultHandlerRegistry().RegisterSigner(Algorithm, Handler{}) -} - -// Handler is a signatures.Signer compatible struct to sign using sigstore -// and a signatures.Verifier compatible struct to verify using sigstore. -type Handler struct{} - -// Algorithm specifies the name of the signing algorithm. -func (h Handler) Algorithm() string { - return Algorithm -} - -// Sign implements the signing functionality. -func (h Handler) Sign(cctx credentials.Context, digest string, sctx signing.SigningContext) (*signing.Signature, error) { - hash := sctx.GetHash() - // exit immediately if hash alg is not SHA-256, rekor doesn't currently support other hash functions - if hash != crypto.SHA256 { - return nil, fmt.Errorf("cannot sign using sigstore. rekor only supports SHA-256 digests: %s provided", hash.String()) - } - - ctx := context.Background() - - // generate a private key - priv, err := cosign.GeneratePrivateKey() - if err != nil { - return nil, fmt.Errorf("error generating keypair: %w", err) - } - - // create an ECDSA signer - signer, err := signature.LoadECDSASignerVerifier(priv, hash) - if err != nil { - return nil, fmt.Errorf("error loading sigstore signer: %w", err) - } - - // get the attributes for the sigstore signer - cfg := attr.Get(cctx) - - // create a fulcio signing client - fs, err := fulcio.NewSigner(ctx, options.KeyOpts{ - FulcioURL: cfg.FulcioURL, - OIDCIssuer: cfg.OIDCIssuer, - OIDCClientID: cfg.OIDCClientID, - SkipConfirmation: true, - }, signer) - if err != nil { - return nil, fmt.Errorf("failed to create signer: %w", err) - } - - // decode the digest string - rawDigest, err := hex.DecodeString(digest) - if err != nil { - return nil, fmt.Errorf("failed to decode digest: %w", err) - } - - // sign the existing digest - sig, err := fs.SignMessage(nil, - signatureoptions.WithDigest(rawDigest), - signatureoptions.WithContext(ctx)) - if err != nil { - return nil, fmt.Errorf("failed to sign: %w", err) - } - - // get the public key for certificate transparency log - pubKeys, err := cosign.GetCTLogPubs(ctx) - if err != nil { - return nil, fmt.Errorf("failed to get cosign CT Log Public Keys: %w", err) - } - - // verify the signed certificate timestamp - if err := cosign.VerifySCT(ctx, fs.Cert, fs.Chain, fs.SCT, pubKeys); err != nil { - return nil, fmt.Errorf("failed to verify signed certifcate timestamp: %w", err) - } - - // get the public key from the signing key pair - pub, err := fs.PublicKey() - if err != nil { - return nil, fmt.Errorf("failed to get public key for signing: %w", err) - } - - // marshal the public key bytes - publicKeyBytes, err := x509.MarshalPKIXPublicKey(pub) - if err != nil { - return nil, fmt.Errorf("failed to marshal public key for signing: %w", err) - } - - // encode the public key to pem format - publicKey := pem.EncodeToMemory(&pem.Block{ - Bytes: publicKeyBytes, - Type: "PUBLIC KEY", - }) - - // init the rekor client - rekorClient, err := client.GetRekorClient(cfg.RekorURL) - if err != nil { - return nil, fmt.Errorf("failed to create rekor client: %w", err) - } - - // create a rekor hashed entry - hashedEntry := prepareRekorEntry(digest, sig, publicKey) - - // valiate the rekor entry before submission - if _, err := hashedEntry.Canonicalize(ctx); err != nil { - return nil, fmt.Errorf("rekor entry is not valid: %w", err) - } - - // prepare the entry for submission - entry := &models.Hashedrekord{ - APIVersion: swag.String(hashedEntry.APIVersion()), - Spec: hashedEntry.HashedRekordObj, - } - - // prepare the create entry request parameters - params := entries.NewCreateLogEntryParams(). - WithContext(ctx). - WithProposedEntry(entry) - - // submit the create entry request - resp, err := rekorClient.Entries.CreateLogEntry(params) - if err != nil { - return nil, fmt.Errorf("failed to create rekor entry: %w", err) - } - - // extract the payload from the rekor response - data, err := json.Marshal(resp.GetPayload()) - if err != nil { - return nil, fmt.Errorf("failed to marshal rekor response: %w", err) - } - - // store the rekor response in the signature value - return &signing.Signature{ - Value: base64.StdEncoding.EncodeToString(data), - MediaType: MediaType, - Algorithm: Algorithm, - Issuer: "", - }, nil -} - -// Verify checks the signature, returns an error on verification failure. -func (h Handler) Verify(digest string, sig *signing.Signature, sctx signing.SigningContext) (err error) { - ctx := context.Background() - - data, err := base64.StdEncoding.DecodeString(sig.Value) - if err != nil { - return fmt.Errorf("failed to decode signature: %w", err) - } - - var entries models.LogEntry - if err := json.Unmarshal(data, &entries); err != nil { - return fmt.Errorf("failed to unmarshal rekor log entry from signature: %w", err) - } - - rawDigest, err := hex.DecodeString(digest) - if err != nil { - return fmt.Errorf("failed to decode digest: %w", err) - } - - for _, entry := range entries { - verifier, err := loadVerifier(ctx) - if err != nil { - return fmt.Errorf("failed to load rekor verifier: %w", err) - } - - body, err := base64.StdEncoding.DecodeString(entry.Body.(string)) - if err != nil { - return fmt.Errorf("failed to decode rekor entry body: %w", err) - } - - rekorEntry := &models.Hashedrekord{} - if err := json.Unmarshal(body, rekorEntry); err != nil { - return fmt.Errorf("failed to unmarshal rekor entry body: %w", err) - } - - if err := rekorEntry.Validate(strfmt.Default); err != nil { - return fmt.Errorf("failed to validate rekor entry: %w", err) - } - - rekorEntrySpec := rekorEntry.Spec.(map[string]any) - rekorHashValue := rekorEntrySpec["data"].(map[string]any)["hash"].(map[string]any)["value"] - - // ensure digest matches - if rekorHashValue != digest { - return errors.New("rekor hash doesn't match provided digest") - } - - // get the signature - rekorSignatureRaw := rekorEntrySpec["signature"].(map[string]any)["content"] - rekorSignature, err := base64.StdEncoding.DecodeString(rekorSignatureRaw.(string)) - if err != nil { - return fmt.Errorf("failed to decode rekor signature: %w", err) - } - - // get the public key from the signature - rekorPublicKeyContent := rekorEntrySpec["signature"].(map[string]any)["publicKey"].(map[string]any)["content"] - rekorPublicKeyRaw, err := base64.StdEncoding.DecodeString(rekorPublicKeyContent.(string)) - if err != nil { - return fmt.Errorf("failed to decode rekor public key: %w", err) - } - - block, _ := pem.Decode(rekorPublicKeyRaw) - if block == nil { - return fmt.Errorf("failed to decode public key: %w", err) - } - - rekorPublicKey, err := x509.ParsePKIXPublicKey(block.Bytes) - if err != nil { - return fmt.Errorf("failed to parse public key: %w", err) - } - - // verify signature - if err := ecdsa.VerifyASN1(rekorPublicKey.(*ecdsa.PublicKey), rawDigest, rekorSignature); !err { - return errors.New("could not verify signature using public key") - } - - // verify log entry - if err := verify.VerifyLogEntry(ctx, &entry, verifier); err != nil { - return fmt.Errorf("failed to verify log entry: %w", err) - } - } - return nil -} - -func loadVerifier(ctx context.Context) (signature.Verifier, error) { - publicKeys, err := cosign.GetRekorPubs(ctx) - if err != nil { - return nil, err - } - - for _, pubKey := range publicKeys.Keys { - return signature.LoadVerifier(pubKey.PubKey, crypto.SHA256) - } - - return nil, nil -} - -// based on: https://github.com/sigstore/cosign/blob/ff648d5fb4ed6d0d1c16eaaceff970411fa969e3/pkg/cosign/tlog.go#L233 -func prepareRekorEntry(digest string, sig, publicKey []byte) hashedrekord_v001.V001Entry { - // TODO: this should match the provided hash digest algorithm but - // rekor only supports SHA256 right now - return hashedrekord_v001.V001Entry{ - HashedRekordObj: models.HashedrekordV001Schema{ - Data: &models.HashedrekordV001SchemaData{ - Hash: &models.HashedrekordV001SchemaDataHash{ - Algorithm: swag.String(models.HashedrekordV001SchemaDataHashAlgorithmSha256), - Value: swag.String(digest), - }, - }, - Signature: &models.HashedrekordV001SchemaSignature{ - Content: strfmt.Base64(sig), - PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{ - Content: strfmt.Base64(publicKey), - }, - }, - }, - } -} diff --git a/pkg/signing/hasher/init.go b/pkg/signing/hasher/init.go deleted file mode 100644 index 24b89a6b0..000000000 --- a/pkg/signing/hasher/init.go +++ /dev/null @@ -1,7 +0,0 @@ -package hasher - -import ( - _ "github.com/open-component-model/ocm/pkg/signing/hasher/nodigest" - _ "github.com/open-component-model/ocm/pkg/signing/hasher/sha256" - _ "github.com/open-component-model/ocm/pkg/signing/hasher/sha512" -) diff --git a/pkg/signing/hasher/nodigest/hasher.go b/pkg/signing/hasher/nodigest/hasher.go deleted file mode 100644 index b319be1ae..000000000 --- a/pkg/signing/hasher/nodigest/hasher.go +++ /dev/null @@ -1,33 +0,0 @@ -package nodigest - -import ( - "crypto" - "hash" - - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/signing" -) - -const Algorithm = metav1.NoDigest - -func init() { - signing.DefaultHandlerRegistry().RegisterHasher(Handler{}) -} - -// Handler is a signatures.Hasher compatible struct to hash with sha256. -type Handler struct{} - -var _ signing.Hasher = Handler{} - -func (h Handler) Algorithm() string { - return Algorithm -} - -// Create creates a Hasher instance for sha256. -func (_ Handler) Create() hash.Hash { - return nil -} - -func (_ Handler) Crypto() crypto.Hash { - return 0 -} diff --git a/pkg/signing/hasher/sha256/hasher.go b/pkg/signing/hasher/sha256/hasher.go deleted file mode 100644 index 0dd2ff922..000000000 --- a/pkg/signing/hasher/sha256/hasher.go +++ /dev/null @@ -1,33 +0,0 @@ -package sha256 - -import ( - "crypto" - "crypto/sha256" - "hash" - - "github.com/open-component-model/ocm/pkg/signing" -) - -var Algorithm = crypto.SHA256.String() - -func init() { - signing.DefaultHandlerRegistry().RegisterHasher(Handler{}) -} - -// Handler is a signatures.Hasher compatible struct to hash with sha256. -type Handler struct{} - -var _ signing.Hasher = Handler{} - -func (_ Handler) Algorithm() string { - return Algorithm -} - -// Create creates a Hasher instance for no digest. -func (_ Handler) Create() hash.Hash { - return sha256.New() -} - -func (_ Handler) Crypto() crypto.Hash { - return crypto.SHA256 -} diff --git a/pkg/signing/hasher/sha512/hasher.go b/pkg/signing/hasher/sha512/hasher.go deleted file mode 100644 index 3ebdd0677..000000000 --- a/pkg/signing/hasher/sha512/hasher.go +++ /dev/null @@ -1,33 +0,0 @@ -package sha512 - -import ( - "crypto" - "crypto/sha512" - "hash" - - "github.com/open-component-model/ocm/pkg/signing" -) - -var Algorithm = crypto.SHA512.String() - -func init() { - signing.DefaultHandlerRegistry().RegisterHasher(Handler{}) -} - -// Handler is a signatures.Hasher compatible struct to hash with sha256. -type Handler struct{} - -var _ signing.Hasher = Handler{} - -func (_ Handler) Algorithm() string { - return Algorithm -} - -// Create creates a Hasher instance for no digest. -func (_ Handler) Create() hash.Hash { - return sha512.New() -} - -func (_ Handler) Crypto() crypto.Hash { - return crypto.SHA512 -} diff --git a/pkg/signing/norm/entry/norm.go b/pkg/signing/norm/entry/norm.go deleted file mode 100644 index d5e14ea82..000000000 --- a/pkg/signing/norm/entry/norm.go +++ /dev/null @@ -1,191 +0,0 @@ -package entry - -import ( - "bytes" - "encoding/json" - "fmt" - "sort" - "strconv" - - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/utils" -) - -var Type = normalization{} - -type normalization struct{} - -func New() signing.Normalization { - return normalization{} -} - -func (_ normalization) NewArray() signing.Normalized { - return &normalized{[]interface{}{}} -} - -func (_ normalization) NewMap() signing.Normalized { - return &normalized{Entries{}} -} - -func (_ normalization) NewValue(v interface{}) signing.Normalized { - return &normalized{v} -} - -func (_ normalization) String() string { - return "entry normalization" -} - -type normalized struct { - value interface{} -} - -func (n *normalized) Value() interface{} { - return n.value -} - -func (n *normalized) IsEmpty() bool { - switch v := n.value.(type) { - case Entries: - return len(v) == 0 - case []interface{}: - return len(v) == 0 - default: - return false - } -} - -func (n *normalized) Append(normalized signing.Normalized) { - n.value = append(n.value.([]interface{}), normalized.Value()) -} - -func (n *normalized) SetField(name string, value signing.Normalized) { - v := n.value.(Entries) - v = append(v, Entry{ - key: name, - value: value.Value(), - }) - // sort the entries based on the key - sort.SliceStable(v, func(i, j int) bool { - return v[i].Key() < v[j].Key() - }) - n.value = v -} - -func (n *normalized) ToString(gap string) string { - return toString(n.value, gap) -} - -func (l *normalized) String() string { - return string(utils.Must(json.Marshal(l.value))) -} - -func (l *normalized) Formatted() string { - return string(utils.Must(json.MarshalIndent(l.value, "", " "))) -} - -func (n *normalized) Marshal(gap string) ([]byte, error) { - byteBuffer := bytes.NewBuffer([]byte{}) - encoder := json.NewEncoder(byteBuffer) - encoder.SetEscapeHTML(false) - encoder.SetIndent("", gap) - - if err := encoder.Encode(n.value); err != nil { - return nil, err - } - - normalizedJson := byteBuffer.Bytes() - - // encoder.Encode appends a newline that we do not want - if normalizedJson[len(normalizedJson)-1] == 10 { - normalizedJson = normalizedJson[:len(normalizedJson)-1] - } - return normalizedJson, nil -} - -// Entry is used to keep exactly one key/value pair. -type Entry struct { - key string - value interface{} -} - -func (e Entry) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]interface{}{ - e.key: e.value, - }) -} - -func NewEntry(key string, value interface{}) Entry { - return Entry{key: key, value: value} -} - -func (e Entry) Get() (string, interface{}) { - return e.key, e.value -} - -func (e Entry) Key() string { - return e.key -} - -func (e Entry) Value() interface{} { - return e.value -} - -func (e Entry) ToString(gap string) string { - return fmt.Sprintf("%s%s: %s", gap, e.Key(), toString(e.Value(), gap)) -} - -type Entries []Entry - -func (l *Entries) Add(key string, value interface{}) { - *l = append(*l, NewEntry(key, value)) -} - -func (l Entries) String() string { - return string(utils.Must(json.Marshal(l))) -} - -func (l Entries) Formatted() string { - return string(utils.Must(json.MarshalIndent(l, "", " "))) -} - -func (l Entries) ToString(gap string) string { - ngap := gap + " " - s := "{" - sep := "" - for _, v := range l { - s = fmt.Sprintf("%s\n%s", s, v.ToString(ngap)) - sep = "\n" + gap - } - s += sep + "}" - return s -} - -func toString(v interface{}, gap string) string { - if v == nil || v == signing.Null { - return "null" - } - switch castIn := v.(type) { - case Entries: - return castIn.ToString(gap) - case []Entry: - return Entries(castIn).ToString(gap) - case Entry: - return castIn.ToString(gap) - case []interface{}: - ngap := gap + " " - s := "[" - sep := "" - for _, v := range castIn { - s = fmt.Sprintf("%s\n%s%s", s, ngap, toString(v, ngap)) - sep = "\n" + gap - } - s += sep + "]" - return s - case string: - return castIn - case bool: - return strconv.FormatBool(castIn) - default: - panic(fmt.Sprintf("unknown type %T in toString. This should not happen", v)) - } -} diff --git a/pkg/signing/norm/jcs/norm.go b/pkg/signing/norm/jcs/norm.go deleted file mode 100644 index d2404a0f8..000000000 --- a/pkg/signing/norm/jcs/norm.go +++ /dev/null @@ -1,135 +0,0 @@ -package jcs - -import ( - "bytes" - "encoding/json" - "fmt" - "strconv" - - "github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer" - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/general" - "github.com/mandelsoft/goutils/maputils" - - "github.com/open-component-model/ocm/pkg/signing" -) - -var Type = normalization{} - -type normalization struct{} - -func New() signing.Normalization { - return normalization{} -} - -func (_ normalization) NewArray() signing.Normalized { - return &normalized{[]interface{}{}} -} - -func (_ normalization) NewMap() signing.Normalized { - return &normalized{map[string]interface{}{}} -} - -func (_ normalization) NewValue(v interface{}) signing.Normalized { - return &normalized{v} -} - -func (_ normalization) String() string { - return "JCS(rfc8785) normalization" -} - -type normalized struct { - value interface{} -} - -func (n *normalized) Value() interface{} { - return n.value -} - -func (n *normalized) IsEmpty() bool { - switch v := n.value.(type) { - case map[string]interface{}: - return len(v) == 0 - case []interface{}: - return len(v) == 0 - default: - return false - } -} - -func (n *normalized) Append(normalized signing.Normalized) { - n.value = append(n.value.([]interface{}), normalized.Value()) -} - -func (n *normalized) SetField(name string, value signing.Normalized) { - v := n.value.(map[string]interface{}) - v[name] = value.Value() -} - -func (n *normalized) ToString(gap string) string { - return toString(n.value, gap) -} - -func (l *normalized) String() string { - return string(general.Must(json.Marshal(l.value))) -} - -func (l *normalized) Formatted() string { - return string(general.Must(json.MarshalIndent(l.value, "", " "))) -} - -func (n *normalized) Marshal(gap string) ([]byte, error) { - byteBuffer := bytes.NewBuffer([]byte{}) - encoder := json.NewEncoder(byteBuffer) - encoder.SetEscapeHTML(false) - encoder.SetIndent("", gap) - - err := encoder.Encode(n.Value()) - if err != nil { - return nil, err - } - if gap != "" { - return byteBuffer.Bytes(), nil - } - data, err := jsoncanonicalizer.Transform(byteBuffer.Bytes()) - if err != nil { - return nil, errors.Wrapf(err, "cannot canonicalize json") - } - return data, nil -} - -func toString(v interface{}, gap string) string { - if v == nil || v == signing.Null { - return "null" - } - switch castIn := v.(type) { - case map[string]interface{}: - ngap := gap + " " - s := "{" - sep := "" - keys := maputils.OrderedKeys(castIn) - for _, n := range keys { - v := castIn[n] - sep = "\n" + gap - s = fmt.Sprintf("%s%s %s: %s", s, sep, n, toString(v, ngap)) - } - s += sep + "}" - return s - case []interface{}: - ngap := gap + " " - s := "[" - sep := "" - for _, v := range castIn { - s = fmt.Sprintf("%s\n%s%s", s, ngap, toString(v, ngap)) - sep = "\n" + gap - } - s += sep + "]" - return s - case string: - return castIn - case bool: - return strconv.FormatBool(castIn) - default: - panic(fmt.Sprintf("unknown type %T in toString. This should not happen", v)) - } -} diff --git a/pkg/signing/registry.go b/pkg/signing/registry.go deleted file mode 100644 index 7fddb87ce..000000000 --- a/pkg/signing/registry.go +++ /dev/null @@ -1,563 +0,0 @@ -package signing - -import ( - "crypto/x509/pkix" - "sort" - "sync" - - "github.com/mandelsoft/goutils/set" - "github.com/mandelsoft/goutils/sliceutils" - "golang.org/x/exp/maps" - "golang.org/x/exp/slices" - - "github.com/open-component-model/ocm/pkg/signing/signutils" -) - -const DEFAULT_TSA_URL = "http://timestamp.digicert.com" - -type Registry interface { - RegistryFuncs - - Copy() Registry -} - -type RegistryFuncs interface { - HandlerRegistryFuncs - KeyRegistryFuncs - - HandlerRegistryProvider - KeyRegistryProvider - - TSAUrl() string - SetTSAUrl(url string) -} - -type HasherProvider interface { - GetHasher(name string) Hasher -} - -type HasherRegistryFuncs interface { - HasherProvider - - RegisterHasher(hasher Hasher) - HasherNames() []string -} - -type HasherRegistry interface { - HasherRegistryFuncs - - Copy() HasherRegistry -} - -type HasherRegistryProvider interface { - HasherRegistry() HasherRegistry -} - -type SignerRegistryFuncs interface { - RegisterSignatureHandler(handler SignatureHandler) - RegisterSigner(algo string, signer Signer) - RegisterVerifier(algo string, verifier Verifier) - GetSigner(name string) Signer - GetVerifier(name string) Verifier - SignerNames() []string -} - -type SignerRegistry interface { - SignerRegistryFuncs - - Copy() SignerRegistry -} - -type SignerRegistryProvider interface { - SignerRegistry() SignerRegistry -} - -type HandlerRegistryFuncs interface { - SignerRegistryFuncs - HasherRegistryFuncs - - SignerRegistryProvider - HasherRegistryProvider -} - -type HandlerRegistry interface { - HandlerRegistryFuncs - Copy() HandlerRegistry -} - -type KeyRegistryFuncs interface { - RegisterPublicKey(name string, key interface{}) - RegisterPrivateKey(name string, key interface{}) - GetPublicKey(name string) interface{} - GetPrivateKey(name string) interface{} - HasKeys() bool - - RegisterIssuer(name string, is *pkix.Name) - GetIssuer(name string) *pkix.Name - HasIssuers() bool -} - -type HandlerRegistryProvider interface { - HandlerRegistry() HandlerRegistry -} - -type KeyRegistry interface { - KeyRegistryFuncs - Copy() KeyRegistry -} - -type KeyRegistryProvider interface { - KeyRegistry() KeyRegistry -} - -//////////////////////////////////////////////////////////////////////////////// - -type ( - _hasherRegistry = HasherRegistry - _signerRegistry = SignerRegistry -) - -type handlerRegistry struct { - _hasherRegistry - _signerRegistry -} - -var _ HandlerRegistry = (*handlerRegistry)(nil) - -func NewHandlerRegistry(parents ...HandlerRegistry) HandlerRegistry { - return &handlerRegistry{ - _hasherRegistry: NewHasherRegistry(sliceutils.ConvertWith(parents, toHasherRegistry)...), - _signerRegistry: NewSignerRegistry(sliceutils.ConvertWith(parents, toSignerRegistry)...), - } -} - -func toHasherRegistry(o HasherRegistryProvider) HasherRegistry { - if o == nil { - return nil - } - return o.HasherRegistry() -} - -func toSignerRegistry(o SignerRegistryProvider) SignerRegistry { - if o == nil { - return nil - } - return o.SignerRegistry() -} - -func (r *handlerRegistry) Copy() HandlerRegistry { - return &handlerRegistry{ - _hasherRegistry: r._hasherRegistry.Copy(), - _signerRegistry: r._signerRegistry.Copy(), - } -} - -func (r *handlerRegistry) HasherRegistry() HasherRegistry { - return r._hasherRegistry -} - -func (r *handlerRegistry) SignerRegistry() SignerRegistry { - return r._signerRegistry -} - -//////////////////////////////////////////////////////////////////////////////// - -type signerRegistry struct { - lock sync.RWMutex - parents []SignerRegistry - signers map[string]Signer - verifiers map[string]Verifier -} - -var _ SignerRegistry = (*signerRegistry)(nil) - -func NewSignerRegistry(parents ...SignerRegistry) SignerRegistry { - return &signerRegistry{ - parents: slices.Clone(parents), - signers: map[string]Signer{}, - verifiers: map[string]Verifier{}, - } -} - -func (r *signerRegistry) Copy() SignerRegistry { - return &signerRegistry{ - parents: r.parents, - signers: maps.Clone(r.signers), - verifiers: maps.Clone(r.verifiers), - } -} - -func (r *signerRegistry) RegisterSignatureHandler(handler SignatureHandler) { - r.lock.Lock() - defer r.lock.Unlock() - r.signers[handler.Algorithm()] = handler - r.verifiers[handler.Algorithm()] = handler -} - -func (r *signerRegistry) RegisterSigner(algo string, signer Signer) { - r.lock.Lock() - defer r.lock.Unlock() - r.signers[algo] = signer - if v, ok := signer.(Verifier); ok && r.verifiers[algo] == nil { - r.verifiers[algo] = v - } -} - -func (r *signerRegistry) SignerNames() []string { - r.lock.Lock() - defer r.lock.Unlock() - - names := set.Set[string]{} - for n := range r.signers { - names.Add(n) - } - for _, p := range r.parents { - if p == nil { - continue - } - names.Add(p.SignerNames()...) - } - result := names.AsArray() - sort.Strings(result) - return result -} - -func (r *signerRegistry) RegisterVerifier(algo string, verifier Verifier) { - r.lock.Lock() - defer r.lock.Unlock() - r.verifiers[algo] = verifier - if v, ok := verifier.(Signer); ok && r.signers[algo] == nil { - r.signers[algo] = v - } -} - -func (r *signerRegistry) GetSigner(name string) Signer { - r.lock.RLock() - defer r.lock.RUnlock() - - s := r.signers[name] - if s != nil { - return s - } - for _, p := range r.parents { - if p == nil { - continue - } - s = p.GetSigner(name) - if s != nil { - return s - } - } - return nil -} - -func (r *signerRegistry) GetVerifier(name string) Verifier { - r.lock.RLock() - defer r.lock.RUnlock() - - v := r.verifiers[name] - if v != nil { - return v - } - for _, p := range r.parents { - if p == nil { - continue - } - v = p.GetVerifier(name) - if v != nil { - return v - } - } - return nil -} - -//////////////////////////////////////////////////////////////////////////////// - -type hasherRegistry struct { - lock sync.RWMutex - parents []HasherRegistry - hasher map[string]Hasher -} - -var _ HasherRegistry = (*hasherRegistry)(nil) - -func NewHasherRegistry(parents ...HasherRegistry) HasherRegistry { - return &hasherRegistry{ - parents: slices.Clone(parents), - hasher: map[string]Hasher{}, - } -} - -func (r *hasherRegistry) Copy() HasherRegistry { - return &hasherRegistry{ - parents: r.parents, - hasher: maps.Clone(r.hasher), - } -} - -func (r *hasherRegistry) RegisterHasher(hasher Hasher) { - r.lock.Lock() - defer r.lock.Unlock() - r.hasher[hasher.Algorithm()] = hasher -} - -func (r *hasherRegistry) HasherNames() []string { - r.lock.Lock() - defer r.lock.Unlock() - - names := set.New[string]() - for n := range r.hasher { - names.Add(n) - } - for _, p := range r.parents { - if p == nil { - continue - } - names.Add(p.HasherNames()...) - } - result := names.AsArray() - sort.Strings(result) - return result -} - -func (r *hasherRegistry) GetHasher(name string) Hasher { - r.lock.RLock() - defer r.lock.RUnlock() - - h := r.hasher[NormalizeHashAlgorithm(name)] - if h != nil { - return h - } - for _, p := range r.parents { - if p == nil { - continue - } - h = p.GetHasher(name) - if h != nil { - return h - } - } - return nil -} - -//////////////////////////////////////////////////////////////////////////////// - -var defaultHandlerRegistry = NewHandlerRegistry() - -func DefaultHandlerRegistry() HandlerRegistry { - return defaultHandlerRegistry -} - -//////////////////////////////////////////////////////////////////////////////// - -type keyRegistry struct { - lock sync.RWMutex - parents []KeyRegistry - publicKeys map[string]interface{} - privateKeys map[string]interface{} - issuers map[string]*pkix.Name -} - -var _ KeyRegistry = (*keyRegistry)(nil) - -func NewKeyRegistry(parents ...KeyRegistry) KeyRegistry { - return &keyRegistry{ - parents: slices.Clone(parents), - publicKeys: map[string]interface{}{}, - privateKeys: map[string]interface{}{}, - issuers: map[string]*pkix.Name{}, - } -} - -func (r *keyRegistry) Copy() KeyRegistry { - return &keyRegistry{ - parents: r.parents, - publicKeys: maps.Clone(r.publicKeys), - privateKeys: maps.Clone(r.privateKeys), - } -} - -func (r *keyRegistry) HasKeys() bool { - r.lock.Lock() - defer r.lock.Unlock() - if len(r.publicKeys) > 0 || len(r.privateKeys) > 0 { - return true - } - for _, p := range r.parents { - if p == nil { - continue - } - if p.HasKeys() { - return true - } - } - return false -} - -func (r *keyRegistry) RegisterPublicKey(name string, key interface{}) { - r.lock.Lock() - defer r.lock.Unlock() - r.publicKeys[name] = key -} - -func (r *keyRegistry) RegisterPrivateKey(name string, key interface{}) { - r.lock.Lock() - defer r.lock.Unlock() - r.privateKeys[name] = key -} - -func (r *keyRegistry) GetPublicKey(name string) interface{} { - r.lock.RLock() - defer r.lock.RUnlock() - k := r.publicKeys[name] - if k != nil { - return k - } - for _, p := range r.parents { - if p == nil { - continue - } - k = p.GetPublicKey(name) - if k != nil { - return k - } - } - return nil -} - -func (r *keyRegistry) GetPrivateKey(name string) interface{} { - r.lock.RLock() - defer r.lock.RUnlock() - - k := r.privateKeys[name] - if k != nil { - return k - } - for _, p := range r.parents { - if p == nil { - continue - } - k = p.GetPrivateKey(name) - if k != nil { - return k - } - } - return nil -} - -func (r *keyRegistry) RegisterIssuer(name string, is *pkix.Name) { - r.lock.Lock() - defer r.lock.Unlock() - r.issuers[name] = is -} - -func (r *keyRegistry) GetIssuer(name string) *pkix.Name { - r.lock.Lock() - defer r.lock.Unlock() - i := r.issuers[name] - if i != nil { - return i - } - for _, p := range r.parents { - i := p.GetIssuer(name) - if i != nil { - return i - } - } - // if not explicitly overwritten, the signature name - // is interpreted as expected distinguished name for the issuer. - dn, err := signutils.ParseDN(name) - if err == nil { - return dn - } - return nil -} - -func (r *keyRegistry) HasIssuers() bool { - r.lock.Lock() - defer r.lock.Unlock() - if len(r.issuers) > 0 { - return true - } - for _, p := range r.parents { - if p == nil { - continue - } - if p.HasIssuers() { - return true - } - } - return false -} - -var defaultKeyRegistry = NewKeyRegistry() - -func DefaultKeyRegistry() KeyRegistry { - return defaultKeyRegistry -} - -//////////////////////////////////////////////////////////////////////////////// - -type ( - _HandlerRegistry = HandlerRegistry - _KeyRegistry = KeyRegistry -) - -type registry struct { - _HandlerRegistry - _KeyRegistry - - tsaUrl string -} - -var _ Registry = (*registry)(nil) - -func NewRegistry(h HandlerRegistry, k KeyRegistry) Registry { - return ®istry{ - _HandlerRegistry: NewHandlerRegistry(h), - _KeyRegistry: NewKeyRegistry(k), - } -} - -func (r *registry) HandlerRegistry() HandlerRegistry { - return r._HandlerRegistry -} - -func (r *registry) KeyRegistry() KeyRegistry { - return r._KeyRegistry -} - -func (r *registry) Copy() Registry { - return ®istry{ - _HandlerRegistry: r.HandlerRegistry().Copy(), - _KeyRegistry: r.KeyRegistry().Copy(), - tsaUrl: r.tsaUrl, - } -} - -func (r *registry) TSAUrl() string { - if r.tsaUrl == "" { - return DEFAULT_TSA_URL - } - return r.tsaUrl -} - -func (r *registry) SetTSAUrl(url string) { - r.tsaUrl = url -} - -func RegistryWithPreferredKeys(reg Registry, keys KeyRegistry) Registry { - if keys == nil { - return reg - } - return ®istry{ - _HandlerRegistry: reg.HandlerRegistry(), - _KeyRegistry: NewKeyRegistry(keys, reg.KeyRegistry()), - } -} - -var defaultRegistry = NewRegistry(DefaultHandlerRegistry(), DefaultKeyRegistry()) - -func DefaultRegistry() Registry { - return defaultRegistry -} diff --git a/pkg/signing/signing_test.go b/pkg/signing/signing_test.go deleted file mode 100644 index dbfeb1c3d..000000000 --- a/pkg/signing/signing_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package signing_test - -import ( - "crypto/x509/pkix" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/signing" - "github.com/open-component-model/ocm/pkg/signing/handlers/rsa" - "github.com/open-component-model/ocm/pkg/signing/hasher/sha256" -) - -var registry = signing.DefaultRegistry() - -const NAME = "testsignature" - -var ISSUER = &pkix.Name{CommonName: "mandelsoft"} - -var _ = Describe("signing", func() { - var defaultContext credentials.Context - - BeforeEach(func() { - defaultContext = credentials.New() - }) - - It("uses rsa signer", func() { - hasher := registry.GetHasher(sha256.Algorithm) - hash, _ := signing.Hash(hasher.Create(), []byte("test")) - - priv, pub, err := rsa.Handler{}.CreateKeyPair() - Expect(err).To(Succeed()) - - registry.RegisterPublicKey(NAME, pub) - registry.RegisterPrivateKey(NAME, priv) - - sctx := &signing.DefaultSigningContext{ - Hash: hasher.Crypto(), - PrivateKey: registry.GetPrivateKey(NAME), - PublicKey: pub, - RootCerts: nil, - Issuer: ISSUER, - } - sig, err := registry.GetSigner(rsa.Algorithm).Sign(defaultContext, hash, sctx) - - Expect(err).To(Succeed()) - Expect(sig.MediaType).To(Equal(rsa.MediaType)) - - sctx.PublicKey = registry.GetPublicKey(NAME) - Expect(registry.GetVerifier(rsa.Algorithm).Verify(hash, sig, sctx)).To(Succeed()) - hash = "A" + hash[1:] - Expect(registry.GetVerifier(rsa.Algorithm).Verify(hash, sig, sctx)).To(HaveOccurred()) - }) -}) diff --git a/pkg/signing/signutils/utils.go b/pkg/signing/signutils/utils.go deleted file mode 100644 index e3b2c8509..000000000 --- a/pkg/signing/signutils/utils.go +++ /dev/null @@ -1,590 +0,0 @@ -package signutils - -import ( - "bytes" - "crypto" - "crypto/dsa" //nolint: staticcheck // yes - "crypto/ecdsa" - "crypto/rsa" - "crypto/x509" - "encoding/pem" - "fmt" - "os" - "runtime" - "strings" - "time" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/modern-go/reflect2" - "golang.org/x/exp/slices" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/utils" -) - -func privateKey(block *pem.Block) (interface{}, error) { - x509Encoded := block.Bytes - switch block.Type { - case "RSA PRIVATE KEY": - return x509.ParsePKCS1PrivateKey(x509Encoded) - case "EC PRIVATE KEY": - return x509.ParseECPrivateKey(x509Encoded) - default: - return nil, fmt.Errorf("invalid pem block type %q", block.Type) - } -} - -func PemBlockForPrivateKey(priv interface{}) *pem.Block { - switch k := priv.(type) { - case *rsa.PrivateKey: - return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)} - case *ecdsa.PrivateKey: - b, err := x509.MarshalECPrivateKey(k) - if err != nil { - fmt.Fprintf(os.Stderr, "Unable to marshal ECDSA private key: %v", err) - os.Exit(2) - } - return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b} - default: - return nil - } -} - -func PemBlockForPublicKey(priv interface{}, gen ...bool) *pem.Block { - switch k := priv.(type) { - case *rsa.PublicKey: - if len(gen) > 0 && gen[0] { - bytes, err := x509.MarshalPKIXPublicKey(k) - if err != nil { - panic(err) - } - return &pem.Block{Type: "PUBLIC KEY", Bytes: bytes} - } - return &pem.Block{Type: "RSA PUBLIC KEY", Bytes: x509.MarshalPKCS1PublicKey(k)} - case *ecdsa.PublicKey: - b, err := x509.MarshalPKIXPublicKey(k) - if err != nil { - return nil - } - return &pem.Block{Type: "ECDSA PUBLIC KEY", Bytes: b} - default: - return nil - } -} - -func ParsePublicKey(data []byte) (interface{}, error) { - block, _ := pem.Decode(data) - if block == nil { - return nil, fmt.Errorf("invalid public key format (expected pem block)") - } - pub, err := x509.ParsePKIXPublicKey(block.Bytes) - if err != nil { - pub, err = x509.ParsePKCS1PublicKey(block.Bytes) - if err != nil { - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse DER encoded public key") - } - pub = cert.PublicKey - } else { - return pub, nil - } - } - switch pub := pub.(type) { - case *rsa.PublicKey: - return pub, nil - case *dsa.PublicKey: - return pub, nil - case *ecdsa.PublicKey: - return pub, nil - default: - return nil, fmt.Errorf("unknown type of public key") - } -} - -func ParsePrivateKey(data []byte) (interface{}, error) { - block, _ := pem.Decode(data) - if block == nil { - return nil, fmt.Errorf("invalid private key format (expected pem block)") - } - return privateKey(block) -} - -func ParseCertificate(data []byte) (*x509.Certificate, error) { - block, _ := pem.Decode(data) - if block != nil { - if block.Type != CertificatePEMBlockType { - return nil, fmt.Errorf("unexpected pem block type for certificate: %q", block.Type) - } - return x509.ParseCertificate(block.Bytes) - } - return nil, fmt.Errorf("invalid certificate format (expected %s pem block)", CertificatePEMBlockType) -} - -func ParseCertificateChain(data []byte, filter bool) ([]*x509.Certificate, error) { - var chain []*x509.Certificate - for { - block, rest := pem.Decode(data) - if block == nil { - break - } - if block.Type != CertificatePEMBlockType { - if !filter { - return nil, fmt.Errorf("unexpected pem block type for certificate: %q", block.Type) - } - } else { - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return nil, err - } - chain = append(chain, cert) - } - data = rest - } - - if len(chain) == 0 { - if !filter { - return nil, fmt.Errorf("invalid certificate format (expected CERTIFICATE pem block)") - } - } - return chain, nil -} - -func PemBlockForCertificate(cert interface{}) *pem.Block { - switch k := cert.(type) { - case *x509.Certificate: - return &pem.Block{Type: CertificatePEMBlockType, Bytes: k.Raw} - default: - return nil - } -} - -type PublicKeySource interface { - Public() crypto.PublicKey -} - -func getGenericData(in blobaccess.GenericData) (blobaccess.GenericData, error) { - data, err := blobaccess.GetGenericData(in) - if err != nil { - if !errors.IsErrInvalidKind(err, blobaccess.KIND_DATASOURCE) { - return nil, err - } - return in, nil - } else { - return data, nil - } -} - -func GetPrivateKey(key GenericPrivateKey) (interface{}, error) { - key, err := getGenericData(key) - if err != nil { - return nil, errors.Wrapf(err, "cannot evaluate private key") - } - switch k := key.(type) { - case []byte: - return ParsePrivateKey(k) - case *rsa.PrivateKey: - return k, nil - case *ecdsa.PrivateKey: - return k, nil - default: - return nil, errors.ErrInvalidType(KIND_PRIVATE_KEY, k) - } -} - -func GetPublicKey(key GenericPublicKey) (interface{}, error) { - key, err := getGenericData(key) - if err != nil { - return nil, errors.Wrapf(err, "cannot evaluate public key") - } - switch k := key.(type) { - case []byte: - return ParsePublicKey(k) - case *rsa.PublicKey: - return k, nil - case *dsa.PublicKey: - return k, nil - case *ecdsa.PublicKey: - return k, nil - case *x509.Certificate: - return k.PublicKey, nil - case PublicKeySource: - return k.Public(), nil - default: - return nil, errors.ErrInvalidType(KIND_PUBLIC_KEY, k) - } -} - -func GetCertificateChain(in GenericCertificateChain, filter bool) ([]*x509.Certificate, error) { - // unfortunately it is not possible to get certificates from a x509.CertPool - - in, err := getGenericData(in) - if err != nil { - return nil, errors.Wrapf(err, "cannot evaluate certificate chain") - } - switch k := in.(type) { - case []byte: - return ParseCertificateChain(k, filter) - case *x509.Certificate: - return []*x509.Certificate{k}, nil - case []*x509.Certificate: - return k, nil - default: - return nil, errors.ErrInvalidType(KIND_CERTIFICATE, k) - } -} - -//////////////////////////////////////////////////////////////////////////////// - -func SystemCertPool() (*x509.CertPool, error) { - pool := x509.NewCertPool() - sys, err := x509.SystemCertPool() - if err != nil { - if runtime.GOOS != "windows" { - return nil, errors.Wrapf(err, "cannot get system cert pool") - } - } else { - pool = sys - } - return pool, nil -} - -func RootPoolFromFile(pemfile string, useOS bool, fss ...vfs.FileSystem) (*x509.CertPool, error) { - fs := utils.FileSystem(fss...) - pemdata, err := utils.ReadFile(pemfile, fs) - if err != nil { - return nil, errors.Wrapf(err, "cannot read cert pem file %q", pemfile) - } - pool := x509.NewCertPool() - if useOS { - sys, err := SystemCertPool() - if err != nil { - return nil, err - } - pool = sys - } - ok := pool.AppendCertsFromPEM(pemdata) - if !ok { - return nil, errors.Newf("cannot add cert pem file to cert pool") - } - return pool, err -} - -func GetCertPool(in GenericCertificatePool, filter bool) (*x509.CertPool, error) { - var certs []*x509.Certificate - var err error - - if reflect2.IsNil(in) { - return nil, nil - } - in, err = getGenericData(in) - if err != nil { - return nil, errors.Wrapf(err, "cannot evaluate certificate pool") - } - switch k := in.(type) { - case []byte: - certs, err = ParseCertificateChain(k, filter) - case *x509.Certificate: - certs = []*x509.Certificate{k} - case []*x509.Certificate: - certs = k - case *x509.CertPool: - return k, nil - default: - err = errors.ErrInvalidType(KIND_CERTPOOL, k) - } - - if err != nil { - return nil, err - } - - pool := x509.NewCertPool() - for _, c := range certs { - pool.AddCert(c) - } - return pool, nil -} - -func AddCertificateToPool(in GenericCertificatePool, chain ...GenericCertificateChain) (GenericCertificatePool, error) { - var certs []*x509.Certificate - var pool *x509.CertPool - var err error - - if !reflect2.IsNil(in) { - switch k := in.(type) { - case []byte: - certs, err = ParseCertificateChain(k, false) - case string: - certs, err = ParseCertificateChain([]byte(k), false) - case *x509.Certificate: - certs = []*x509.Certificate{k} - case []*x509.Certificate: - certs = slices.Clone(k) - case *x509.CertPool: - pool = k - default: - err = errors.ErrInvalidType(KIND_CERTPOOL, k) - } - } - if err != nil { - return in, err - } - - for _, c := range chain { - sub, err := GetCertificateChain(c, false) - if err != nil { - return in, err - } - if pool != nil { - for _, cert := range sub { - pool.AddCert(cert) - } - } else { - certs = append(certs, sub...) - } - } - if pool != nil { - return pool, nil - } - return certs, nil -} - -func GetCertificate(in GenericCertificate, filter bool) (*x509.Certificate, *x509.CertPool, error) { - var certs []*x509.Certificate - var err error - - in, err = getGenericData(in) - if err != nil { - return nil, nil, errors.Wrapf(err, "cannot evaluate certificate") - } - - switch k := in.(type) { - case []byte: - certs, err = ParseCertificateChain(k, filter) - case *x509.Certificate: - certs = []*x509.Certificate{k} - case []*x509.Certificate: - certs = k - default: - err = errors.ErrInvalidType(KIND_CERTIFICATE, k) - } - - if err != nil { - return nil, nil, err - } - - if len(certs) == 0 { - return nil, nil, fmt.Errorf("no certificate") - } - - pool := x509.NewCertPool() - for _, c := range certs { - pool.AddCert(c) - } - return certs[0], pool, nil -} - -func IsSelfSigned(cert *x509.Certificate) bool { - if cert.AuthorityKeyId == nil { - return true - } - return bytes.Equal(cert.SubjectKeyId, cert.AuthorityKeyId) -} - -func GetTime(in interface{}) (time.Time, error) { - switch t := in.(type) { - case time.Time: - return t, nil - case *time.Time: - return *t, nil - case string: - notBefore, err := time.Parse("Jan 2 15:04:05 2006", t) - if err != nil { - return time.Time{}, errors.Wrapf(err, "invalid time specification") - } - return notBefore, nil - default: - return time.Time{}, errors.ErrInvalidType("time specification", in) - } -} - -//////////////////////////////////////////////////////////////////////////////// - -type KeyUsage interface { - String() string - AddTo(*x509.Certificate) -} - -type _keyUsage x509.KeyUsage - -func (this _keyUsage) AddTo(cert *x509.Certificate) { - cert.KeyUsage |= x509.KeyUsage(this) -} - -func (this _keyUsage) String() string { - switch x509.KeyUsage(this) { - case x509.KeyUsageDigitalSignature: - return "Signature" - case x509.KeyUsageContentCommitment: - return "ContentCommitment" - case x509.KeyUsageKeyEncipherment: - return "KeyEncipherment" - case x509.KeyUsageDataEncipherment: - return "DataEncipherment" - case x509.KeyUsageKeyAgreement: - return "KeyAgreement" - case x509.KeyUsageCertSign: - return "CertSign" - case x509.KeyUsageCRLSign: - return "CRLSign" - case x509.KeyUsageEncipherOnly: - return "EncipherOnly" - case x509.KeyUsageDecipherOnly: - return "DecipherOnly" - default: - return "UnknownKeyUsage" - } -} - -var _keyUsages = []x509.KeyUsage{ - x509.KeyUsageDigitalSignature, - x509.KeyUsageContentCommitment, - x509.KeyUsageKeyEncipherment, - x509.KeyUsageDataEncipherment, - x509.KeyUsageKeyAgreement, - x509.KeyUsageCertSign, - x509.KeyUsageCRLSign, - x509.KeyUsageEncipherOnly, - x509.KeyUsageDecipherOnly, -} - -func KeyUsages(usages x509.KeyUsage) []string { - result := []string{} - for _, u := range _keyUsages { - if usages&u != 0 { - result = append(result, (_keyUsage(u)).String()) - } - } - return result -} - -type _extKeyUsage x509.ExtKeyUsage - -func (this _extKeyUsage) AddTo(cert *x509.Certificate) { - for _, k := range cert.ExtKeyUsage { - if k == x509.ExtKeyUsage(this) { - return - } - } - cert.ExtKeyUsage = append(cert.ExtKeyUsage, x509.ExtKeyUsage(this)) -} - -func (this _extKeyUsage) String() string { - switch x509.ExtKeyUsage(this) { - case x509.ExtKeyUsageAny: - return "Any" - case x509.ExtKeyUsageServerAuth: - return "ServerAuth" - case x509.ExtKeyUsageClientAuth: - return "ClientAuth" - case x509.ExtKeyUsageCodeSigning: - return "CodeSigning" - case x509.ExtKeyUsageEmailProtection: - return "EmailProtection" - case x509.ExtKeyUsageIPSECEndSystem: - return "IPSECEndSystem" - case x509.ExtKeyUsageIPSECTunnel: - return "IPSECTunnel" - case x509.ExtKeyUsageIPSECUser: - return "IPSECUser" - case x509.ExtKeyUsageTimeStamping: - return "TimeStamping" - case x509.ExtKeyUsageOCSPSigning: - return "OCSPSigning" - case x509.ExtKeyUsageMicrosoftServerGatedCrypto: - return "MicrosoftServerGatedCrypto" - case x509.ExtKeyUsageNetscapeServerGatedCrypto: - return "NetscapeServerGatedCrypto" - case x509.ExtKeyUsageMicrosoftCommercialCodeSigning: - return "MicrosoftCommercialCodeSigning" - case x509.ExtKeyUsageMicrosoftKernelCodeSigning: - return "MicrosoftKernelCodeSigning" - default: - return "UnknownExtKeyUsage" - } -} - -func ExtKeyUsages(usages []x509.ExtKeyUsage) []string { - result := []string{} - for _, u := range usages { - result = append(result, (_extKeyUsage(u)).String()) - } - return result -} - -func ParseKeyUsage(name string) KeyUsage { - switch strings.ToLower(name) { - case "signature": - return _keyUsage(x509.KeyUsageDigitalSignature) - case "commitment": - return _keyUsage(x509.KeyUsageContentCommitment) - case "keyencipherment": - return _keyUsage(x509.KeyUsageKeyEncipherment) - case "dataencipherment": - return _keyUsage(x509.KeyUsageDataEncipherment) - case "keyagreement": - return _keyUsage(x509.KeyUsageKeyAgreement) - case "certsign": - return _keyUsage(x509.KeyUsageCertSign) - case "crlsign": - return _keyUsage(x509.KeyUsageCRLSign) - case "encipheronly": - return _keyUsage(x509.KeyUsageEncipherOnly) - case "decipheronly": - return _keyUsage(x509.KeyUsageDecipherOnly) - - case "any": - return _extKeyUsage(x509.ExtKeyUsageAny) - case "serverauth": - return _extKeyUsage(x509.ExtKeyUsageServerAuth) - case "clientauth": - return _extKeyUsage(x509.ExtKeyUsageClientAuth) - case "codesigning": - return _extKeyUsage(x509.ExtKeyUsageCodeSigning) - case "emailprotection": - return _extKeyUsage(x509.ExtKeyUsageEmailProtection) - case "ipsecendsystem": - return _extKeyUsage(x509.ExtKeyUsageIPSECEndSystem) - case "ipsectunnel": - return _extKeyUsage(x509.ExtKeyUsageIPSECTunnel) - case "ipsecuser": - return _extKeyUsage(x509.ExtKeyUsageIPSECUser) - case "timestamping": - return _extKeyUsage(x509.ExtKeyUsageTimeStamping) - case "ocspsigning": - return _extKeyUsage(x509.ExtKeyUsageOCSPSigning) - case "microsoftservergatedcrypto": - return _extKeyUsage(x509.ExtKeyUsageMicrosoftServerGatedCrypto) - case "netscapeservergatedcrypto": - return _extKeyUsage(x509.ExtKeyUsageNetscapeServerGatedCrypto) - case "microsoftcommercialcodesigning": - return _extKeyUsage(x509.ExtKeyUsageMicrosoftCommercialCodeSigning) - case "microsoftkernelcodesigning": - return _extKeyUsage(x509.ExtKeyUsageMicrosoftKernelCodeSigning) - } - return nil -} - -func GetKeyUsage(opt interface{}) KeyUsage { - switch o := opt.(type) { - case x509.ExtKeyUsage: - return _extKeyUsage(o) - case x509.KeyUsage: - return _keyUsage(o) - case string: - return ParseKeyUsage(o) - default: - return nil - } -} diff --git a/pkg/signing/types.go b/pkg/signing/types.go deleted file mode 100644 index 47e84ac46..000000000 --- a/pkg/signing/types.go +++ /dev/null @@ -1,106 +0,0 @@ -package signing - -import ( - "crypto" - "crypto/x509/pkix" - "encoding/json" - "hash" - - "github.com/sirupsen/logrus" - - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/signing/signutils" -) - -type SigningContext interface { - GetHash() crypto.Hash - GetPrivateKey() signutils.GenericPrivateKey - GetPublicKey() signutils.GenericPublicKey - GetRootCerts() signutils.GenericCertificatePool - GetIssuer() *pkix.Name -} - -type DefaultSigningContext struct { - Hash crypto.Hash - PrivateKey signutils.GenericPrivateKey - PublicKey signutils.GenericPublicKey - RootCerts signutils.GenericCertificatePool - Issuer *pkix.Name -} - -var _ SigningContext = (*DefaultSigningContext)(nil) - -func (d *DefaultSigningContext) GetHash() crypto.Hash { - return d.Hash -} - -func (d *DefaultSigningContext) GetPrivateKey() signutils.GenericPrivateKey { - return d.PrivateKey -} - -func (d *DefaultSigningContext) GetPublicKey() signutils.GenericPublicKey { - return d.PublicKey -} - -func (d *DefaultSigningContext) GetRootCerts() signutils.GenericCertificatePool { - return d.RootCerts -} - -func (d *DefaultSigningContext) GetIssuer() *pkix.Name { - return d.Issuer -} - -type Signature struct { - Value string - MediaType string - Algorithm string - Issuer string -} - -func (s *Signature) String() string { - data, err := json.Marshal(s) //nolint:musttag // only for string output - if err != nil { - logrus.Error(err) - } - - return string(data) -} - -// Signer interface is used to implement different signing algorithms. -// Each Signer should have a matching Verifier. -type Signer interface { - // Sign returns the signature for the given digest. - // If known a given public key can be passed. The signer may - // decide to put a trusted public key into the signature, - // for example for public keys provided by organization validated - // certificates. - // If used the key and/or certificate must be validated, for certificates - // the distinguished name must match the issuer. - Sign(cctx credentials.Context, digest string, sctx SigningContext) (*Signature, error) - // Algorithm is the name of the finally used signature algorithm. - // A signer might be registered using a logical name, so there might - // be multiple signer registration providing the same signature algorithm - Algorithm() string -} - -// Verifier interface is used to implement different verification algorithms. -// Each Verifier should have a matching Signer. -type Verifier interface { - // Verify checks the signature, returns an error on verification failure - Verify(digest string, sig *Signature, sctx SigningContext) error - Algorithm() string -} - -// SignatureHandler can create and verify signature of a dedicated type. -type SignatureHandler interface { - Algorithm() string - Signer - Verifier -} - -// Hasher creates a new hash.Hash interface. -type Hasher interface { - Algorithm() string - Create() hash.Hash - Crypto() crypto.Hash -} diff --git a/pkg/spiff/options.go b/pkg/spiff/options.go deleted file mode 100644 index 48d8d6a53..000000000 --- a/pkg/spiff/options.go +++ /dev/null @@ -1,166 +0,0 @@ -package spiff - -import ( - "fmt" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/sliceutils" - "github.com/mandelsoft/spiff/spiffing" - "github.com/mandelsoft/vfs/pkg/cwdfs" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/contexts/datacontext" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/vfsattr" - "github.com/open-component-model/ocm/pkg/utils" -) - -type Option interface { - ApplyToRequest(r *Request) error -} - -type Options []Option - -func (o *Options) Add(opt Option) *Options { - if opt != nil { - *o = append(*o, opt) - } - return o -} - -func (o Options) ApplyToRequest(r *Request) error { - for _, o := range o { - if o != nil { - err := o.ApplyToRequest(r) - if err != nil { - return err - } - } - } - return nil -} - -func GetRequest(opts ...Option) (*Request, error) { - req := &Request{Mode: spiffing.MODE_DEFAULT} - err := Options(opts).ApplyToRequest(req) - if err != nil { - return nil, err - } - return req, nil -} - -type OptionFunction func(r *Request) error - -func (f OptionFunction) ApplyToRequest(r *Request) error { - return f(r) -} - -func FileSystem(fs vfs.FileSystem) OptionFunction { - return func(r *Request) error { - r.FileSystem = utils.FileSystem(fs) - return nil - } -} - -func Context(ctx datacontext.Context) OptionFunction { - return FileSystem(vfsattr.Get(ctx)) -} - -func Values(values interface{}) OptionFunction { - return func(r *Request) error { - r.Values = values - return nil - } -} - -func Functions(functions spiffing.Functions) OptionFunction { - return func(r *Request) error { - r.Functions = functions - return nil - } -} - -func ValuesNode(values string) OptionFunction { - return func(r *Request) error { - r.ValuesNode = values - return nil - } -} - -func StubData(name string, data []byte) OptionFunction { - return func(r *Request) error { - if len(data) > 0 { - r.Stubs = append(r.Stubs, spiffing.NewSourceData(name, data)) - } - return nil - } -} - -func TemplateData(name string, data []byte) OptionFunction { - return func(r *Request) error { - if len(data) == 0 { - return fmt.Errorf("no template data for " + name) - } - r.Template = spiffing.NewSourceData(name, data) - return nil - } -} - -func StubFile(path string, fss ...vfs.FileSystem) OptionFunction { - return func(r *Request) error { - r.Stubs = append(r.Stubs, spiffing.NewSourceFile(path, utils.FileSystem(sliceutils.CopyAppend(fss, r.FileSystem)...))) - return nil - } -} - -func TemplateFile(path string, fss ...vfs.FileSystem) OptionFunction { - return func(r *Request) error { - r.Template = spiffing.NewSourceFile(path, utils.FileSystem(sliceutils.CopyAppend(fss, r.FileSystem)...)) - return nil - } -} - -func WorkDir(path string) OptionFunction { - return func(r *Request) error { - fs, err := cwdfs.New(r.FileSystem, path) - if err != nil { - return errors.Wrapf(err, "cannot set working directory %s", path) - } - r.FileSystem = fs - return nil - } -} - -func Mode(m int) OptionFunction { - return func(r *Request) error { - r.Mode = m - return nil - } -} - -func Validated(schemedata []byte, opts ...Option) Option { - if schemedata == nil { - return Options(opts) - } - return OptionFunction(func(r *Request) error { - tmp := *r - tmp.Template = nil - tmp.Stubs = nil - err := Options(opts).ApplyToRequest(&tmp) - if err != nil { - return err - } - if tmp.Template != nil { - err = ValidateSourceByScheme(tmp.Template, schemedata) - if err != nil { - return errors.Wrapf(err, "template %s", tmp.Template.Name()) - } - } - for _, s := range tmp.Stubs { - err = ValidateSourceByScheme(s, schemedata) - if err != nil { - return errors.Wrapf(err, "validating %s", s.Name()) - } - } - return Options(opts).ApplyToRequest(r) - }) -} diff --git a/pkg/testutils/package_test.go b/pkg/testutils/package_test.go deleted file mode 100644 index 8e5805c70..000000000 --- a/pkg/testutils/package_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package testutils_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - me "github.com/open-component-model/ocm/pkg/testutils" -) - -var _ = Describe("package tests", func() { - It("go module name", func() { - mod := me.Must(me.GetModuleName()) - Expect(mod).To(Equal("github.com/open-component-model/ocm")) - }) -}) diff --git a/pkg/testutils/signing_test.go b/pkg/testutils/signing_test.go deleted file mode 100644 index abfe337f4..000000000 --- a/pkg/testutils/signing_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package testutils_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/testutils" -) - -var _ = Describe("normalization", func() { - It("compares with substitution variables", func() { - exp := "A ${TEST}." - res := "A testcase." - vars := common.Properties{ - "TEST": "testcase", - } - Expect(res).To(testutils.StringEqualTrimmedWithContext(exp, common.Properties{}, vars)) - Expect(res).To(testutils.StringEqualTrimmedWithContext(exp, vars, common.Properties{})) - }) -}) diff --git a/pkg/testutils/utils.go b/pkg/testutils/utils.go deleted file mode 100644 index ab71d423d..000000000 --- a/pkg/testutils/utils.go +++ /dev/null @@ -1,157 +0,0 @@ -package testutils - -import ( - "encoding/json" - "fmt" - "io" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/onsi/gomega/types" - - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -func Close(c io.Closer, msg ...interface{}) { - DeferWithOffset(1, c.Close, msg...) -} - -func Defer(f func() error, msg ...interface{}) { - DeferWithOffset(1, f, msg...) -} - -func DeferWithOffset(o int, f func() error, msg ...interface{}) { - err := f() - if err != nil { - switch len(msg) { - case 0: - ExpectWithOffset(1+o, err).To(Succeed()) - case 1: - Fail(fmt.Sprintf("%s: %s", msg[0], err), 1+o) - default: - Fail(fmt.Sprintf("%s: %s", fmt.Sprintf(msg[0].(string), msg[1:]...), err), 1+o) - } - } -} - -func NotNil[T any](o T, extra ...interface{}) T { - ExpectWithOffset(1, o, extra...).NotTo(BeNil()) - return o -} - -func Must[T any](o T, err error) T { - ExpectWithOffset(1, err).To(Succeed()) - return o -} - -func Must2[T any, V any](a T, b V, err error) (T, V) { - ExpectWithOffset(1, err).To(Succeed()) - return a, b -} - -func Must3[T, U, V any](a T, b U, c V, err error) (T, U, V) { - ExpectWithOffset(1, err).To(Succeed()) - return a, b, c -} - -type result[T any] struct { - res T - err error -} - -func (r result[T]) Must(offset ...int) T { - ExpectWithOffset(utils.Optional(offset...)+1, r.err).To(Succeed()) - return r.res -} - -func R[T any](o T, err error) result[T] { - return Calling(o, err) -} - -func Calling[T any](o T, err error) result[T] { - return result[T]{o, err} -} - -func MustWithOffset[T any](offset int, res result[T]) T { - ExpectWithOffset(offset+1, res.err).To(Succeed()) - return res.res -} - -func MustBeNonNil[T any](o T) T { - ExpectWithOffset(1, o).NotTo(BeNil()) - return o -} - -func MustBeSuccessful(actual ...interface{}) { - if actual[len(actual)-1] == nil { - return - } - err, ok := actual[len(actual)-1].(error) - if !ok { - Fail("no errors return", 1) - } - ExpectWithOffset(1, err).To(Succeed()) -} - -func MustBeSuccessfulWithOffset(offset int, err error) { - ExpectWithOffset(offset+1, err).To(Succeed()) -} - -func MustFailWithMessage(err error, msg string) { - ExpectWithOffset(1, err).To(HaveOccurred()) - ExpectWithOffset(1, err.Error()).To(Equal(msg)) -} - -func ErrorFrom(args ...interface{}) error { - e, ok := args[len(args)-1].(error) - if !ok { - Fail("no errors return", 1) - } - return e -} - -func ExpectError(values ...interface{}) types.Assertion { - return Expect(values[len(values)-1]) -} - -func AsString(actual interface{}) (string, error) { - s, ok := actual.(string) - if !ok { - b, ok := actual.([]byte) - if !ok { - return "", fmt.Errorf("Actual value is no string (or byte array), but a %T.", actual) - } - s = string(b) - } - return s, nil -} - -func AsStructure(actual interface{}, substs ...Substitutions) (interface{}, error) { - var err error - - s, ok := actual.(string) - if !ok { - b, ok := actual.([]byte) - if !ok { - b, err = json.Marshal(actual) - if err != nil { - return "", fmt.Errorf("Actual value (%T) is no string, byte array, or serializable object.", actual) - } - } - s = string(b) - } - if subst := MergeSubst(substs...); len(subst) != 0 { - s, err = eval(s, subst) - if err != nil { - return nil, err - } - } - var value interface{} - err = runtime.DefaultYAMLEncoding.Unmarshal([]byte(s), &value) - if err != nil { - return nil, err - } - return value, nil -} diff --git a/pkg/toi/drivers/default/driver.go b/pkg/toi/drivers/default/driver.go deleted file mode 100644 index f6e9b92d2..000000000 --- a/pkg/toi/drivers/default/driver.go +++ /dev/null @@ -1,10 +0,0 @@ -package _default - -import ( - "github.com/open-component-model/ocm/pkg/toi/drivers/docker" - "github.com/open-component-model/ocm/pkg/toi/install" -) - -var New = func() install.Driver { - return &docker.Driver{} -} diff --git a/pkg/toi/drivers/docker/driver.go b/pkg/toi/drivers/docker/driver.go deleted file mode 100644 index 1367f092e..000000000 --- a/pkg/toi/drivers/docker/driver.go +++ /dev/null @@ -1,566 +0,0 @@ -package docker - -import ( - "archive/tar" - "context" - "fmt" - "io" - "os" - unix_path "path" - "strconv" - "strings" - "sync" - - "github.com/distribution/reference" - "github.com/docker/cli/cli/command" - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/image" - registrytypes "github.com/docker/docker/api/types/registry" - "github.com/docker/docker/client" - "github.com/docker/docker/pkg/jsonmessage" - "github.com/docker/docker/pkg/stdcopy" - "github.com/docker/docker/registry" - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/set" - "github.com/mitchellh/copystructure" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/toi" - "github.com/open-component-model/ocm/pkg/toi/install" -) - -const ( - OptionQuiet = "DOCKER_DRIVER_QUIET" - OptionCleanup = "CLEANUP_CONTAINERS" - OptionPullPolicy = "PULL_POLICY" - PullPolicyAlways = "Always" - PullPolicyNever = "Never" - PullPolicyIfNotPresent = "IfNotPresent" - OptionNetworkMode = "NETWORK_MODE" - OptionUsernsMode = "USERNS_MODE" - - trueAsString = "true" - falseAsString = "false" -) - -var Options = set.New[string]( - OptionQuiet, - OptionCleanup, - OptionPullPolicy, - OptionNetworkMode, - OptionUsernsMode, -) - -// Driver is capable of running Docker invocation images using Docker itself. -type Driver struct { - config map[string]string - // If true, this will not actually run Docker - Simulate bool - dockerCli command.Cli - dockerConfigurationOptions []ConfigurationOption - containerOut io.Writer - containerErr io.Writer - containerHostCfg container.HostConfig - containerCfg container.Config -} - -var _ install.Driver = (*Driver)(nil) - -func New() install.Driver { - return &Driver{} -} - -// GetContainerConfig returns a copy of the container configuration -// used by the driver during container exec. -func (d *Driver) GetContainerConfig() (container.Config, error) { - cpy, err := copystructure.Copy(d.containerCfg) - if err != nil { - return container.Config{}, err - } - - cfg, ok := cpy.(container.Config) - if !ok { - return container.Config{}, errors.New("unable to process container config") - } - - return cfg, nil -} - -// GetContainerHostConfig returns a copy of the container host configuration -// used by the driver during container exec. -func (d *Driver) GetContainerHostConfig() (container.HostConfig, error) { - cpy, err := copystructure.Copy(d.containerHostCfg) - if err != nil { - return container.HostConfig{}, err - } - - cfg, ok := cpy.(container.HostConfig) - if !ok { - return container.HostConfig{}, errors.New("unable to process container host config") - } - - return cfg, nil -} - -// SetConfig sets Docker driver configuration. -func (d *Driver) SetConfig(settings map[string]string) error { - // Set default and provide feedback on acceptable input values. - value, ok := settings[OptionCleanup] - if !ok { - settings[OptionCleanup] = trueAsString - } else if value != trueAsString && value != falseAsString { - return fmt.Errorf("config variable %s has unexpected value %q. Supported values are 'true', 'false', or unset", OptionCleanup, value) - } - - value, ok = settings[OptionPullPolicy] - if ok { - if value != PullPolicyAlways && value != PullPolicyIfNotPresent && value != PullPolicyNever { - return fmt.Errorf("config variable %s has unexpected value %q. Supported values are '%s', '%s', '%s' , or unset", OptionPullPolicy, value, PullPolicyAlways, PullPolicyIfNotPresent, PullPolicyNever) - } - } - - value, ok = settings[OptionNetworkMode] - if ok { - m := container.NetworkMode(value) - d.dockerConfigurationOptions = append(d.dockerConfigurationOptions, NetworkModeOpt(value)) - if _, ok := settings[OptionUsernsMode]; m.IsHost() && !ok { - settings[OptionUsernsMode] = "host" - } - } - - value, ok = settings[OptionUsernsMode] - if ok { - d.dockerConfigurationOptions = append(d.dockerConfigurationOptions, UsernsModeOpt(value)) - } - - d.config = settings - return nil -} - -// SetDockerCli makes the driver use an already initialized cli. -func (d *Driver) SetDockerCli(dockerCli command.Cli) { - d.dockerCli = dockerCli -} - -// SetContainerOut sets the container output stream. -func (d *Driver) SetContainerOut(w io.Writer) { - d.containerOut = w -} - -// SetContainerErr sets the container error stream. -func (d *Driver) SetContainerErr(w io.Writer) { - d.containerErr = w -} - -func pullImage(ctx context.Context, cli command.Cli, imageName string) error { - ref, err := reference.ParseNormalizedNamed(imageName) - if err != nil { - return fmt.Errorf("unable to parse normalized name: %w", err) - } - - // Resolve the Repository name from fqn to RepositoryInfo - repoInfo, err := registry.ParseRepositoryInfo(ref) - if err != nil { - return fmt.Errorf("unable to parse repository info: %w", err) - } - - authConfig := command.ResolveAuthConfig(cli.ConfigFile(), repoInfo.Index) - - encodedAuth, err := registrytypes.EncodeAuthConfig(authConfig) - if err != nil { - return fmt.Errorf("unable encode auth: %w", err) - } - - options := image.PullOptions{ - RegistryAuth: encodedAuth, - } - - responseBody, err := cli.Client().ImagePull(ctx, imageName, options) - if err != nil { - return fmt.Errorf("unable to pull image: %w", err) - } - - defer responseBody.Close() - - // passing isTerm = false here because of https://github.com/Nvveen/Gotty/pull/1 - err = jsonmessage.DisplayJSONMessagesStream(responseBody, cli.Out(), cli.Out().FD(), false, nil) - if err != nil { - return fmt.Errorf("unable to display json message: %w", err) - } - - return nil -} - -func (d *Driver) initializeDockerCli() (command.Cli, error) { - if d.config == nil { - d.config = map[string]string{} - } - - if d.dockerCli != nil { - return d.dockerCli, nil - } - - cli, err := GetDockerClient() - if err != nil { - return nil, err - } - - if d.config[OptionQuiet] == "1" { - err = cli.Apply(command.WithCombinedStreams(io.Discard)) - if err != nil { - return nil, err - } - } - - d.dockerCli = cli - return cli, nil -} - -func (d *Driver) Exec(op *install.Operation) (*install.OperationResult, error) { - ctx := context.Background() - - cli, err := d.initializeDockerCli() - if err != nil { - return nil, err - } - - if d.Simulate { - return nil, nil - } - if d.config[OptionPullPolicy] == PullPolicyAlways { - if err := pullImage(ctx, cli, op.Image.Ref); err != nil { - return nil, err - } - } - - ii, err := d.inspectImage(ctx, op.Image.Ref) - if err != nil { - return nil, err - } - - err = d.validateImageDigest(op.Image, ii.RepoDigests) - if err != nil { - return nil, errors.Wrap(err, "image digest validation failed") - } - - if err := d.setConfigurationOptions(op); err != nil { - return nil, err - } - - resp, err := cli.Client().ContainerCreate(ctx, &d.containerCfg, &d.containerHostCfg, nil, nil, "") - if err != nil { - return nil, fmt.Errorf("cannot create container: %w", err) - } - - if d.config[OptionCleanup] == trueAsString { - defer cli.Client().ContainerRemove(ctx, resp.ID, container.RemoveOptions{}) - } - - containerUID := getContainerUserID(ii.Config.User) - tarContent, done, err := generateTar(op.Files, containerUID) - if err != nil { - return nil, fmt.Errorf("error staging files: %w", err) - } - options := container.CopyToContainerOptions{ - AllowOverwriteDirWithFile: false, - } - // This copies the tar to the root of the container. The tar has been assembled using the - // path from the given file, starting at the /. - err = cli.Client().CopyToContainer(ctx, resp.ID, "/", tarContent, options) - if err != nil { - return nil, fmt.Errorf("error copying to / in container: %w", err) - } - - if err = done(); err != nil { - return nil, fmt.Errorf("unable to send data: %w", err) - } - tarContent.Close() - - attach, err := cli.Client().ContainerAttach(ctx, resp.ID, container.AttachOptions{ - Stream: true, - Stdout: true, - Stderr: true, - Logs: true, - }) - if err != nil { - return nil, fmt.Errorf("unable to retrieve logs: %w", err) - } - var ( - stdout io.Writer = os.Stdout - stderr io.Writer = os.Stderr - ) - if d.containerOut != nil { - stdout = d.containerOut - } else if op.Out != nil { - stdout = op.Out - } - if d.containerErr != nil { - stderr = d.containerErr - } else if op.Err != nil { - stderr = op.Err - } - go func() { - defer attach.Close() - for { - _, err = stdcopy.StdCopy(stdout, stderr, attach.Reader) - if err != nil { - break - } - } - }() - - if err = cli.Client().ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil { - return nil, fmt.Errorf("cannot start container: %w", err) - } - statusc, errc := cli.Client().ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning) - select { - case err := <-errc: - if err != nil { - opResult, fetchErr := d.fetchOutputs(ctx, resp.ID, op) - return opResult, containerError("error in container", err, fetchErr) - } - case s := <-statusc: - if s.StatusCode == 0 { - return d.fetchOutputs(ctx, resp.ID, op) - } - if s.Error != nil { - opResult, fetchErr := d.fetchOutputs(ctx, resp.ID, op) - return opResult, containerError(fmt.Sprintf("container exit code: %d", s.StatusCode), err, fetchErr) - } - opResult, fetchErr := d.fetchOutputs(ctx, resp.ID, op) - return opResult, containerError(fmt.Sprintf("container exit code: %d", s.StatusCode), err, fetchErr) - } - opResult, fetchErr := d.fetchOutputs(ctx, resp.ID, op) - if fetchErr != nil { - return opResult, fmt.Errorf("fetching outputs failed: %w", fetchErr) - } - return opResult, err -} - -// getContainerUserID determines the user id that the container will execute as -// based on the image's configured user. Defaults to 0 (root) if a user id is not set. -func getContainerUserID(user string) int { - if user != "" { - // Only look at the user, strip off a group if one was specified with USER uid:gid - if uid, err := strconv.Atoi(strings.Split(user, ":")[0]); err == nil { - return uid - } - } - return 0 -} - -// ApplyConfigurationOptions applies the configuration options set on the driver by the user. -func (d *Driver) ApplyConfigurationOptions() error { - for _, opt := range d.dockerConfigurationOptions { - if err := opt(&d.containerCfg, &d.containerHostCfg); err != nil { - return fmt.Errorf("unable to apply docker configuration: %w", err) - } - } - - return nil -} - -// setConfigurationOptions initializes the container and host configuration options on the driver, -// combining the default configuration with any overrides set by the user. -func (d *Driver) setConfigurationOptions(op *install.Operation) error { - var env []string - for k, v := range op.Environment { - env = append(env, fmt.Sprintf("%s=%v", k, v)) - } - - d.containerCfg = container.Config{ - Image: op.Image.Ref, - Env: env, - Cmd: []string{op.Action, op.ComponentVersion}, - AttachStderr: true, - AttachStdout: true, - } - - d.containerHostCfg = container.HostConfig{} - - if err := d.ApplyConfigurationOptions(); err != nil { - return fmt.Errorf("failed to apply configuration: %w", err) - } - - return nil -} - -func containerError(containerMessage string, containerErr, fetchErr error) error { - if fetchErr != nil { - return errors.NewEf(containerErr, "%s: %v. fetching outputs failed", containerMessage, fetchErr) - } - return errors.NewEf(containerErr, "%s", containerMessage) -} - -// fetchOutputs takes a context and a container ID; it copies the PathOutputs directory from that container. -// The goal is to collect all the files in the directory (recursively) and put them in a flat map of path to contents. -// This map will be inside the OperationResult. When fetchOutputs returns an error, it may also return partial results. -func (d *Driver) fetchOutputs(ctx context.Context, container string, op *install.Operation) (*install.OperationResult, error) { - opResult := &install.OperationResult{ - Outputs: map[string][]byte{}, - } - // The PathOutputs directory probably only exists if outputs are created. In the - // case there are no outputs defined on the operation, there probably are none to copy - // and we should return early. - if len(op.Outputs) == 0 { - return opResult, nil - } - ioReader, _, err := d.dockerCli.Client().CopyFromContainer(ctx, container, install.PathOutputs) - if err != nil { - return nil, fmt.Errorf("error copying outputs from container: %w", err) - } - tarReader := tar.NewReader(ioReader) - header, err := tarReader.Next() - // io.EOF pops us out of loop on successful run. - for err == nil { - // skip directories because we're gathering file contents - if header.FileInfo().IsDir() { - header, err = tarReader.Next() - continue - } - - var contents []byte - // CopyFromContainer strips prefix above outputs directory. - name := strings.TrimPrefix(header.Name, "outputs/") - outputName, shouldCapture := op.Outputs[name] - if shouldCapture { - contents, err = io.ReadAll(tarReader) - if err != nil { - return opResult, fmt.Errorf("error while reading %q from outputs tar: %w", header.Name, err) - } - opResult.Outputs[outputName] = contents - } - - header, err = tarReader.Next() - } - - if !errors.Is(err, io.EOF) { - return opResult, err - } - - return opResult, nil -} - -// generateTar creates a tarfile containing the specified files, with the owner -// set to the uid that the container runs as so that it is guaranteed to have -// read access to the files we copy into the container. -func generateTar(files map[string]blobaccess.BlobAccess, uid int) (io.ReadCloser, func() error, error) { - r, w := io.Pipe() - tw := tar.NewWriter(w) - for path := range files { - if unix_path.IsAbs(path) { - return nil, nil, fmt.Errorf("destination path %s should be a relative unix path", path) - } - } - var err error - wg := sync.WaitGroup{} - wg.Add(1) - toi.Log.Info("waiting for successful data transfer") - go func() { - defer wg.Done() - defer w.Close() - - have := map[string]bool{} - for path, content := range files { - path = unix_path.Join(install.PathInputs, path) - toi.Log.Info("transferring", "path", path) - // Write a header for the parent directories so that newly created intermediate directories are accessible by the user - dir := path - for dir != "/" { - dir = unix_path.Dir(dir) - if !have[dir] { - dirHdr := &tar.Header{ - Typeflag: tar.TypeDir, - Name: dir, - Mode: 0o700, - Uid: uid, - Size: 0, - } - tw.WriteHeader(dirHdr) - have[dir] = true - } - } - - // Grant access to just the owner (container user), so that files can be read by the container - fildHdr := &tar.Header{ - Typeflag: tar.TypeReg, - Name: path, - Mode: 0o600, - Size: content.Size(), - Uid: uid, - } - tw.WriteHeader(fildHdr) - reader, e := content.Reader() - if e != nil { - toi.Log.LogError(err, "cannot transfer", "path", path) - err = e - return - } - io.Copy(tw, reader) - } - }() - return r, func() error { wg.Wait(); return err }, nil -} - -// ConfigurationOption is an option used to customize docker driver container and host config. -type ConfigurationOption func(*container.Config, *container.HostConfig) error - -// inspectImage inspects the operation image and returns an object of types.ImageInspect, -// pulling the image if not found locally. -func (d *Driver) inspectImage(ctx context.Context, image string) (types.ImageInspect, error) { - ii, _, err := d.dockerCli.Client().ImageInspectWithRaw(ctx, image) - switch { - case client.IsErrNotFound(err): - fmt.Fprintf(d.dockerCli.Err(), "Unable to find image '%s' locally\n", image) - if d.config[OptionPullPolicy] == PullPolicyNever { - return ii, errors.Wrapf(err, "image %s not found", image) - } - if err := pullImage(ctx, d.dockerCli, image); err != nil { - return ii, err - } - if ii, _, err = d.dockerCli.Client().ImageInspectWithRaw(ctx, image); err != nil { - return ii, errors.Wrapf(err, "cannot inspect image %s", image) - } - case err != nil: - return ii, errors.Wrapf(err, "cannot inspect image %s", image) - } - - return ii, nil -} - -// validateImageDigest validates the operation image digest, if exists, against -// the supplied repoDigests. -func (d *Driver) validateImageDigest(image toi.Image, repoDigests []string) error { - if image.Digest == "" { - return nil - } - - if len(repoDigests) == 0 { - return fmt.Errorf("image %s has no repo digests", image) - } - - for _, repoDigest := range repoDigests { - // RepoDigests are of the form 'imageName@sha256:' or imageName: - // We only care about the ones in digest form - ref, err := reference.ParseNormalizedNamed(repoDigest) - if err != nil { - return fmt.Errorf("unable to parse repo digest %s", repoDigest) - } - - digestRef, ok := ref.(reference.Digested) - if !ok { - continue - } - - digest := digestRef.Digest().String() - - // image.Digest is the digest of the original invocation image defined in the bundle. - // It persists even when the bundle's invocation image has been relocated. - if digest == image.Digest { - return nil - } - } - - return fmt.Errorf("content digest mismatch: image %s was defined with the digest %s, but no matching repoDigest was found upon inspecting the image", image.Ref, image.Digest) -} diff --git a/pkg/toi/drivers/filesystem/driver.go b/pkg/toi/drivers/filesystem/driver.go deleted file mode 100644 index 7abd65804..000000000 --- a/pkg/toi/drivers/filesystem/driver.go +++ /dev/null @@ -1,92 +0,0 @@ -package filesystem - -import ( - "io" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/finalizer" - "github.com/mandelsoft/vfs/pkg/osfs" - "github.com/mandelsoft/vfs/pkg/vfs" - "sigs.k8s.io/yaml" - - "github.com/open-component-model/ocm/pkg/toi/install" -) - -const OptionTargetPath = "TARGET_PATH" - -// Driver is capable of running Docker invocation images using Docker itself. -type Driver struct { - config map[string]string - Simulate bool - TargetPath string - Filesystem vfs.FileSystem -} - -var _ install.Driver = (*Driver)(nil) - -func New(fs vfs.FileSystem) install.Driver { - if fs == nil { - fs = osfs.New() - } - return &Driver{Filesystem: fs} -} - -// SetConfig sets Docker driver configuration. -func (d *Driver) SetConfig(settings map[string]string) error { - if settings != nil { - d.TargetPath = settings[OptionTargetPath] - } - - if d.TargetPath == "" { - d.TargetPath = "toi" - } - d.config = settings - return nil -} - -func (d *Driver) Exec(op *install.Operation) (*install.OperationResult, error) { - if d.Simulate { - return nil, nil - } - - err := d.Filesystem.MkdirAll(d.TargetPath, 0o700) - if err != nil { - return nil, errors.Wrapf(err, "creating target path") - } - - var finalize finalizer.Finalizer - defer finalize.Finalize() - - for k, v := range op.Files { - n := vfs.Join(d.Filesystem, d.TargetPath, install.Inputs, k) - err := d.Filesystem.MkdirAll(vfs.Dir(d.Filesystem, n), 0o700) - if err != nil { - return nil, errors.Wrapf(err, "creating directory for file %q", k) - } - r, err := v.Reader() - if err != nil { - return nil, errors.Wrapf(err, "reading data for %q", k) - } - finalize.Close(r) - file, err := d.Filesystem.OpenFile(n, vfs.O_TRUNC|vfs.O_CREATE|vfs.O_WRONLY, 0o600) - if err != nil { - return nil, errors.Wrapf(err, "writing file %q", n) - } - finalize.Close(file) - _, err = io.Copy(file, r) - if err != nil { - return nil, errors.Wrapf(err, "writing %q", n) - } - finalize.Finalize() - } - props := map[string]string{} - props["image"] = op.Image.String() - props["componentVersion"] = op.ComponentVersion - props["action"] = op.Action - data, err := yaml.Marshal(props) - if err != nil { - return nil, errors.Wrapf(err, "writing operation properties") - } - vfs.WriteFile(d.Filesystem, vfs.Join(d.Filesystem, d.TargetPath, "properties"), data, 0o600) - return &install.OperationResult{}, nil -} diff --git a/pkg/toi/drivers/mock/driver.go b/pkg/toi/drivers/mock/driver.go deleted file mode 100644 index a2fb70333..000000000 --- a/pkg/toi/drivers/mock/driver.go +++ /dev/null @@ -1,27 +0,0 @@ -package mock - -import ( - "github.com/open-component-model/ocm/pkg/toi/install" - "github.com/open-component-model/ocm/pkg/utils" -) - -type Driver struct { - handler func(*install.Operation) (*install.OperationResult, error) -} - -var _ install.Driver = (*Driver)(nil) - -func New(handler ...func(*install.Operation) (*install.OperationResult, error)) install.Driver { - return &Driver{utils.Optional(handler...)} -} - -func (d *Driver) SetConfig(props map[string]string) error { - return nil -} - -func (d *Driver) Exec(op *install.Operation) (*install.OperationResult, error) { - if d.handler != nil { - return d.handler(op) - } - return &install.OperationResult{}, nil -} diff --git a/pkg/toi/install/action_test.go b/pkg/toi/install/action_test.go deleted file mode 100644 index 3c896af82..000000000 --- a/pkg/toi/install/action_test.go +++ /dev/null @@ -1,294 +0,0 @@ -package install_test - -import ( - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/env" - . "github.com/open-component-model/ocm/pkg/env/builder" - - "github.com/mandelsoft/vfs/pkg/memoryfs" - - "github.com/open-component-model/ocm/pkg/common" - "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/identity/hostpath" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - "github.com/open-component-model/ocm/pkg/mime" - "github.com/open-component-model/ocm/pkg/toi" - "github.com/open-component-model/ocm/pkg/toi/drivers/mock" - "github.com/open-component-model/ocm/pkg/toi/install" -) - -const ( - COMPONENT = "acme.org/test" - VERSION = "0.1.0" -) - -type Driver struct { - install.Driver - Found *install.Operation -} - -func NewDriver() *Driver { - driver := &Driver{} - driver.Driver = mock.New(func(op *install.Operation) (*install.OperationResult, error) { - driver.Found = op - return &install.OperationResult{}, nil - }) - return driver -} - -var _ = Describe("Transfer handler", func() { - var env *Builder - var driver *Driver - - cid1 := credentials.NewConsumerIdentity("test", hostpath.ID_HOSTNAME, "test.de") - creds1 := credentials.NewCredentials(common.Properties{"user": "test", "password": "pw"}) - - BeforeEach(func() { - env = NewBuilder(FileSystem(memoryfs.New(), "")) - - env.OCMCommonTransport("ctf", accessio.FormatDirectory, func() { - env.ComponentVersion(COMPONENT, VERSION, func() { - env.Provider("acme.org") - env.Resource("package", VERSION, toi.TypeTOIPackage, v1.LocalRelation, func() { - env.BlobData(mime.MIME_YAML, []byte("")) - }) - }) - }) - - driver = NewDriver() - }) - - It("gets credentials", func() { - env.CredentialsContext().SetCredentialsForConsumer(cid1, creds1) - - c := Must(credentials.CredentialsForConsumer(env.OCMContext().CredentialsContext(), cid1)) - Expect(c.Properties()).To(Equal(creds1.Properties())) - }) - - It("executes with credential substitution", func() { - env.CredentialsContext().SetCredentialsForConsumer(cid1, creds1) - - p, _ := common.NewBufferedPrinter() - - mapping := ` -testparam: (( merge )) -creds: (( hasCredentials("mycred") ? [getCredentials("mycred")] :[] )) -` - spec := &toi.PackageSpecification{ - CredentialsRequest: toi.CredentialsRequest{ - Credentials: map[string]toi.CredentialsRequestSpec{ - "mycred": { - ConsumerId: cid1, - Description: "test", - Optional: false, - }, - }, - }, - Executors: []toi.Executor{ - { - Actions: []string{"install"}, - Image: &toi.Image{ - Ref: "a/b:v1", - }, - ParameterMapping: []byte(mapping), - }, - }, - } - - credspec := &toi.Credentials{ - Credentials: map[string]toi.CredentialSpec{ - "mycred": { - ConsumerId: cid1, - }, - }, - } - - params := ` -testparam: value -` - - repo := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, "/ctf", 0, env)) - defer Close(repo) - cv := Must(repo.LookupComponentVersion(COMPONENT, VERSION)) - defer Close(cv) - - Must(install.ExecuteAction(p, driver, "install", spec, credspec, []byte(params), env, cv, nil)) - - effparams := Must(driver.Found.Files[install.InputParameters].Get()) - Expect(string(effparams)).To(StringEqualTrimmedWithContext(` -creds: -- password: pw - user: test -testparam: value -`)) - }) - - It("executes with credential property substitution", func() { - env.CredentialsContext().SetCredentialsForConsumer(cid1, creds1) - - p, _ := common.NewBufferedPrinter() - - mapping := ` -testparam: (( merge )) -creds: (( hasCredentials("mycred") ? getCredentials("mycred", "user") :"" )) -` - spec := &toi.PackageSpecification{ - CredentialsRequest: toi.CredentialsRequest{ - Credentials: map[string]toi.CredentialsRequestSpec{ - "mycred": { - ConsumerId: cid1, - Description: "test", - Optional: false, - }, - }, - }, - Executors: []toi.Executor{ - { - Actions: []string{"install"}, - Image: &toi.Image{ - Ref: "a/b:v1", - }, - ParameterMapping: []byte(mapping), - }, - }, - } - - credspec := &toi.Credentials{ - Credentials: map[string]toi.CredentialSpec{ - "mycred": { - ConsumerId: cid1, - }, - }, - } - - params := ` -testparam: value -` - - repo := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, "/ctf", 0, env)) - defer Close(repo) - cv := Must(repo.LookupComponentVersion(COMPONENT, VERSION)) - defer Close(cv) - - Must(install.ExecuteAction(p, driver, "install", spec, credspec, []byte(params), env, cv, nil)) - - effparams := Must(driver.Found.Files[install.InputParameters].Get()) - Expect(string(effparams)).To(StringEqualTrimmedWithContext(` -creds: test -testparam: value -`)) - }) - - It("executes with single credential property substitution", func() { - creds1 := credentials.NewCredentials(common.Properties{"user": "test"}) - - env.CredentialsContext().SetCredentialsForConsumer(cid1, creds1) - - p, _ := common.NewBufferedPrinter() - - mapping := ` -testparam: (( merge )) -creds: (( hasCredentials("mycred") ? getCredentials("mycred", "*") :"" )) -` - spec := &toi.PackageSpecification{ - CredentialsRequest: toi.CredentialsRequest{ - Credentials: map[string]toi.CredentialsRequestSpec{ - "mycred": { - ConsumerId: cid1, - Description: "test", - Optional: false, - }, - }, - }, - Executors: []toi.Executor{ - { - Actions: []string{"install"}, - Image: &toi.Image{ - Ref: "a/b:v1", - }, - ParameterMapping: []byte(mapping), - }, - }, - } - - credspec := &toi.Credentials{ - Credentials: map[string]toi.CredentialSpec{ - "mycred": { - ConsumerId: cid1, - }, - }, - } - - params := ` -testparam: value -` - - repo := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, "/ctf", 0, env)) - defer Close(repo) - cv := Must(repo.LookupComponentVersion(COMPONENT, VERSION)) - defer Close(cv) - - Must(install.ExecuteAction(p, driver, "install", spec, credspec, []byte(params), env, cv, nil)) - - effparams := Must(driver.Found.Files[install.InputParameters].Get()) - Expect(string(effparams)).To(StringEqualTrimmedWithContext(` -creds: test -testparam: value -`)) - }) - - It("executes with optional credential substitution without credentials", func() { - env.CredentialsContext().SetCredentialsForConsumer(cid1, creds1) - - p, _ := common.NewBufferedPrinter() - - mapping := ` -testparam: (( merge )) -creds: (( hasCredentials("mycred") ? [getCredentials("mycred")] :[] )) -` - spec := &toi.PackageSpecification{ - CredentialsRequest: toi.CredentialsRequest{ - Credentials: map[string]toi.CredentialsRequestSpec{ - "mycred": { - ConsumerId: cid1, - Description: "test", - Optional: true, - }, - }, - }, - Executors: []toi.Executor{ - { - Actions: []string{"install"}, - Image: &toi.Image{ - Ref: "a/b:v1", - }, - ParameterMapping: []byte(mapping), - }, - }, - } - - credspec := &toi.Credentials{} - - params := ` -testparam: value -` - - repo := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, "/ctf", 0, env)) - defer Close(repo) - cv := Must(repo.LookupComponentVersion(COMPONENT, VERSION)) - defer Close(cv) - - Must(install.ExecuteAction(p, driver, "install", spec, credspec, []byte(params), env, cv, nil)) - - effparams := Must(driver.Found.Files[install.InputParameters].Get()) - Expect(string(effparams)).To(StringEqualTrimmedWithContext(` -creds: [] -testparam: value -`)) - }) -}) diff --git a/pkg/toi/install/bundle/spec.go b/pkg/toi/install/bundle/spec.go deleted file mode 100644 index 835054a4c..000000000 --- a/pkg/toi/install/bundle/spec.go +++ /dev/null @@ -1,32 +0,0 @@ -package bundle - -import ( - "encoding/json" - - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/toi" - "github.com/open-component-model/ocm/pkg/toi/install" -) - -type BundleSpecification struct { - install.CredentialsRequest `json:",inline"` - Template json.RawMessage `json:"configTemplate,omitempty"` - Libraries []metav1.ResourceReference `json:"templateLibraries,omitempty"` - Scheme json.RawMessage `json:"configScheme,omitempty"` - - Actions []string `json:"actions,omitempty"` - Outputs map[string]string `json:"outputs,omitempty"` -} - -type InstallationSpecification struct { - ResourceRef *metav1.ResourceReference `json:"resourceRef,omitempty"` - Image *toi.Image `json:"image,omitempty"` - - Actions map[string]string `json:"actions,omitempty"` - Required map[string]string `json:"required,omitempty"` -} - -type InstallationValues struct { - install.Credentials `json:",inline"` - Settings json.RawMessage `json:"values,omitempty"` -} diff --git a/pkg/toi/install/credentials.go b/pkg/toi/install/credentials.go deleted file mode 100644 index a3d20d721..000000000 --- a/pkg/toi/install/credentials.go +++ /dev/null @@ -1,185 +0,0 @@ -package install - -import ( - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/spiff/features" - "github.com/mandelsoft/spiff/spiffing" - - "github.com/open-component-model/ocm/pkg/common" - globalconfig "github.com/open-component-model/ocm/pkg/contexts/config/config" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/config" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/directcreds" - "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/memory" - memorycfg "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/memory/config" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/toi" - "github.com/open-component-model/ocm/pkg/utils" -) - -type ( - Credentials = toi.Credentials - CredentialSpec = toi.CredentialSpec - CredentialsRequest = toi.CredentialsRequest - CredentialsRequestSpec = toi.CredentialsRequestSpec -) - -type CredentialValues map[string]common.Properties - -func ParseCredentialSpecification(data []byte, desc string) (*Credentials, error) { - spiff := spiffing.New().WithFeatures(features.CONTROL, features.INTERPOLATION) - - templ, err := spiff.Unmarshal(desc, data) - if err != nil { - return nil, errors.Newf("invalid credential settings: %s", err) - } - - cfg, err := spiff.Cascade(templ, nil) - if err != nil { - return nil, errors.Wrapf(err, "error processing credential settings") - } - final, err := spiff.Marshal(cfg) - if err != nil { - return nil, errors.Wrapf(err, "credential marshalling") - } - var spec Credentials - - err = runtime.DefaultYAMLEncoding.Unmarshal(final, &spec) - if err != nil { - return nil, errors.Wrapf(err, "credentials settings") - } - return &spec, nil -} - -func ParseCredentialRequest(data []byte) (*CredentialsRequest, error) { - var req CredentialsRequest - - err := runtime.DefaultYAMLEncoding.Unmarshal(data, &req) - if err != nil { - return nil, errors.Wrapf(err, "cannot parse credential request") - } - return &req, err -} - -func GetCredentials(ctx credentials.Context, spec *Credentials, req map[string]CredentialsRequestSpec, mapping map[string]string) (*globalconfig.Config, CredentialValues, error) { - cfg := config.New() - mem := memorycfg.New("default") - memrepo := memory.NewRepositorySpec("default") - list := errors.ErrListf("providing requested credentials") - - credvalues := CredentialValues{} - var sub *errors.ErrorList - for _, n := range utils.StringMapKeys(req) { - r := req[n] - list.Add(sub.Result()) - sub = errors.ErrListf("credential request %q", n) - found, ok := spec.Credentials[n] - if !ok { - if !r.Optional { - sub.Add(errors.ErrNotFound("credential", n)) - } - continue - } - creds, consumer, err := evaluate(ctx, &found) - if err != nil { - sub.Add(errors.Wrapf(err, "failed to evaluate")) - continue - } - mapped := n - if mapping != nil { - mapped = mapping[n] - } - if mapped == "" { - return nil, nil, errors.Newf("mapping missing credential %q", n) - } - credvalues[mapped] = creds - err = mem.AddCredentials(mapped, creds) - if err != nil { - sub.Add(errors.Wrapf(err, "failed to add credentials")) - continue - } - if len(consumer) != 0 { - err = cfg.AddConsumer(consumer, credentials.NewCredentialsSpec(mapped, memrepo)) - if err != nil { - sub.Add(errors.Newf("failed to add consumer %s from config", consumer)) - continue - } - } - if len(r.ConsumerId) != 0 { - err = cfg.AddConsumer(r.ConsumerId, credentials.NewCredentialsSpec(mapped, memrepo)) - if err != nil { - sub.Add(errors.Newf("failed to add consumer %s from request", consumer)) - continue - } - } - } - for _, r := range spec.Forwarded { - if len(r.ConsumerId) == 0 { - return nil, nil, errors.ErrInvalid("consumer", r.ConsumerId.String()) - } - match := ctx.ConsumerIdentityMatchers().Get(r.ConsumerType) - if match == nil { - match = credentials.PartialMatch - } - src, err := ctx.GetCredentialsForConsumer(r.ConsumerId, match) - if err != nil || src == nil { - return nil, nil, errors.ErrNotFoundWrap(err, "consumer", r.ConsumerId.String()) - } - if src == nil { - return nil, nil, errors.ErrNotFoundWrap(err, "consumer", r.ConsumerId.String()) - } - creds, err := src.Credentials(ctx) - if err != nil { - return nil, nil, errors.Wrapf(err, "cannot get credentials for %s", r.ConsumerId.String()) - } - props := creds.Properties() - cfg.AddConsumer(r.ConsumerId, directcreds.NewCredentials(props)) - } - - list.Add(sub.Result()) - main := globalconfig.New() - main.AddConfig(mem) - main.AddConfig(cfg) - return main, credvalues, list.Result() -} - -func evaluate(ctx credentials.Context, spec *CredentialSpec) (common.Properties, credentials.ConsumerIdentity, error) { - var err error - var props common.Properties - var src credentials.CredentialsSource - cnt := 0 - if len(spec.Credentials) > 0 { - cnt++ - props = spec.Credentials - } - if spec.Reference != nil { - cnt++ - src, err = spec.Reference.Credentials(ctx) - if err != nil { - return nil, nil, errors.Wrapf(err, "cannot evaluate credential reference") - } - } - if spec.ConsumerId != nil { - cnt++ - match := ctx.ConsumerIdentityMatchers().Get(spec.ConsumerType) - if match == nil { - match = credentials.PartialMatch - } - src, err = ctx.GetCredentialsForConsumer(spec.ConsumerId, match) - if err != nil { - return nil, nil, errors.ErrNotFoundWrap(err, "consumer", spec.ConsumerId.String()) - } - } - if cnt > 1 { - return nil, nil, errors.Newf("only one of consumer id or reference or credentials possible") - } - if src != nil { - creds, err := src.Credentials(ctx) - if err != nil { - return nil, nil, errors.Wrapf(err, "cannot get credentials for %s", spec.ConsumerId.String()) - } - props = creds.Properties() - } - - return props, spec.TargetConsumerId, nil -} diff --git a/pkg/toi/install/interface.go b/pkg/toi/install/interface.go deleted file mode 100644 index 24ebf9bf5..000000000 --- a/pkg/toi/install/interface.go +++ /dev/null @@ -1,56 +0,0 @@ -package install - -import ( - "io" - - "github.com/open-component-model/ocm/pkg/blobaccess/blobaccess" - "github.com/open-component-model/ocm/pkg/toi" -) - -const ( - PathTOI = "/toi" - Inputs = "inputs" - Outputs = "outputs" - PathExec = PathTOI + "/run" - PathOutputs = PathTOI + "/" + Outputs - PathInputs = PathTOI + "/" + Inputs - InputParameters = "parameters" - InputConfig = "config" - InputOCMConfig = "ocmconfig" - InputOCMRepo = "ocmrepo" -) - -type Driver interface { - SetConfig(props map[string]string) error - Exec(op *Operation) (*OperationResult, error) -} - -// Operation describes the data passed into the driver to run an operation. -type Operation struct { - // Action is the action to be performed. It is passed a srgument to the executable - Action string - // ComponentVersion is the name of the root component/version to install - ComponentVersion string - // Image is the image to invoke - Image toi.Image - // Environment contains environment variables that should be injected into the container execution - Environment map[string]string - // Files contains files that should be injected into the invocation image. - Files map[string]blobaccess.BlobAccess - // Outputs map of output (sub)paths (e.g. NAME) to the name of the output. - // Indicates which outputs the driver should return the contents of in the OperationResult. - Outputs map[string]string - // Output stream for log messages from the driver - Out io.Writer - // Output stream for error messages from the driver - Err io.Writer -} - -// OperationResult is the output of the Driver running an Operation. -type OperationResult struct { - // Outputs maps from the name of the output to its content. - Outputs map[string][]byte - - // Error is any errors from executing the operation. - Error error -} diff --git a/pkg/toi/logging.go b/pkg/toi/logging.go deleted file mode 100644 index c24abb05e..000000000 --- a/pkg/toi/logging.go +++ /dev/null @@ -1,9 +0,0 @@ -package toi - -import ( - logging2 "github.com/open-component-model/ocm/pkg/logging" -) - -var REALM = logging2.DefineSubRealm("TOI logging", "toi") - -var Log = logging2.DynamicLogger(REALM) diff --git a/pkg/toi/spec.go b/pkg/toi/spec.go deleted file mode 100644 index d6a177d01..000000000 --- a/pkg/toi/spec.go +++ /dev/null @@ -1,165 +0,0 @@ -package toi - -import ( - "encoding/json" - "fmt" - - "github.com/mandelsoft/goutils/errors" - - "github.com/open-component-model/ocm/pkg/common" - "github.com/open-component-model/ocm/pkg/contexts/credentials" - "github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" -) - -const ( - TypeTOIPackage = "toiPackage" - PackageSpecificationMimeType = "application/vnd.toi.ocm.software.package.v1+yaml" - - TypeYAML = resourcetypes.OCM_YAML - - AdditionalResourceConfigFile = "configFile" - AdditionalResourceCredentialsFile = "credentialsFile" -) - -const ( - TypeTOIExecutor = "toiExecutor" - ExecutorSpecificationMimeType = "application/vnd.toi.ocm.software.executor.v1+yaml" -) - -type PackageSpecification struct { - CredentialsRequest `json:",inline"` - Template json.RawMessage `json:"configTemplate,omitempty"` - Libraries []metav1.ResourceReference `json:"templateLibraries,omitempty"` - Scheme json.RawMessage `json:"configScheme,omitempty"` - Executors []Executor `json:"executors"` - Description string `json:"description"` - AdditionalResources map[string]*AdditionalResource `json:"additionalResources,omitempty"` -} - -type AdditionalResource struct { - *metav1.ResourceReference `json:",inline"` - Content json.RawMessage `json:"content,omitempty"` -} - -type Executor struct { - Actions []string `json:"actions,omitempty"` - ResourceRef *metav1.ResourceReference `json:"resourceRef,omitempty"` - Image *Image `json:"image,omitempty"` - CredentialMapping map[string]string `json:"credentialMapping,omitempty"` - ParameterMapping json.RawMessage `json:"parameterMapping,omitempty"` - Config json.RawMessage `json:"config,omitempty"` - Outputs map[string]string `json:"outputs,omitempty"` -} - -func (e *Executor) Name() string { - if e.ResourceRef != nil { - return e.ResourceRef.String() - } - if e.Image != nil { - return e.Image.String() - } - return "unspecified executor" -} - -type Image struct { - Ref string `json:"ref"` - Digest string `json:"digest"` -} - -func (i *Image) String() string { - r := "" - if i.Ref != "" { - r = i.Ref - } - if i.Digest != "" { - r += "@" + i.Digest - } - return r -} - -//////////////////////////////////////////////////////////////////////////////// - -type ExecutorSpecification struct { - CredentialsRequest `json:",inline"` - Actions []string `json:"actions,omitempty"` - Image *Image `json:"image,omitempty"` - ImageRef *metav1.ResourceReference `json:"imageRef,omitempty"` - Template json.RawMessage `json:"configTemplate,omitempty"` - Libraries []metav1.ResourceReference `json:"templateLibraries,omitempty"` - Scheme json.RawMessage `json:"configScheme,omitempty"` - Outputs map[string]OutputSpec `json:"outputs,omitempty"` -} - -type OutputSpec struct { - Description string `json:"description,omitempty"` -} - -//////////////////////////////////////////////////////////////////////////////// - -type CredentialsRequest struct { - Credentials map[string]CredentialsRequestSpec `json:"credentials,omitempty"` -} - -type CredentialsRequestSpec struct { - // ConsumerId specified to consumer id the credentials are used for - ConsumerId credentials.ConsumerIdentity `json:"consumerId,omitempty"` - // Description described the usecase the credentials will be used for - Description string `json:"description"` - // Properties describes the meaning of the used properties for this - // credential set. - Properties common.Properties `json:"properties"` - // Optional set to true make the request optional - Optional bool `json:"optional,omitempty"` -} - -var ErrUndefined error = errors.New("nil reference") - -func (s *CredentialsRequestSpec) Match(o *CredentialsRequestSpec) error { - if o == nil { - return ErrUndefined - } - if !s.ConsumerId.Equals(o.ConsumerId) { - return fmt.Errorf("consumer id mismatch") - } - for k := range o.Properties { - if _, ok := s.Properties[k]; !ok { - return fmt.Errorf("property %q not declared", k) - } - } - if s.Optional && !o.Optional { - return fmt.Errorf("cannot be optional") - } - return nil -} - -type Credentials struct { - Credentials map[string]CredentialSpec `json:"credentials,omitempty"` - - // Forwarded may define a list of consumer ids, which should be taken from the - // local configuration and forwarded to the TOI executor in addition to the - // credentials explicitly requested by the installation package. - Forwarded []ForwardSpec `json:"forwardedConsumers,omitempty"` -} - -type CredentialSpec struct { - // ConsumerId specifies the consumer id to look for the credentials - ConsumerId credentials.ConsumerIdentity `json:"consumerId,omitempty"` - // ConsumerType is the optional type used for matching the credentials - ConsumerType string `json:"consumerType,omitempty"` - // Reference refers to credentials store in some other repo - Reference *cpi.GenericCredentialsSpec `json:"reference,omitempty"` - // Credentials are direct credentials (one of Reference or Credentials must be set) - Credentials common.Properties `json:"credentials,omitempty"` - - // TargetConsumerId specifies the consumer id to feed with these credentials - TargetConsumerId credentials.ConsumerIdentity `json:"targetConsumerId,omitempty"` -} - -type ForwardSpec struct { - // ConsumerId specifies the consumer id to look for the credentials - ConsumerId credentials.ConsumerIdentity `json:"consumerId"` - // ConsumerType is the optional type used for matching the credentials - ConsumerType string `json:"consumerType,omitempty"` -} diff --git a/pkg/toi/support/README.md b/pkg/toi/support/README.md deleted file mode 100644 index d6d8f3a2b..000000000 --- a/pkg/toi/support/README.md +++ /dev/null @@ -1,49 +0,0 @@ -### Support for TOI Executors - -This package provides a generic command line tool support to provide -TOI executor CLIs. - -Such a CLI wraps an executor specific executor function. If no options -are passed it complies to the TOI image binding contract. - -For development purposes it can be called with a bunch of option to fake -the file system binding and redirect it to explicitly specified files. - -It provides a contract to the executor function of type - -
-
-func(options *ExecutorOptions) error
-
-
- - which already reolves all those dependencies by providing an - [ExecutorOptions](support.go#:~:text=type%20ExecutorOptions%20struct,%7D) - object with the prepared contract data, including access to to - the `ocm.ComponentVersionAccess` of the component version providing - the package to work on. - - A typical `main` function of an executor could then look like: - -
-     package main
-
-     import (
-        "os"
-
-        "github.com/open-component-model/ocm/pkg/contexts/clictx"
-
-        "yourpackage"
-     )
-
-     func main() {
-        ctx:=clictx.New()
-        // special configuration of the context. e.g. setting a virstual filesystem
-        c := support.NewCLICommand(ctx.OCMContext(), "your executor name", yourpackage.ExecutorFunction)
-        if err := c.Execute(); err != nil {
-            os.Exit(1)
-        }
-     }
- 
- - diff --git a/pkg/toi/support/app.go b/pkg/toi/support/app.go deleted file mode 100644 index 2409f8893..000000000 --- a/pkg/toi/support/app.go +++ /dev/null @@ -1,171 +0,0 @@ -package support - -import ( - "fmt" - "strings" - - _ "github.com/open-component-model/ocm/pkg/contexts/clictx/config" - - "github.com/mandelsoft/goutils/errors" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - - "github.com/open-component-model/ocm/pkg/clisupport" - "github.com/open-component-model/ocm/pkg/cobrautils" - "github.com/open-component-model/ocm/pkg/cobrautils/logopts" - "github.com/open-component-model/ocm/pkg/common" - "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/vfsattr" - datactg "github.com/open-component-model/ocm/pkg/contexts/datacontext/config/attrs" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/out" - "github.com/open-component-model/ocm/pkg/toi" - "github.com/open-component-model/ocm/pkg/toi/install" - "github.com/open-component-model/ocm/pkg/version" -) - -type BootstrapperCLIOptions struct { - ExecutorOptions - logopts.Options - CredentialSettings []string - Settings []string -} - -func NewCLICommand(ctx ocm.Context, name string, exec func(options *ExecutorOptions) error) *cobra.Command { - if ctx == nil { - ctx = ocm.DefaultContext() - } - opts := &BootstrapperCLIOptions{ - ExecutorOptions: ExecutorOptions{ - Context: ctx, - }, - } - cmd := &cobra.Command{ - Use: name + " {} [] ", - Short: "Bootstrapper using the OCM bootstrap mechanism", - Version: version.Get().String(), - TraverseChildren: true, - SilenceUsage: true, - DisableFlagsInUseLine: true, - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - action := "" - if len(args) > 0 { - if len(args) > 1 { - action = args[0] - opts.ComponentVersionName = args[1] - } else { - opts.ComponentVersionName = args[0] - } - } - opts.Action = action - return opts.Complete() - }, - RunE: func(cmd *cobra.Command, args []string) error { - out.Outf(opts.OutputContext, "This is %s (%s)\n", name, version.Get().String()) - e := &Executor{Completed: true, Options: &opts.ExecutorOptions, Run: exec} - return e.Execute() - }, - } - cobrautils.TweakCommand(cmd, nil) - - cmd.AddCommand(NewVersionCommand()) - opts.AddFlags(cmd.Flags()) - cmd.InitDefaultHelpCmd() - - return cmd -} - -func (o *BootstrapperCLIOptions) AddFlags(fs *pflag.FlagSet) { - o.Options.AddFlags(fs) - fs.StringVarP(&o.OCMConfig, "ocmconfig", "", "", "ocm configuration file") - fs.StringArrayVarP(&o.CredentialSettings, "cred", "C", nil, "credential setting") - fs.StringArrayVarP(&o.Settings, "attribute", "X", nil, "attribute setting") - - fs.StringVarP(&o.Inputs, "inputs", "", "", "input path") - fs.StringVarP(&o.Outputs, "outputs", "", "", "output path") - fs.StringVarP(&o.Root, "bootstraproot", "", install.PathTOI, "bootstrapper contract root folder") - fs.StringVarP(&o.Config, "config", "", "", "bootstrapper configuration input file") - fs.StringVarP(&o.Parameters, "parameters", "", "", "bootstrapper parameter input file") - fs.StringVarP(&o.RepoPath, "ctf", "", "", "bootstrapper transport archive") -} - -func (o *BootstrapperCLIOptions) Complete() error { - o.Options.Configure(o.Context, o.Context.LoggingContext()) - if err := o.ExecutorOptions.Complete(); err != nil { - return fmt.Errorf("unable to complete options: %w", err) - } - - id := credentials.ConsumerIdentity{} - attrs := common.Properties{} - - for _, s := range o.CredentialSettings { - i := strings.Index(s, "=") - if i < 0 { - return errors.ErrInvalid("credential setting", s) - } - - name := s[:i] - value := s[i+1:] - - if strings.HasPrefix(name, ":") { - if len(attrs) != 0 { - o.Context.CredentialsContext().SetCredentialsForConsumer(id, credentials.NewCredentials(attrs)) - id = credentials.ConsumerIdentity{} - attrs = common.Properties{} - } - name = name[1:] - id[name] = value - } else { - attrs[name] = value - } - - if len(name) == 0 { - return errors.ErrInvalid("credential setting", s) - } - } - - if len(attrs) != 0 { - o.Context.CredentialsContext().SetCredentialsForConsumer(id, credentials.NewCredentials(attrs)) - } else if len(id) != 0 { - return errors.Newf("empty credential attribute set for %s", id.String()) - } - - set, err := clisupport.ParseLabels(vfsattr.Get(o.Context), o.Settings, "attribute setting") - if err == nil && len(set) > 0 { - ctx := o.Context.ConfigContext() - spec := datactg.New() - for _, s := range set { - attr := s.Name - eff := datacontext.DefaultAttributeScheme.Shortcuts()[attr] - if eff != "" { - attr = eff - } - err = spec.AddRawAttribute(attr, s.Value) - if err != nil { - return errors.Wrapf(err, "attribute %s", s.Name) - } - } - err = ctx.ApplyConfig(spec, "cli") - } - - if err != nil { - return fmt.Errorf("unable to parse labels: %w", err) - } - - o.Logger = toi.Log - return nil -} - -func NewVersionCommand() *cobra.Command { - return &cobra.Command{ - Use: "version", - Aliases: []string{"v"}, - Short: "displays the version", - Run: func(cmd *cobra.Command, args []string) { - v := version.Get() - //nolint:forbidigo // It's an intentional Printf. - fmt.Printf("%#v", v) - }, - } -} diff --git a/pkg/toi/support/support.go b/pkg/toi/support/support.go deleted file mode 100644 index cadb2835b..000000000 --- a/pkg/toi/support/support.go +++ /dev/null @@ -1,198 +0,0 @@ -package support - -import ( - "fmt" - "io" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/logging" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/common" - "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/repositories/memory" - "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/vfsattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm" - "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" - ocmutils "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" - "github.com/open-component-model/ocm/pkg/out" - "github.com/open-component-model/ocm/pkg/toi/install" - "github.com/open-component-model/ocm/pkg/utils" -) - -type ExecutorOptions struct { - Context ocm.Context - Logger logging.Logger - OutputContext out.Context - Action string - ComponentVersionName string - Root string - Inputs string - Outputs string - OCMConfig string - Config string - ConfigData []byte - Parameters string - ParameterData []byte - RepoPath string - Repository ocm.Repository - CredentialRepo credentials.Repository - ComponentVersion ocm.ComponentVersionAccess - Closer func() error -} - -func (o *ExecutorOptions) FileSystem() vfs.FileSystem { - return vfsattr.Get(o.Context) -} - -func (o *ExecutorOptions) Complete() error { - if o.ComponentVersionName == "" { - return fmt.Errorf("component version required") - } - compvers, err := common.ParseNameVersion(o.ComponentVersionName) - if err != nil { - return fmt.Errorf("unable to parse component name and version: %w", err) - } - - if o.OutputContext == nil { - o.OutputContext = out.New() - } - - if o.Action == "" { - o.Action = "install" - } - - if o.Root == "" { - o.Root = install.PathTOI - } - - if o.Inputs == "" { - o.Inputs = o.Root + "/" + install.Inputs - } - - if o.Outputs == "" { - o.Outputs = o.Root + "/" + install.Outputs - } - - if o.RepoPath == "" { - o.RepoPath = o.Inputs + "/" + install.InputOCMRepo - } - - if o.Config == "" { - cfg := o.Inputs + "/" + install.InputConfig - if ok, err := vfs.FileExists(o.FileSystem(), cfg); ok && err == nil { - o.Config = cfg - } - } - - if o.Config != "" && o.ConfigData == nil { - o.ConfigData, err = utils.ReadFile(o.Config, o.FileSystem()) - if err != nil { - return errors.Wrapf(err, "cannot read config %q", o.Config) - } - } - - if o.OCMConfig == "" { - cfg, err := utils.ResolvePath(o.Inputs + "/" + install.InputOCMConfig) - if err != nil { - return errors.Wrapf(err, "cannot resolve OCM config %q", o.Inputs) - } - if ok, err := vfs.FileExists(o.FileSystem(), cfg); ok && err == nil { - o.OCMConfig = cfg - } - } - - o.Context, err = ocmutils.Configure(o.Context, o.OCMConfig) - if err != nil { - return fmt.Errorf("unable to configure context: %w", err) - } - - if o.Parameters == "" { - p, err := utils.ResolvePath(o.Inputs + "/" + install.InputParameters) - if err != nil { - return errors.Wrapf(err, "cannot resolve path %q", o.Inputs) - } - if ok, err := vfs.FileExists(o.FileSystem(), p); ok && err == nil { - o.Parameters = p - } - } - - if o.Parameters != "" && o.ParameterData == nil { - o.ParameterData, err = utils.ReadFile(o.Parameters, o.FileSystem()) - if err != nil { - return errors.Wrapf(err, "cannot read parameters %q", o.Config) - } - } - - var repoCloser io.Closer - if o.Repository == nil { - repo, err := ctf.Open(o.Context, accessobj.ACC_READONLY, o.RepoPath, 0, accessio.PathFileSystem(o.FileSystem())) - if err != nil { - return errors.Wrapf(err, "cannot open ctf %q", o.RepoPath) - } - o.Repository = repo - repoCloser = repo - } - - var versCloser io.Closer - - if o.ComponentVersion == nil { - cv, err := o.Repository.LookupComponentVersion(compvers.GetName(), compvers.GetVersion()) - if err != nil { - return fmt.Errorf("failed component version lookup: %w", err) - } - o.ComponentVersion = cv - versCloser = cv - } - - old := o.Closer - o.Closer = func() error { - list := errors.ErrListf("closing") - if versCloser != nil { - list.Add(errors.Wrapf(versCloser.Close(), "component version")) - } - if repoCloser != nil { - list.Add(errors.Wrapf(repoCloser.Close(), "repository")) - } - if old != nil { - list.Add(errors.Wrapf(old(), "external closer")) - } - return list.Result() - } - - if o.CredentialRepo == nil { - c, err := o.Context.CredentialsContext().RepositoryForSpec(memory.NewRepositorySpec("default")) - if err != nil { - return errors.Wrapf(err, "cannot get default memory based credential repository") - } - o.CredentialRepo = c - } - return nil -} - -type Executor struct { - Completed bool - Options *ExecutorOptions - Run func(o *ExecutorOptions) error -} - -func (e *Executor) Execute() error { - if e.Options == nil { - e.Completed = false - e.Options = &ExecutorOptions{} - } - if !e.Completed { - err := e.Options.Complete() - if err != nil { - return fmt.Errorf("unable to complete options: %w", err) - } - } - list := errors.ErrListf("executor:") - list.Add(e.Run(e.Options)) - if e.Options.Closer != nil { - list.Add(e.Options.Closer()) - } - return list.Result() -} diff --git a/pkg/tokens/github/main/main.go b/pkg/tokens/github/main/main.go deleted file mode 100644 index dc271eb7a..000000000 --- a/pkg/tokens/github/main/main.go +++ /dev/null @@ -1,64 +0,0 @@ -package main - -import ( - "encoding/base64" - out "fmt" - "io" - "net/http" - "os" -) - -const ( - RequestTokenEnvKey = "ACTIONS_ID_TOKEN_REQUEST_TOKEN" - RequestURLEnvKey = "ACTIONS_ID_TOKEN_REQUEST_URL" -) - -type githubActions struct{} - -func (ga *githubActions) Enabled() bool { - if os.Getenv(RequestTokenEnvKey) == "" { - return false - } - if os.Getenv(RequestURLEnvKey) == "" { - return false - } - return true -} - -func (ga *githubActions) Get(audience string) error { - url := os.Getenv(RequestURLEnvKey) + "&audience=" + audience - - //nolint: all // yes - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return err - } - - req.Header.Add("Authorization", "bearer "+os.Getenv(RequestTokenEnvKey)) - resp, err := http.DefaultClient.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - data, err := io.ReadAll(resp.Body) - if err != nil { - return err - } - out.Printf("Token: %s\n", base64.StdEncoding.EncodeToString(data)) - return nil -} - -func main() { - p := &githubActions{} - - if p.Enabled() { - err := p.Get("sigstore") - if err != nil { - out.Fprintf(os.Stderr, "Error: %s\n", err) - os.Exit(1) - } - } else { - out.Printf("no enabled\n") - } -} diff --git a/pkg/utils/pkgutils/doc.go b/pkg/utils/pkgutils/doc.go deleted file mode 100644 index 862b20fe4..000000000 --- a/pkg/utils/pkgutils/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Deprecated: This package is deprecated and will be removed in a future release. Please use the pkgutils package -// provided by github.com/mandelsoft/goutils. -package pkgutils diff --git a/pkg/utils/pkgutils/package.go b/pkg/utils/pkgutils/package.go deleted file mode 100644 index 857ca195f..000000000 --- a/pkg/utils/pkgutils/package.go +++ /dev/null @@ -1,102 +0,0 @@ -package pkgutils - -import ( - "fmt" - "reflect" - "runtime" - "strings" -) - -// GetPackageName gets the package name for an object, a type, a function or a caller offset. -// -// Examples: -// -// GetPackageName(1) -// GetPackageName(&MyStruct{}) -// GetPackageName(GetPackageName) -// GetPackageName(generics.TypeOf[MyStruct]()) -func GetPackageName(i ...interface{}) (string, error) { - if len(i) == 0 { - i = []interface{}{0} - } - if t, ok := i[0].(reflect.Type); ok { - pkgpath := t.PkgPath() - if pkgpath == "" { - return "", fmt.Errorf("unable to determine package name") - } - return pkgpath, nil - } - v := reflect.ValueOf(i[0]) - for v.Kind() == reflect.Ptr { - v = v.Elem() - } - switch v.Kind() { - case reflect.Func: - return getPackageNameForFuncPC(v.Pointer()) - case reflect.Struct, reflect.Chan, reflect.Map, reflect.Slice, reflect.Array: - pkgpath := v.Type().PkgPath() - if pkgpath == "" { - return "", fmt.Errorf("unable to determine package name") - } - return pkgpath, nil - default: - offset, err := CastInt(v.Interface()) - if err != nil { - return "", err - } - pc, _, _, ok := runtime.Caller(offset + 1) - if !ok { - return "", fmt.Errorf("unable to find caller") - } - return getPackageNameForFuncPC(pc) - } -} - -func getPackageNameForFuncPC(pc uintptr) (string, error) { - // Retrieve the function's runtime information - funcForPC := runtime.FuncForPC(pc) - if funcForPC == nil { - return "", fmt.Errorf("could not determine package name") - } - // Get the full name of the function, including the package path - fullFuncName := funcForPC.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] - - return packagePath, nil -} - -func CastInt(i interface{}) (int, error) { - switch v := i.(type) { - case int: - return v, nil - case int8: - return int(v), nil - case int16: - return int(v), nil - case int32: - return int(v), nil - case int64: - return int(v), nil - case uint: - return int(v), nil - case uint8: - return int(v), nil - case uint16: - return int(v), nil - case uint32: - return int(v), nil - case uint64: - return int(v), nil - default: - return 0, fmt.Errorf("unable to cast %T into int", i) - } -} diff --git a/pkg/utils/pkgutils/package_test.go b/pkg/utils/pkgutils/package_test.go deleted file mode 100644 index d3568920f..000000000 --- a/pkg/utils/pkgutils/package_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package pkgutils_test - -import ( - "reflect" - - . "github.com/mandelsoft/goutils/testutils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/mandelsoft/goutils/generics" - - me "github.com/open-component-model/ocm/pkg/utils/pkgutils" - "github.com/open-component-model/ocm/pkg/utils/pkgutils/testpackage" -) - -type typ struct{} - -var _ = Describe("package tests", func() { - DescribeTable("determine package type for ", func(typ interface{}) { - Expect(Must(me.GetPackageName(typ))).To(Equal(reflect.TypeOf(testpackage.MyStruct{}).PkgPath())) - }, - Entry("struct", &testpackage.MyStruct{}), - Entry("array", &testpackage.MyArray{}), - Entry("list", &testpackage.MyList{}), - Entry("map", &testpackage.MyMap{}), - Entry("chan", make(testpackage.MyChan)), - Entry("func", testpackage.MyFunc), - Entry("func type", generics.TypeOf[testpackage.MyFuncType]()), - Entry("struct type", generics.TypeOf[testpackage.MyStruct]()), - ) - It("determine package for caller func", func() { - Expect(Must(testpackage.MyFunc())).To(Equal(reflect.TypeOf(testpackage.MyStruct{}).PkgPath())) - Expect(Must(testpackage.MyFunc(1))).To(Equal(reflect.TypeOf(typ{}).PkgPath())) - }) -}) diff --git a/pkg/utils/pkgutils/suite_test.go b/pkg/utils/pkgutils/suite_test.go deleted file mode 100644 index 2946fa4d3..000000000 --- a/pkg/utils/pkgutils/suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package pkgutils_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestConfig(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Package Utils Test Suite") -} diff --git a/pkg/utils/pkgutils/testpackage/testtypes.go b/pkg/utils/pkgutils/testpackage/testtypes.go deleted file mode 100644 index 08a9522d2..000000000 --- a/pkg/utils/pkgutils/testpackage/testtypes.go +++ /dev/null @@ -1,20 +0,0 @@ -package testpackage - -import ( - "github.com/mandelsoft/goutils/pkgutils" - "github.com/mandelsoft/goutils/sliceutils" -) - -type ( - MyStruct struct{} - - MyList []int - MyArray [3]int - MyMap map[int]int - MyChan chan int - MyFuncType func() -) - -func MyFunc(i ...int) (string, error) { - return pkgutils.GetPackageName(sliceutils.Convert[interface{}](i)...) -} diff --git a/pkg/utils/subst/subst.go b/pkg/utils/subst/subst.go deleted file mode 100644 index bdb187967..000000000 --- a/pkg/utils/subst/subst.go +++ /dev/null @@ -1,201 +0,0 @@ -package subst - -import ( - "bytes" - "container/list" - "regexp" - "sync" - - "github.com/mandelsoft/goutils/errors" - mlog "github.com/mandelsoft/logging" - "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/mikefarah/yq/v4/pkg/yqlib" - glog "gopkg.in/op/go-logging.v1" - "gopkg.in/yaml.v3" - - ocmlog "github.com/open-component-model/ocm/pkg/logging" - "github.com/open-component-model/ocm/pkg/runtime" - "github.com/open-component-model/ocm/pkg/utils" -) - -type SubstitutionTarget interface { - SubstituteByData(path string, value []byte) error - SubstituteByValue(path string, value interface{}) error - - Content() ([]byte, error) -} - -func ParseFile(file string, fss ...vfs.FileSystem) (SubstitutionTarget, error) { - fs := utils.FileSystem(fss...) - - data, err := utils.ReadFile(file, fs) - if err != nil { - return nil, errors.Wrapf(err, "cannot read file %q", file) - } - s, err := Parse(data) - if err != nil { - return nil, errors.Wrapf(err, "file %q", file) - } - return s, nil -} - -func Parse(data []byte) (SubstitutionTarget, error) { - sync.OnceFunc(func() { - var lvl glog.Level - switch ocmlog.Context().GetDefaultLevel() { - case mlog.None: - fallthrough - case mlog.ErrorLevel: - lvl = glog.ERROR - case mlog.WarnLevel: - lvl = glog.WARNING - case mlog.InfoLevel: - lvl = glog.INFO - case mlog.DebugLevel: - fallthrough - case mlog.TraceLevel: - lvl = glog.DEBUG - } - glog.SetLevel(lvl, "yq-lib") - })() - - var ( - err error - fi fileinfo - ) - - fi.json = true - rdr := bytes.NewBuffer(data) - jsnDcdr := yqlib.NewJSONDecoder() - - if err := jsnDcdr.Init(rdr); err != nil { - return nil, err - } - - if fi.content, err = jsnDcdr.Decode(); err != nil { - fi.json = false - ymlPrfs := yqlib.NewDefaultYamlPreferences() - ymlDcdr := yqlib.NewYamlDecoder(ymlPrfs) - rdr = bytes.NewBuffer(data) - if err := ymlDcdr.Init(rdr); err != nil { - return nil, err - } - if fi.content, err = ymlDcdr.Decode(); err != nil { - return nil, err - } - } - - fi.content.SetDocument(0) - fi.content.SetFilename("substitution-target") - fi.content.SetFileIndex(0) - - return &fi, nil -} - -type fileinfo struct { - content *yqlib.CandidateNode - json bool -} - -func (f *fileinfo) Content() ([]byte, error) { - var enc yqlib.Encoder - if f.json { - prfs := yqlib.NewDefaultJsonPreferences() - prfs.ColorsEnabled = false - enc = yqlib.NewJSONEncoder(prfs) - } else { - prfs := yqlib.NewDefaultYamlPreferences() - enc = yqlib.NewYamlEncoder(prfs) - } - - buf := bytes.NewBuffer([]byte{}) - pw := yqlib.NewSinglePrinterWriter(buf) - p := yqlib.NewPrinter(enc, pw) - inptLst := list.New() - inptLst.PushBack(f.content) - - if err := p.PrintResults(inptLst); err == nil { - return buf.Bytes(), nil - } else { - return nil, err - } -} - -var sniffJson = regexp.MustCompile(`^\s*(\{|\[|")`) - -func (f *fileinfo) SubstituteByData(path string, value []byte) error { - var err error - - if !f.json && sniffJson.Match(value) { - // yaml is generally a superset of json so we could just insert the json value - // into a yaml file and have a valid yaml. - // However having a yaml file that looks like a mix of yaml and json is off putting. - // So if the value looks like json and the target file is yaml we will first - // attempt to re-enode the value as yaml before inserting into the target document. - // However... we don't want to perform re-encoding for everything because if the - // value is actually yaml with some snippets in json style for readability - // purposes we don't want to unecessarily lose that styling. Hence the initial - // sniff test for json instead of always re-encoding. - var valueData interface{} - if err = runtime.DefaultJSONEncoding.Unmarshal(value, &valueData); err == nil { - if value, err = runtime.DefaultYAMLEncoding.Marshal(valueData); err != nil { - return err - } - } - } - - m := &yaml.Node{} - if err = yaml.Unmarshal(value, m); err != nil { - return err - } - - nd := &yqlib.CandidateNode{} - nd.SetDocument(0) - nd.SetFilename("value") - nd.SetFileIndex(0) - - if err = nd.UnmarshalYAML(m.Content[0], map[string]*yqlib.CandidateNode{}); err != nil { - return err - } - - return f.substituteByValue(path, nd) -} - -func (f *fileinfo) SubstituteByValue(path string, value interface{}) error { - var mrshl func(interface{}) ([]byte, error) - - if f.json { - mrshl = runtime.DefaultJSONEncoding.Marshal - } else { - mrshl = runtime.DefaultYAMLEncoding.Marshal - } - - if bval, err := mrshl(value); err != nil { - return err - } else { - return f.SubstituteByData(path, bval) - } -} - -func (f *fileinfo) substituteByValue(path string, value *yqlib.CandidateNode) error { - inptLst := list.New() - inptLst.PushBack(f.content) - - vlLst := list.New() - vlLst.PushBack(value) - - ctxt := yqlib.Context{MatchingNodes: inptLst} - ctxt.SetVariable("newValue", vlLst) - - yqlib.InitExpressionParser() - expr := "." + path + " |= $newValue" - - nd, err := yqlib.ExpressionParser.ParseExpression(expr) - if err != nil { - return err - } - - ngvtr := yqlib.NewDataTreeNavigator() - _, err = ngvtr.GetMatchingNodes(ctxt, nd) - return err -} diff --git a/pkg/utils/tarutils/list.go b/pkg/utils/tarutils/list.go deleted file mode 100644 index a88be1340..000000000 --- a/pkg/utils/tarutils/list.go +++ /dev/null @@ -1,53 +0,0 @@ -package tarutils - -import ( - "archive/tar" - "io" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/vfs/pkg/osfs" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/common/compression" - "github.com/open-component-model/ocm/pkg/utils" -) - -func ListArchiveContent(path string, fss ...vfs.FileSystem) ([]string, error) { - sfs := utils.OptionalDefaulted(osfs.New(), fss...) - - f, err := sfs.Open(path) - if err != nil { - return nil, errors.Wrapf(err, "cannot open %s", path) - } - defer f.Close() - return ListArchiveContentFromReader(f) -} - -func ListArchiveContentFromReader(r io.Reader) ([]string, error) { - in, _, err := compression.AutoDecompress(r) - if err != nil { - return nil, errors.Wrapf(err, "cannot determine compression") - } - - var result []string - - tr := tar.NewReader(in) - for { - header, err := tr.Next() - if err != nil { - if errors.Is(err, io.EOF) { - return result, nil - } - return nil, err - } - - switch header.Typeflag { - case tar.TypeDir: - result = append(result, header.Name) - case tar.TypeSymlink, tar.TypeLink: - result = append(result, header.Name) - case tar.TypeReg: - result = append(result, header.Name) - } - } -} diff --git a/pkg/utils/template/registry.go b/pkg/utils/template/registry.go deleted file mode 100644 index 697a4be86..000000000 --- a/pkg/utils/template/registry.go +++ /dev/null @@ -1,114 +0,0 @@ -package template - -import ( - "fmt" - "strings" - "sync" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/vfs/pkg/vfs" - - "github.com/open-component-model/ocm/pkg/utils" -) - -const KIND_TEMPLATER = "templater" - -type TemplaterFactory func(system vfs.FileSystem) Templater - -type Registry interface { - Register(name string, fac TemplaterFactory, desc string) - Create(name string, fs vfs.FileSystem) (Templater, error) - Describe(name string) (string, error) - KnownTypeNames() []string -} - -type templaterInfo struct { - templater TemplaterFactory - description string -} - -type registry struct { - lock sync.RWMutex - templaters map[string]templaterInfo -} - -func NewRegistry() Registry { - return ®istry{ - templaters: map[string]templaterInfo{}, - } -} - -func (r *registry) Register(name string, fac TemplaterFactory, desc string) { - r.lock.Lock() - defer r.lock.Unlock() - - r.templaters[name] = templaterInfo{ - templater: fac, - description: desc, - } -} - -func (r *registry) Create(name string, fs vfs.FileSystem) (Templater, error) { - r.lock.RLock() - defer r.lock.RUnlock() - - t, ok := r.templaters[name] - if !ok { - return nil, errors.ErrNotSupported(KIND_TEMPLATER, name) - } - return t.templater(fs), nil -} - -func (r *registry) Describe(name string) (string, error) { - r.lock.RLock() - defer r.lock.RUnlock() - - t, ok := r.templaters[name] - if !ok { - return "", errors.ErrNotSupported(KIND_TEMPLATER, name) - } - return t.description, nil -} - -func (r *registry) KnownTypeNames() []string { - r.lock.RLock() - defer r.lock.RUnlock() - - return utils.StringMapKeys(r.templaters) -} - -func Usage(scheme Registry) string { - s := ` -There are several templaters that can be selected by the --templater option: -` - for _, t := range scheme.KnownTypeNames() { - desc, err := scheme.Describe(t) - if err == nil { - var title string - idx := strings.Index(desc, "\n") - if idx >= 0 { - title = desc[:idx] - desc = desc[idx+1:] - } - if strings.TrimSpace(desc) == "" { - s = fmt.Sprintf("%s- %s %s\n\n", s, t, title) - } else { - s = fmt.Sprintf("%s- %s %s\n\n%s", s, t, title, utils.IndentLines(desc, " ")) - } - if !strings.HasSuffix(s, "\n") { - s += "\n" - } - } - } - return s -} - -var _registry = NewRegistry() - -func Register(name string, fac TemplaterFactory, desc string) { - _registry.Register(name, fac, desc) -} - -func DefaultRegistry() Registry { - return _registry -} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go deleted file mode 100644 index 39148ba27..000000000 --- a/pkg/utils/utils.go +++ /dev/null @@ -1,357 +0,0 @@ -package utils - -import ( - "archive/tar" - "bytes" - "compress/gzip" - crypto "crypto/rand" - "encoding/json" - "errors" - "fmt" - "io" - "math/big" - "math/rand" - "net/http" - "os" - "reflect" - "sort" - "strings" - "time" - - "github.com/mandelsoft/vfs/pkg/vfs" - "github.com/modern-go/reflect2" - "github.com/spf13/cobra" - "golang.org/x/exp/maps" - "golang.org/x/exp/slices" - "sigs.k8s.io/yaml" - - ocmlog "github.com/open-component-model/ocm/pkg/logging" -) - -// PrintPrettyYaml prints the given objects as yaml if enabled. -func PrintPrettyYaml(obj interface{}, enabled bool) { - if !enabled { - return - } - - data, err := yaml.Marshal(obj) - if err != nil { - //nolint: forbidigo // Intentional Println to not mess up potential output parsers. - fmt.Println(err) - return - } - - //nolint: forbidigo // Intentional Println. - fmt.Println(string(data)) -} - -// GetFileType returns the mimetype of a file. -func GetFileType(fs vfs.FileSystem, path string) (string, error) { - file, err := fs.Open(path) - if err != nil { - return "", err - } - defer file.Close() - // see http://golang.org/pkg/net/http/#DetectContentType for the 512 bytes - buf := make([]byte, 512) - _, err = file.Read(buf) - if err != nil { - return "", err - } - return http.DetectContentType(buf), nil -} - -// CleanMarkdownUsageFunc removes Markdown tags from the long usage of the command. -// With this func it is possible to generate the Markdown docs but still have readable commandline help func. -// Note: currently only "
" tags are removed.
-func CleanMarkdownUsageFunc(cmd *cobra.Command) {
-	defaultHelpFunc := cmd.HelpFunc()
-	cmd.SetHelpFunc(func(cmd *cobra.Command, s []string) {
-		cmd.Long = strings.ReplaceAll(cmd.Long, "
", "")
-		cmd.Long = strings.ReplaceAll(cmd.Long, "
", "") - defaultHelpFunc(cmd, s) - }) -} - -// RawJSON converts an arbitrary value to json.RawMessage. -func RawJSON(value interface{}) (*json.RawMessage, error) { - jsonval, err := json.Marshal(value) - if err != nil { - return nil, err - } - return (*json.RawMessage)(&jsonval), nil -} - -// Gzip applies gzip compression to an arbitrary byte slice. -func Gzip(data []byte, compressionLevel int) ([]byte, error) { - buf := bytes.NewBuffer([]byte{}) - gzipWriter, err := gzip.NewWriterLevel(buf, compressionLevel) - if err != nil { - return nil, fmt.Errorf("unable to create gzip writer: %w", err) - } - defer gzipWriter.Close() - - if _, err = gzipWriter.Write(data); err != nil { - return nil, fmt.Errorf("unable to write to stream: %w", err) - } - - if err = gzipWriter.Close(); err != nil { - return nil, fmt.Errorf("unable to close writer: %w", err) - } - - return buf.Bytes(), nil -} - -var chars = []rune("abcdefghijklmnopqrstuvwxyz1234567890") - -// RandomString creates a new random string with the given length. -func RandomString(n int) string { - b := make([]rune, n) - for i := range b { - var value int - if v, err := crypto.Int(crypto.Reader, big.NewInt(int64(len(chars)))); err == nil { - value = int(v.Int64()) - } else { - // insecure fallback to provide a valid result - ocmlog.Logger().Error("failed to generate random number", "error", err.Error()) - value = rand.Intn(len(chars)) //nolint: gosec // only used as fallback - } - b[i] = chars[value] - } - return string(b) -} - -// SafeConvert converts a byte slice to string. -// If the byte slice is nil, an empty string is returned. -func SafeConvert(bytes []byte) string { - if bytes == nil { - return "" - } - - return string(bytes) -} - -const ( - BYTE = 1.0 << (10 * iota) - KIBIBYTE - MEBIBYTE - GIBIBYTE -) - -// BytesString converts bytes into a human-readable string. -// This function is inspired by https://www.reddit.com/r/golang/comments/8micn7/review_bytes_to_human_readable_format/ -func BytesString(bytes uint64, accuracy int) string { - unit := "" - value := float32(bytes) - - switch { - case bytes >= GIBIBYTE: - unit = "GiB" - value /= GIBIBYTE - case bytes >= MEBIBYTE: - unit = "MiB" - value /= MEBIBYTE - case bytes >= KIBIBYTE: - unit = "KiB" - value /= KIBIBYTE - case bytes >= BYTE: - unit = "bytes" - case bytes == 0: - return "0" - } - - stringValue := strings.TrimSuffix( - fmt.Sprintf("%.2f", value), "."+strings.Repeat("0", accuracy), - ) - - return fmt.Sprintf("%s %s", stringValue, unit) -} - -// WriteFileToTARArchive writes a new file with name=filename and content=contentReader to archiveWriter. -func WriteFileToTARArchive(filename string, contentReader io.Reader, archiveWriter *tar.Writer) error { - if filename == "" { - return errors.New("filename must not be empty") - } - - if contentReader == nil { - return errors.New("contentReader must not be nil") - } - - if archiveWriter == nil { - return errors.New("archiveWriter must not be nil") - } - - tempfile, err := os.CreateTemp("", "") - if err != nil { - return fmt.Errorf("unable to create tempfile: %w", err) - } - defer func() { - tempfile.Close() - os.Remove(tempfile.Name()) - }() - - fsize, err := io.Copy(tempfile, contentReader) - if err != nil { - return fmt.Errorf("unable to copy content to tempfile: %w", err) - } - - if _, err := tempfile.Seek(0, io.SeekStart); err != nil { - return fmt.Errorf("unable to seek to beginning of tempfile: %w", err) - } - - header := tar.Header{ - Name: filename, - Size: fsize, - Mode: 0o600, - ModTime: time.Now(), - } - - if err := archiveWriter.WriteHeader(&header); err != nil { - return fmt.Errorf("unable to write tar header: %w", err) - } - - if _, err := io.Copy(archiveWriter, tempfile); err != nil { - return fmt.Errorf("unable to write file to tar archive: %w", err) - } - - return nil -} - -func IndentLines(orig string, gap string, skipfirst ...bool) string { - return JoinIndentLines(strings.Split(strings.TrimPrefix(orig, "\n"), "\n"), gap, skipfirst...) -} - -func JoinIndentLines(orig []string, gap string, skipfirst ...bool) string { - if len(orig) == 0 { - return "" - } - skip := false - for _, b := range skipfirst { - skip = skip || b - } - - s := "" - if !skip { - s = gap - } - return s + strings.Join(orig, "\n"+gap) -} - -func StringMapKeys[K ~string, E any](m map[K]E) []K { - if m == nil { - return nil - } - keys := maps.Keys(m) - slices.Sort(keys) - return keys -} - -type Comparable[K any] interface { - Compare(o K) int -} - -func Sort[K Comparable[K]](a []K) { - sort.Slice(a, func(i, j int) bool { return a[i].Compare(a[j]) < 0 }) -} - -func MapKeys[K comparable, E any](m map[K]E) []K { - if m == nil { - return nil - } - - keys := []K{} - for k := range m { - keys = append(keys, k) - } - return keys -} - -type ComparableMapKey[K any] interface { - Comparable[K] - comparable -} - -func SortedMapKeys[K ComparableMapKey[K], E any](m map[K]E) []K { - if m == nil { - return nil - } - - keys := []K{} - for k := range m { - keys = append(keys, k) - } - Sort(keys) - return keys -} - -// Optional returns the first optional non-zero element given as variadic argument, -// if given, or the zero element as default. -func Optional[T any](list ...T) T { - var zero T - for _, e := range list { - if !reflect.DeepEqual(e, zero) { - return e - } - } - return zero -} - -// OptionalDefaulted returns the first optional non-nil element given as variadic -// argument, or the given default element. For value types a given zero -// argument is excepted, also. -func OptionalDefaulted[T any](def T, list ...T) T { - for _, e := range list { - if !reflect2.IsNil(e) { - return e - } - } - return def -} - -// OptionalDefaultedBool checks all args for true. If arg is given -// the given default is returned. -func OptionalDefaultedBool(def bool, list ...bool) bool { - if len(list) == 0 { - return def - } - for _, e := range list { - if e { - return e - } - } - return false -} - -// GetOptionFlag returns the flag value used to set a bool option -// based on optionally specified explicit value(s). -// The default value is to enable the option (true). -func GetOptionFlag(list ...bool) bool { - return OptionalDefaultedBool(true, list...) -} - -func IsNil(o interface{}) bool { - return reflect2.IsNil(o) -} - -// Must expect a result to be provided without error. -func Must[T any](o T, err error) T { - if err != nil { - panic(fmt.Errorf("expected a %T, but got error %w", o, err)) - } - return o -} - -func IgnoreError(_ error) { -} - -func BoolP[T ~bool](b T) *bool { - v := bool(b) - return &v -} - -func AsBool(b *bool, def ...bool) bool { - if b == nil && len(def) > 0 { - return Optional(def...) - } - return b != nil && *b -} diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go deleted file mode 100644 index 262fcacd6..000000000 --- a/pkg/utils/utils_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package utils_test - -import ( - "archive/tar" - "bytes" - "io" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/utils" -) - -var _ = Describe("utils", func() { - Context("WriteFileToTARArchive", func() { - It("should write file", func() { - fname := "testfile" - content := []byte("testcontent") - - archiveBuf := bytes.NewBuffer([]byte{}) - tw := tar.NewWriter(archiveBuf) - - Expect(utils.WriteFileToTARArchive(fname, bytes.NewReader(content), tw)).To(Succeed()) - Expect(tw.Close()).To(Succeed()) - - tr := tar.NewReader(archiveBuf) - fheader, err := tr.Next() - Expect(err).ToNot(HaveOccurred()) - Expect(fheader.Name).To(Equal(fname)) - - actualContentBuf := bytes.NewBuffer([]byte{}) - _, err = io.Copy(actualContentBuf, tr) - Expect(err).ToNot(HaveOccurred()) - Expect(actualContentBuf.Bytes()).To(Equal(content)) - - _, err = tr.Next() - Expect(err).To(Equal(io.EOF)) - }) - - It("should write empty file", func() { - fname := "testfile" - - archiveBuf := bytes.NewBuffer([]byte{}) - tw := tar.NewWriter(archiveBuf) - - Expect(utils.WriteFileToTARArchive(fname, bytes.NewReader([]byte{}), tw)).To(Succeed()) - Expect(tw.Close()).To(Succeed()) - - tr := tar.NewReader(archiveBuf) - fheader, err := tr.Next() - Expect(err).ToNot(HaveOccurred()) - Expect(fheader.Name).To(Equal(fname)) - - actualContentBuf := bytes.NewBuffer([]byte{}) - contentLenght, err := io.Copy(actualContentBuf, tr) - Expect(err).ToNot(HaveOccurred()) - Expect(contentLenght).To(Equal(int64(0))) - - _, err = tr.Next() - Expect(err).To(Equal(io.EOF)) - }) - - It("should return error if filename is empty", func() { - tw := tar.NewWriter(bytes.NewBuffer([]byte{})) - contentReader := bytes.NewReader([]byte{}) - Expect(utils.WriteFileToTARArchive("", contentReader, tw)).To(MatchError("filename must not be empty")) - }) - - It("should return error if contentReader is nil", func() { - tw := tar.NewWriter(bytes.NewBuffer([]byte{})) - Expect(utils.WriteFileToTARArchive("testfile", nil, tw)).To(MatchError("contentReader must not be nil")) - }) - - It("should return error if outArchive is nil", func() { - contentReader := bytes.NewReader([]byte{}) - Expect(utils.WriteFileToTARArchive("testfile", contentReader, nil)).To(MatchError("archiveWriter must not be nil")) - }) - }) -}) diff --git a/pkg/version/version.go b/pkg/version/version.go deleted file mode 100644 index 1c7ef18fc..000000000 --- a/pkg/version/version.go +++ /dev/null @@ -1,116 +0,0 @@ -package version - -import ( - "fmt" - "runtime" - "strconv" - "strings" - - "github.com/Masterminds/semver/v3" - - "github.com/open-component-model/ocm" -) - -var ( - gitVersion = "0.0.0-dev" - gitCommit string - gitTreeState string - buildDate = "1970-01-01T00:00:00Z" -) - -func init() { - if gitVersion == "0.0.0-dev" { - // gitVersion = strings.TrimSpace(string(MustAsset("../../VERSION"))) - gitVersion = strings.TrimSpace(ocm.Version) - } -} - -type Info struct { - Major string `json:"major"` - Minor string `json:"minor"` - Patch string `json:"patch"` - PreRelease string `json:"prerelease"` - Meta string `json:"meta"` - GitVersion string `json:"gitVersion"` - GitCommit string `json:"gitCommit"` - GitTreeState string `json:"gitTreeState"` - BuildDate string `json:"buildDate"` - GoVersion string `json:"goVersion"` - Compiler string `json:"compiler"` - Platform string `json:"platform"` -} - -// String returns info as a human-friendly version string. -func (info Info) String() string { - return info.GitVersion -} - -// String returns info as a short semantic version string (0.8.15). -func (info Info) SemVer() string { - return info.Major + "." + info.Minor + "." + info.Patch -} - -// String returns current Release version. -func Current() string { - return Get().SemVer() -} - -// GetInterface returns the overall codebase version. It's for detecting -// what code a binary was built from. -// These variables typically come from -ldflags settings and in -// their absence fallback to the settings in pkg/version/base.go. -func Get() Info { - var ( - gitMajor string - gitMinor string - gitPatch string = "0" - gitPre string - gitMeta string - ) - - v, err := semver.NewVersion(gitVersion) - if err == nil { - gitMajor = strconv.Itoa(int(v.Major())) - gitMinor = strconv.Itoa(int(v.Minor())) - gitPatch = strconv.Itoa(int(v.Patch())) - gitPre = v.Prerelease() - gitMeta = v.Metadata() - } else { - version := gitVersion - if i := strings.Index(version, "-"); i >= 0 { - gitPre = version[i+1:] - version = version[:i] - } - if i := strings.Index(version, "+"); i >= 0 { - gitMeta = version[i+1:] - version = version[:i] - } - if i := strings.Index(gitPre, "+"); i >= 0 { - gitMeta = gitPre[i+1:] - gitPre = gitPre[:i] - } - v := strings.Split(version, ".") - if len(v) >= 2 { - gitMajor = v[0] - gitMinor = v[1] - if len(v) >= 3 { - gitPatch = v[2] - } - } - } - - return Info{ - Major: gitMajor, - Minor: gitMinor, - Patch: gitPatch, - PreRelease: gitPre, - Meta: gitMeta, - GitVersion: gitVersion, - GitCommit: gitCommit, - GitTreeState: gitTreeState, - BuildDate: buildDate, - GoVersion: runtime.Version(), - Compiler: runtime.Compiler, - Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), - } -} diff --git a/testdata/component-descriptor.yaml b/testdata/component-descriptor.yaml deleted file mode 100644 index 021678f8b..000000000 --- a/testdata/component-descriptor.yaml +++ /dev/null @@ -1,57 +0,0 @@ -meta: - schemaVersion: 'v2' - -component: - name: 'github.com/gardener/gardener' - version: 'v1.7.2' - - repositoryContexts: - - type: 'ociRegistry' - baseUrl: 'eu.gcr.io/gardener-project/components/dev' - componentNameMapping: 'urlPath' - - provider: 'internal' - - labels: - - name: 'a_label' - value: 'a_value' - - name: 'another_label/with/path' - value: - another_value: 'with_nested_dict' - - sources: - - name: 'github_com_gardener_gardener' - type: 'git' - version: 'v1.7.2' - labels: [] - access: - type: 'github' - repoUrl: 'github.com/gardener/gardener' - ref: 'refs/tags/v1.7.2' - - componentReferences: - - name: 'my-etcd-druid' - componentName: 'github.com/gardener/etcd-druid' - extraIdentity: {} - version: 'v0.3.0' - labels: [] - - resources: - - name: 'apiserver' - version: 'v1.7.2' # version is implied by component, i.e. v1.7.2 in this case - extraIdentity: {} - type: 'ociImage' - relation: 'local' - labels: [] - access: - type: 'ociRegistry' - imageReference: 'eu.gcr.io/gardener-project/gardener/apiserver:v1.7.4' - - name: 'grafana' - version: '7.0.3' - extraIdentity: {} - type: 'ociImage' - relation: 'external' - labels: [] - access: - type: 'ociRegistry' - imageReference: 'registry-1.docker.io/grafana/grafana/7.0.3' \ No newline at end of file diff --git a/testdata/references.yaml b/testdata/references.yaml deleted file mode 100644 index 06d0c96bf..000000000 --- a/testdata/references.yaml +++ /dev/null @@ -1,4 +0,0 @@ -name: data -componentName: github.com/mandelsoft/playground -version: v1.2.3 -